From 8d60bf8a7e0d8e3b7035385071a378dbfa9cb3f2 Mon Sep 17 00:00:00 2001 From: Eirik Nygaard Date: Fri, 15 Jul 2005 14:46:17 +0000 Subject: [PATCH] Commit untouched SCTP files from KAME originally written by Randall Stewart. SCTP or Stream Control Transmission Protocol is designed so you can have multiple streams within the same connection. Obtained from: KAME --- sys/netinet/sctp.h | 301 + sys/netinet/sctp_asconf.c | 2917 +++++++++ sys/netinet/sctp_asconf.h | 66 + sys/netinet/sctp_callout.h | 67 + sys/netinet/sctp_constants.h | 832 +++ sys/netinet/sctp_crc32.c | 181 + sys/netinet/sctp_crc32.h | 49 + sys/netinet/sctp_hashdriver.c | 262 + sys/netinet/sctp_hashdriver.h | 43 + sys/netinet/sctp_header.h | 489 ++ sys/netinet/sctp_indata.c | 4751 +++++++++++++++ sys/netinet/sctp_indata.h | 63 + sys/netinet/sctp_input.c | 4420 ++++++++++++++ sys/netinet/sctp_input.h | 45 + sys/netinet/sctp_output.c | 10493 ++++++++++++++++++++++++++++++++ sys/netinet/sctp_output.h | 153 + sys/netinet/sctp_pcb.c | 5207 ++++++++++++++++ sys/netinet/sctp_pcb.h | 707 +++ sys/netinet/sctp_peeloff.c | 223 + sys/netinet/sctp_peeloff.h | 59 + sys/netinet/sctp_sha1.c | 260 + sys/netinet/sctp_sha1.h | 86 + sys/netinet/sctp_structs.h | 655 ++ sys/netinet/sctp_timer.c | 1561 +++++ sys/netinet/sctp_timer.h | 74 + sys/netinet/sctp_uio.h | 524 ++ sys/netinet/sctp_usrreq.c | 4815 +++++++++++++++ sys/netinet/sctp_var.h | 275 + sys/netinet/sctputil.c | 3844 ++++++++++++ sys/netinet/sctputil.h | 283 + sys/netinet6/sctp6_usrreq.c | 1829 ++++++ sys/netinet6/sctp6_var.h | 65 + 32 files changed, 45599 insertions(+) create mode 100644 sys/netinet/sctp.h create mode 100644 sys/netinet/sctp_asconf.c create mode 100644 sys/netinet/sctp_asconf.h create mode 100644 sys/netinet/sctp_callout.h create mode 100644 sys/netinet/sctp_constants.h create mode 100644 sys/netinet/sctp_crc32.c create mode 100644 sys/netinet/sctp_crc32.h create mode 100644 sys/netinet/sctp_hashdriver.c create mode 100644 sys/netinet/sctp_hashdriver.h create mode 100644 sys/netinet/sctp_header.h create mode 100644 sys/netinet/sctp_indata.c create mode 100644 sys/netinet/sctp_indata.h create mode 100644 sys/netinet/sctp_input.c create mode 100644 sys/netinet/sctp_input.h create mode 100644 sys/netinet/sctp_output.c create mode 100644 sys/netinet/sctp_output.h create mode 100644 sys/netinet/sctp_pcb.c create mode 100644 sys/netinet/sctp_pcb.h create mode 100644 sys/netinet/sctp_peeloff.c create mode 100644 sys/netinet/sctp_peeloff.h create mode 100644 sys/netinet/sctp_sha1.c create mode 100644 sys/netinet/sctp_sha1.h create mode 100644 sys/netinet/sctp_structs.h create mode 100644 sys/netinet/sctp_timer.c create mode 100644 sys/netinet/sctp_timer.h create mode 100644 sys/netinet/sctp_uio.h create mode 100644 sys/netinet/sctp_usrreq.c create mode 100644 sys/netinet/sctp_var.h create mode 100644 sys/netinet/sctputil.c create mode 100644 sys/netinet/sctputil.h create mode 100644 sys/netinet6/sctp6_usrreq.c create mode 100644 sys/netinet6/sctp6_var.h diff --git a/sys/netinet/sctp.h b/sys/netinet/sctp.h new file mode 100644 index 0000000000..52db65b8dc --- /dev/null +++ b/sys/netinet/sctp.h @@ -0,0 +1,301 @@ +/* $KAME: sctp.h,v 1.17 2004/08/17 04:06:15 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp.h,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +#ifndef _NETINET_SCTP_H_ +#define _NETINET_SCTP_H_ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include + +/* + * SCTP protocol - RFC2960. + */ + +struct sctphdr { + u_int16_t src_port; /* source port */ + u_int16_t dest_port; /* destination port */ + u_int32_t v_tag; /* verification tag of packet */ + u_int32_t checksum; /* Adler32 C-Sum */ + /* chunks follow... */ +}; + +/* + * SCTP Chunks + */ +struct sctp_chunkhdr { + u_int8_t chunk_type; /* chunk type */ + u_int8_t chunk_flags; /* chunk flags */ + u_int16_t chunk_length; /* chunk length */ + /* optional params follow */ +}; + +/* + * SCTP chunk parameters + */ +struct sctp_paramhdr { + u_int16_t param_type; /* parameter type */ + u_int16_t param_length; /* parameter length */ +}; + +/* + * user socket options + */ +/* read-write options */ +#define SCTP_NODELAY 0x00000001 +#define SCTP_MAXSEG 0x00000002 +#define SCTP_ASSOCINFO 0x00000003 + +#define SCTP_INITMSG 0x00000004 +#define SCTP_AUTOCLOSE 0x00000005 +#define SCTP_SET_PEER_PRIMARY_ADDR 0x00000006 +#define SCTP_PRIMARY_ADDR 0x00000007 + +/* read-only options */ +#define SCTP_STATUS 0x00000008 +#define SCTP_PCB_STATUS 0x00000009 + +/* ancillary data/notification interest options */ +#define SCTP_EVENTS 0x0000000a +/* sctp_opt_info params */ +#define SCTP_PEER_ADDR_PARAMS 0x0000000b +#define SCTP_GET_PEER_ADDR_INFO 0x0000000c +/* Hidden socket option that gets the addresses */ +#define SCTP_GET_PEER_ADDRESSES 0x0000000d +#define SCTP_GET_LOCAL_ADDRESSES 0x0000000e +/* + * Blocking I/O is enabled on any TCP type socket by default. + * For the UDP model if this is turned on then the socket buffer is + * shared for send resources amongst all associations. The default + * for the UDP model is that is SS_NBIO is set. Which means all associations + * have a seperate send limit BUT they will NOT ever BLOCK instead + * you will get an error back EAGAIN if you try to send to much. If + * you want the blocking symantics you set this option at the cost + * of sharing one socket send buffer size amongst all associations. + * Peeled off sockets turn this option off and block... but since both TCP and + * peeled off sockets have only one assoc per socket this is fine. + * It probably does NOT make sense to set this on SS_NBIO on a TCP model OR + * peeled off UDP model, but we do allow you to do so. You just use + * the normal syscall to toggle SS_NBIO the way you want. + */ +/* Blocking I/O is controled by the SS_NBIO flag on the + * socket state so_state field. + */ +#define SCTP_GET_SNDBUF_USE 0x0000000f +/* latter added read/write */ +#define SCTP_ADAPTION_LAYER 0x00000010 +#define SCTP_DISABLE_FRAGMENTS 0x00000011 +/* sctp_bindx() flags as socket options */ +#define SCTP_BINDX_ADD_ADDR 0x00000012 +#define SCTP_BINDX_REM_ADDR 0x00000013 +/* return the total count in bytes needed to hold all local addresses bound */ +#define SCTP_GET_LOCAL_ADDR_SIZE 0x00000014 +/* Without this applied we will give V4 and V6 addresses on a V6 socket */ +#define SCTP_I_WANT_MAPPED_V4_ADDR 0x00000015 +/* Return the total count in bytes needed to hold the remote address */ +#define SCTP_GET_REMOTE_ADDR_SIZE 0x00000016 +#define SCTP_GET_PEGS 0x00000017 +#define SCTP_DEFAULT_SEND_PARAM 0x00000018 +#define SCTP_SET_DEBUG_LEVEL 0x00000019 +#define SCTP_RTOINFO 0x0000001a +#define SCTP_AUTO_ASCONF 0x0000001b +#define SCTP_MAXBURST 0x0000001c +#define SCTP_GET_STAT_LOG 0x0000001d +#define SCTP_CONNECT_X 0x0000001e /* hidden opt for connectx */ +#define SCTP_RESET_STREAMS 0x0000001f +#define SCTP_CONNECT_X_DELAYED 0x00000020 /* hidden opt for connectx_delayed + * part of sctp_sendx() + */ +#define SCTP_CONNECT_X_COMPLETE 0x00000021 +#define SCTP_GET_ASOC_ID_LIST 0x00000022 + +/* Other BSD items */ +#define SCTP_GET_NONCE_VALUES 0x00000023 +#define SCTP_DELAYED_ACK_TIME 0x00000024 + +/* Things for the AUTH draft possibly */ +#define SCTP_PEER_PUBLIC_KEY 0x00000100 /* get the peers public key */ +#define SCTP_MY_PUBLIC_KEY 0x00000101 /* get/set my endpoints public key */ +#define SCTP_SET_AUTH_SECRET 0x00000102 /* get/set my shared secret */ +#define SCTP_SET_AUTH_CHUNKS 0x00000103/* specify what chunks you want + * the system may have additional requirments + * as well. I.e. probably ASCONF/ASCONF-ACK no matter + * if you want it or not. + */ +/* Debug things that need to be purged */ +#define SCTP_SET_INITIAL_DBG_SEQ 0x00001f00 +#define SCTP_RESET_PEGS 0x00002000 +#define SCTP_CLR_STAT_LOG 0x00002100 + +/* + * user state values + */ +#define SCTP_CLOSED 0x0000 +#define SCTP_BOUND 0x1000 +#define SCTP_LISTEN 0x2000 +#define SCTP_COOKIE_WAIT 0x0002 +#define SCTP_COOKIE_ECHOED 0x0004 +#define SCTP_ESTABLISHED 0x0008 +#define SCTP_SHUTDOWN_SENT 0x0010 +#define SCTP_SHUTDOWN_RECEIVED 0x0020 +#define SCTP_SHUTDOWN_ACK_SENT 0x0040 +#define SCTP_SHUTDOWN_PENDING 0x0080 + +/* + * SCTP operational error codes (user visible) + */ +#define SCTP_ERROR_NO_ERROR 0x0000 +#define SCTP_ERROR_INVALID_STREAM 0x0001 +#define SCTP_ERROR_MISSING_PARAM 0x0002 +#define SCTP_ERROR_STALE_COOKIE 0x0003 +#define SCTP_ERROR_OUT_OF_RESOURCES 0x0004 +#define SCTP_ERROR_UNRESOLVABLE_ADDR 0x0005 +#define SCTP_ERROR_UNRECOG_CHUNK 0x0006 +#define SCTP_ERROR_INVALID_PARAM 0x0007 +#define SCTP_ERROR_UNRECOG_PARAM 0x0008 +#define SCTP_ERROR_NO_USER_DATA 0x0009 +#define SCTP_ERROR_COOKIE_IN_SHUTDOWN 0x000a +/* draft-ietf-tsvwg-sctpimpguide */ +#define SCTP_ERROR_RESTART_NEWADDRS 0x000b +/* draft-ietf-tsvwg-addip-sctp */ +#define SCTP_ERROR_DELETE_LAST_ADDR 0x0100 +#define SCTP_ERROR_RESOURCE_SHORTAGE 0x0101 +#define SCTP_ERROR_DELETE_SOURCE_ADDR 0x0102 +#define SCTP_ERROR_ILLEGAL_ASCONF_ACK 0x0103 + +/* + * error cause parameters (user visisble) + */ +struct sctp_error_cause { + u_int16_t code; + u_int16_t length; + /* optional cause-specific info may follow */ +}; + +struct sctp_error_invalid_stream { + struct sctp_error_cause cause; /* code=SCTP_ERROR_INVALID_STREAM */ + u_int16_t stream_id; /* stream id of the DATA in error */ + u_int16_t reserved; +}; + +struct sctp_error_missing_param { + struct sctp_error_cause cause; /* code=SCTP_ERROR_MISSING_PARAM */ + u_int32_t num_missing_params; /* number of missing parameters */ + /* u_int16_t param_type's follow */ +}; + +struct sctp_error_stale_cookie { + struct sctp_error_cause cause; /* code=SCTP_ERROR_STALE_COOKIE */ + u_int32_t stale_time; /* time in usec of staleness */ +}; + +struct sctp_error_out_of_resource { + struct sctp_error_cause cause; /* code=SCTP_ERROR_OUT_OF_RESOURCES */ +}; + +struct sctp_error_unresolv_addr { + struct sctp_error_cause cause; /* code=SCTP_ERROR_UNRESOLVABLE_ADDR */ + +}; + +struct sctp_error_unrecognized_chunk { + struct sctp_error_cause cause; /* code=SCTP_ERROR_UNRECOG_CHUNK */ + struct sctp_chunkhdr ch; /* header from chunk in error */ +}; + +#define HAVE_SCTP 1 +#define HAVE_KERNEL_SCTP 1 +#define HAVE_SCTP_PRSCTP 1 +#define HAVE_SCTP_ADDIP 1 +#define HAVE_SCTP_CANSET_PRIMARY 1 +#define HAVE_SCTP_SAT_NETWORK_CAPABILITY1 +#define HAVE_SCTP_MULTIBUF 1 +#define HAVE_SCTP_NOCONNECT 0 +#define HAVE_SCTP_ECN_NONCE 1 /* ECN Nonce option */ + +/* Main SCTP chunk types, we place + * these here since that way natd and f/w's + * in user land can find them. + */ +#define SCTP_DATA 0x00 +#define SCTP_INITIATION 0x01 +#define SCTP_INITIATION_ACK 0x02 +#define SCTP_SELECTIVE_ACK 0x03 +#define SCTP_HEARTBEAT_REQUEST 0x04 +#define SCTP_HEARTBEAT_ACK 0x05 +#define SCTP_ABORT_ASSOCIATION 0x06 +#define SCTP_SHUTDOWN 0x07 +#define SCTP_SHUTDOWN_ACK 0x08 +#define SCTP_OPERATION_ERROR 0x09 +#define SCTP_COOKIE_ECHO 0x0a +#define SCTP_COOKIE_ACK 0x0b +#define SCTP_ECN_ECHO 0x0c +#define SCTP_ECN_CWR 0x0d +#define SCTP_SHUTDOWN_COMPLETE 0x0e + +/* draft-ietf-tsvwg-addip-sctp */ +#define SCTP_ASCONF 0xc1 +#define SCTP_ASCONF_ACK 0x80 + +/* draft-ietf-stewart-prsctp */ +#define SCTP_FORWARD_CUM_TSN 0xc0 + +/* draft-ietf-stewart-pktdrpsctp */ +#define SCTP_PACKET_DROPPED 0x81 + +/* draft-ietf-stewart-strreset-xxx */ +#define SCTP_STREAM_RESET 0x82 + +/* ABORT and SHUTDOWN COMPLETE FLAG */ +#define SCTP_HAD_NO_TCB 0x01 + +/* Packet dropped flags */ +#define SCTP_FROM_MIDDLE_BOX SCTP_HAD_NO_TCB +#define SCTP_BADCRC 0x02 +#define SCTP_PACKET_TRUNCATED 0x04 + +#define SCTP_SAT_NETWORK_MIN 400 /* min ms for RTT to set satellite time */ +#define SCTP_SAT_NETWORK_BURST_INCR 2 /* how many times to multiply maxburst in sat */ +/* Data Chuck Specific Flags */ +#define SCTP_DATA_FRAG_MASK 0x03 +#define SCTP_DATA_MIDDLE_FRAG 0x00 +#define SCTP_DATA_LAST_FRAG 0x01 +#define SCTP_DATA_FIRST_FRAG 0x02 +#define SCTP_DATA_NOT_FRAG 0x03 +#define SCTP_DATA_UNORDERED 0x04 + +/* ECN Nonce: SACK Chunk Specific Flags */ +#define SCTP_SACK_NONCE_SUM 0x01 + +#include + +#endif /* !_NETINET_SCTP_H_ */ diff --git a/sys/netinet/sctp_asconf.c b/sys/netinet/sctp_asconf.c new file mode 100644 index 0000000000..91e0e3ee11 --- /dev/null +++ b/sys/netinet/sctp_asconf.c @@ -0,0 +1,2917 @@ +/* $KAME: sctp_asconf.c,v 1.23 2004/08/17 06:28:01 t-momose Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_asconf.c,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#if !(defined(__OpenBSD__) || defined(__APPLE__)) +#include "opt_ipsec.h" +#endif +#if defined(__FreeBSD__) +#include "opt_compat.h" +#include "opt_inet6.h" +#include "opt_inet.h" +#endif +#if defined(__NetBSD__) +#include "opt_inet.h" +#endif + +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef INET6 +#include +#include +#if defined(__FreeBSD__) || (__NetBSD__) +#include +#elif defined(__OpenBSD__) +#include +#endif +#include +#include +#endif /* INET6 */ + +#include + +#include +#include +#include +#include +#include +#include + +/* + * debug flags: + * SCTP_DEBUG_ASCONF1: protocol info, general info and errors + * SCTP_DEBUG_ASCONF2: detailed info + */ +#ifdef SCTP_DEBUG +extern u_int32_t sctp_debug_on; + +#if defined(SCTP_BASE_FREEBSD) || defined(__APPLE__) +#define strlcpy strncpy +#endif +#endif /* SCTP_DEBUG */ + +/* + * draft-ietf-tsvwg-addip-sctp + * + * Address management only currently supported + * For the bound all case: + * the asoc local addr list is always a "DO NOT USE" list + * For the subset bound case: + * If ASCONFs are allowed: + * the endpoint local addr list is the usable address list + * the asoc local addr list is the "DO NOT USE" list + * If ASCONFs are not allowed: + * the endpoint local addr list is the default usable list + * the asoc local addr list is the usable address list + * + * An ASCONF parameter queue exists per asoc which holds the pending + * address operations. Lists are updated upon receipt of ASCONF-ACK. + * + * Deleted addresses are always immediately removed from the lists as + * they will (shortly) no longer exist in the kernel. We send ASCONFs + * as a courtesy, only if allowed. + */ + +/* + * ASCONF parameter processing + * response_required: set if a reply is required (eg. SUCCESS_REPORT) + * returns a mbuf to an "error" response parameter or NULL/"success" if ok + * FIX: allocating this many mbufs on the fly is pretty inefficient... + */ + +static struct mbuf * +sctp_asconf_success_response(uint32_t id) +{ + struct mbuf *m_reply = NULL; + struct sctp_asconf_paramhdr *aph; + + MGET(m_reply, M_DONTWAIT, MT_DATA); + if (m_reply == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_success_response: couldn't get mbuf!\n"); + } +#endif /* SCTP_DEBUG */ + return NULL; + } + aph = mtod(m_reply, struct sctp_asconf_paramhdr *); + aph->correlation_id = id; + aph->ph.param_type = htons(SCTP_SUCCESS_REPORT); + aph->ph.param_length = sizeof(struct sctp_asconf_paramhdr); + m_reply->m_len = aph->ph.param_length; + aph->ph.param_length = htons(aph->ph.param_length); + + return m_reply; +} + +static struct mbuf * +sctp_asconf_error_response(uint32_t id, uint16_t cause, uint8_t *error_tlv, + uint16_t tlv_length) +{ + struct mbuf *m_reply = NULL; + struct sctp_asconf_paramhdr *aph; + struct sctp_error_cause *error; + uint8_t *tlv; + + MGET(m_reply, M_DONTWAIT, MT_DATA); + if (m_reply == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_error_response: couldn't get mbuf!\n"); + } +#endif /* SCTP_DEBUG */ + return NULL; + } + aph = mtod(m_reply, struct sctp_asconf_paramhdr *); + error = (struct sctp_error_cause *)(aph + 1); + + aph->correlation_id = id; + aph->ph.param_type = htons(SCTP_ERROR_CAUSE_IND); + error->code = htons(cause); + error->length = tlv_length + sizeof(struct sctp_error_cause); + aph->ph.param_length = error->length + + sizeof(struct sctp_asconf_paramhdr); + + if (aph->ph.param_length > MLEN) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_error_response: tlv_length (%xh) too big\n", + tlv_length); + } +#endif /* SCTP_DEBUG */ + sctp_m_freem(m_reply); /* discard */ + return NULL; + } + + if (error_tlv != NULL) { + tlv = (uint8_t *)(error + 1); + memcpy(tlv, error_tlv, tlv_length); + } + + m_reply->m_len = aph->ph.param_length; + error->length = htons(error->length); + aph->ph.param_length = htons(aph->ph.param_length); + + return m_reply; +} + +static struct mbuf * +sctp_process_asconf_add_ip(struct sctp_asconf_paramhdr *aph, + struct sctp_tcb *stcb, int response_required) +{ + struct mbuf *m_reply = NULL; + struct sockaddr_storage sa_store; + struct sctp_ipv4addr_param *v4addr; + uint16_t param_type, param_length, aparam_length; + struct sockaddr *sa; + struct sockaddr_in *sin; +#ifdef INET6 + struct sockaddr_in6 *sin6; + struct sctp_ipv6addr_param *v6addr; +#endif /* INET6 */ + + aparam_length = ntohs(aph->ph.param_length); + v4addr = (struct sctp_ipv4addr_param *)(aph + 1); +#ifdef INET6 + v6addr = (struct sctp_ipv6addr_param *)(aph + 1); +#endif /* INET6 */ + param_type = ntohs(v4addr->ph.param_type); + param_length = ntohs(v4addr->ph.param_length); + + sa = (struct sockaddr *)&sa_store; + switch (param_type) { + case SCTP_IPV4_ADDRESS: + if (param_length != sizeof(struct sctp_ipv4addr_param)) { + /* invalid param size */ + return NULL; + } + sin = (struct sockaddr_in *)&sa_store; + bzero(sin, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(struct sockaddr_in); + sin->sin_port = stcb->rport; + sin->sin_addr.s_addr = v4addr->addr; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_add_ip: adding "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ + break; + case SCTP_IPV6_ADDRESS: +#ifdef INET6 + if (param_length != sizeof(struct sctp_ipv6addr_param)) { + /* invalid param size */ + return NULL; + } + sin6 = (struct sockaddr_in6 *)&sa_store; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_port = stcb->rport; + memcpy((caddr_t)&sin6->sin6_addr, v6addr->addr, + sizeof(struct in6_addr)); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_add_ip: adding "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ +#else + /* IPv6 not enabled! */ + /* FIX ME: currently sends back an invalid param error */ + m_reply = sctp_asconf_error_response(aph->correlation_id, + SCTP_ERROR_INVALID_PARAM, (uint8_t *)aph, aparam_length); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_add_ip: v6 disabled- skipping "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ + return m_reply; +#endif /* INET6 */ + break; + default: + m_reply = sctp_asconf_error_response(aph->correlation_id, + SCTP_ERROR_UNRESOLVABLE_ADDR, (uint8_t *)aph, + aparam_length); + return m_reply; + } /* end switch */ + + /* add the address */ + if (sctp_add_remote_addr(stcb, sa, 0, 6) != 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_add_ip: error adding address\n"); + } +#endif /* SCTP_DEBUG */ + m_reply = sctp_asconf_error_response(aph->correlation_id, + SCTP_ERROR_RESOURCE_SHORTAGE, (uint8_t *)aph, + aparam_length); + } else { + /* notify upper layer */ + sctp_ulp_notify(SCTP_NOTIFY_ASCONF_ADD_IP, stcb, 0, sa); + if (response_required) { + m_reply = + sctp_asconf_success_response(aph->correlation_id); + } + } + + return m_reply; +} + +static struct mbuf * +sctp_process_asconf_delete_ip(struct mbuf *m, struct sctp_asconf_paramhdr *aph, + struct sctp_tcb *stcb, int response_required) +{ + struct mbuf *m_reply = NULL; + struct sockaddr_storage sa_store, sa_source; + struct sctp_ipv4addr_param *v4addr; + uint16_t param_type, param_length, aparam_length; + struct sockaddr *sa; + struct sockaddr_in *sin; + struct ip *iph; + int result; +#ifdef INET6 + struct sockaddr_in6 *sin6; + struct sctp_ipv6addr_param *v6addr; +#endif /* INET6 */ + + aparam_length = ntohs(aph->ph.param_length); + v4addr = (struct sctp_ipv4addr_param *)(aph + 1); +#ifdef INET6 + v6addr = (struct sctp_ipv6addr_param *)(aph + 1); +#endif /* INET6 */ + param_type = ntohs(v4addr->ph.param_type); + param_length = ntohs(v4addr->ph.param_length); + + /* get the source IP address for deletion check */ + iph = mtod(m, struct ip *); + if (iph->ip_v == IPVERSION) { + /* IPv4 source */ + sin = (struct sockaddr_in *)&sa_source; + bzero(sin, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(struct sockaddr_in); + sin->sin_port = stcb->rport; + sin->sin_addr.s_addr = iph->ip_src.s_addr; + } +#ifdef INET6 + else if (iph->ip_v == (IPV6_VERSION >> 4)) { + /* IPv6 source */ + struct ip6_hdr *ip6; + + sin6 = (struct sockaddr_in6 *)&sa_source; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_port = stcb->rport; + ip6 = mtod(m, struct ip6_hdr *); + sin6->sin6_addr = ip6->ip6_src; + } +#endif /* INET6 */ + else + return NULL; + + sa = (struct sockaddr *)&sa_store; + switch (param_type) { + case SCTP_IPV4_ADDRESS: + if (param_length != sizeof(struct sctp_ipv4addr_param)) { + /* invalid param size */ + return NULL; + } + sin = (struct sockaddr_in *)&sa_store; + bzero(sin, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(struct sockaddr_in); + sin->sin_port = stcb->rport; + sin->sin_addr.s_addr = v4addr->addr; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_delete_ip: deleting "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ + break; + case SCTP_IPV6_ADDRESS: + if (param_length != sizeof(struct sctp_ipv6addr_param)) { + /* invalid param size */ + return NULL; + } +#ifdef INET6 + sin6 = (struct sockaddr_in6 *)&sa_store; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_port = stcb->rport; + memcpy(&sin6->sin6_addr, v6addr->addr, + sizeof(struct in6_addr)); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_delete_ip: deleting "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ +#else + /* IPv6 not enabled! No "action" needed; just ack it */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_delete_ip: v6 disabled- ignoring: "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ + /* just respond with a "success" ASCONF-ACK */ + return NULL; +#endif /* INET6 */ + break; + default: + m_reply = sctp_asconf_error_response(aph->correlation_id, + SCTP_ERROR_UNRESOLVABLE_ADDR, (uint8_t *)aph, + aparam_length); + return m_reply; + } /* end switch */ + + /* make sure the source address is not being deleted */ + if ((memcmp(sa, &sa_source, sizeof(struct sockaddr_in)) == 0) +#ifdef INET6 + || (memcmp(sa, &sa_source, sizeof(struct sockaddr_in6)) == 0) +#endif /* INET6 */ + ) { + /* trying to delete the source address! */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_delete_ip: tried to delete source addr\n"); + } +#endif /* SCTP_DEBUG */ + m_reply = sctp_asconf_error_response(aph->correlation_id, + SCTP_ERROR_DELETE_SOURCE_ADDR, (uint8_t *)aph, + aparam_length); + return m_reply; + } + + /* delete the address */ + result = sctp_del_remote_addr(stcb, sa); + /* + * note if result == -2, the address doesn't exist in the asoc + * but since it's being deleted anyways, we just ack the delete + * -- but this probably means something has already gone awry + */ + if (result == -1) { + /* only one address in the asoc */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_delete_ip: tried to delete last IP addr!\n"); + } +#endif /* SCTP_DEBUG */ + m_reply = sctp_asconf_error_response(aph->correlation_id, + SCTP_ERROR_DELETE_LAST_ADDR, (uint8_t *)aph, + aparam_length); + } else { + /* notify upper layer */ + sctp_ulp_notify(SCTP_NOTIFY_ASCONF_DELETE_IP, stcb, 0, sa); + } + + if (response_required) { + m_reply = sctp_asconf_success_response(aph->correlation_id); + } + return m_reply; +} + +static struct mbuf * +sctp_process_asconf_set_primary(struct sctp_asconf_paramhdr *aph, + struct sctp_tcb *stcb, int response_required) +{ + struct mbuf *m_reply = NULL; + struct sockaddr_storage sa_store; + struct sctp_ipv4addr_param *v4addr; + uint16_t param_type, param_length, aparam_length; + struct sockaddr *sa; + struct sockaddr_in *sin; +#ifdef INET6 + struct sockaddr_in6 *sin6; + struct sctp_ipv6addr_param *v6addr; +#endif /* INET6 */ + + aparam_length = ntohs(aph->ph.param_length); + v4addr = (struct sctp_ipv4addr_param *)(aph + 1); +#ifdef INET6 + v6addr = (struct sctp_ipv6addr_param *)(aph + 1); +#endif /* INET6 */ + param_type = ntohs(v4addr->ph.param_type); + param_length = ntohs(v4addr->ph.param_length); + + sa = (struct sockaddr *)&sa_store; + switch (param_type) { + case SCTP_IPV4_ADDRESS: + if (param_length != sizeof(struct sctp_ipv4addr_param)) { + /* invalid param size */ + return NULL; + } + sin = (struct sockaddr_in *)&sa_store; + bzero(sin, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(struct sockaddr_in); + sin->sin_addr.s_addr = v4addr->addr; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_set_primary: "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ + break; + case SCTP_IPV6_ADDRESS: + if (param_length != sizeof(struct sctp_ipv6addr_param)) { + /* invalid param size */ + return NULL; + } +#ifdef INET6 + sin6 = (struct sockaddr_in6 *)&sa_store; + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + memcpy((caddr_t)&sin6->sin6_addr, v6addr->addr, + sizeof(struct in6_addr)); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_set_primary: "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ +#else + /* IPv6 not enabled! No "action" needed; just ack it */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_set_primary: v6 disabled- ignoring: "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ + /* just respond with a "success" ASCONF-ACK */ + return NULL; +#endif /* INET6 */ + break; + default: + m_reply = sctp_asconf_error_response(aph->correlation_id, + SCTP_ERROR_UNRESOLVABLE_ADDR, (uint8_t *)aph, + aparam_length); + return m_reply; + } /* end switch */ + + /* set the primary address */ + if (sctp_set_primary_addr(stcb, sa, NULL) == 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_set_primary: primary address set\n"); + } +#endif /* SCTP_DEBUG */ + /* notify upper layer */ + sctp_ulp_notify(SCTP_NOTIFY_ASCONF_SET_PRIMARY, stcb, 0, sa); + + if (response_required) { + m_reply = sctp_asconf_success_response(aph->correlation_id); + } + } else { + /* couldn't set the requested primary address! */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_asconf_set_primary: set primary failed!\n"); + } +#endif /* SCTP_DEBUG */ + /* must have been an invalid address, so report */ + m_reply = sctp_asconf_error_response(aph->correlation_id, + SCTP_ERROR_UNRESOLVABLE_ADDR, (uint8_t *)aph, + aparam_length); + } + + return m_reply; +} + +/* + * handles an ASCONF chunk + * if all parameters are processed ok, send a plain (empty) ASCONF-ACK + */ +void +sctp_handle_asconf(struct mbuf *m, unsigned int offset, struct sctp_asconf_chunk *cp, + struct sctp_tcb *stcb, struct sctp_nets *net) +{ + struct sctp_association *asoc; + uint32_t serial_num; + struct mbuf *m_ack, *m_result, *m_tail; + struct sctp_asconf_ack_chunk *ack_cp; + struct sctp_asconf_paramhdr *aph, *ack_aph; + struct sctp_ipv6addr_param *p_addr; + unsigned int asconf_limit; + int error = 0; /* did an error occur? */ + /* asconf param buffer */ + static u_int8_t aparam_buf[DEFAULT_PARAM_BUFFER]; + + /* verify minimum length */ + if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_asconf_chunk)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf: chunk too small = %xh\n", + ntohs(cp->ch.chunk_length)); + } +#endif /* SCTP_DEBUG */ + return; + } + + asoc = &stcb->asoc; + serial_num = ntohl(cp->serial_number); + + if (serial_num == asoc->asconf_seq_in) { + /* got a duplicate ASCONF */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf: got duplicate serial number = %xh\n", + serial_num); + } +#endif /* SCTP_DEBUG */ + /* resend last ASCONF-ACK... */ + sctp_send_asconf_ack(stcb, 1); + return; + } else if (serial_num != (asoc->asconf_seq_in + 1)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf: incorrect serial number = %xh (expected next = %xh)\n", + serial_num, asoc->asconf_seq_in+1); + } +#endif /* SCTP_DEBUG */ + return; + } + + /* it's the expected "next" sequence number, so process it */ + asoc->asconf_seq_in = serial_num; /* update sequence */ + /* get length of all the param's in the ASCONF */ + asconf_limit = offset + ntohs(cp->ch.chunk_length); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf: asconf_limit=%u, sequence=%xh\n", + asconf_limit, serial_num); + } +#endif /* SCTP_DEBUG */ + if (asoc->last_asconf_ack_sent != NULL) { + /* free last ASCONF-ACK message sent */ + sctp_m_freem(asoc->last_asconf_ack_sent); + asoc->last_asconf_ack_sent = NULL; + } + MGETHDR(m_ack, M_DONTWAIT, MT_DATA); + if (m_ack == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf: couldn't get mbuf!\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + m_tail = m_ack; /* current reply chain's tail */ + + /* fill in ASCONF-ACK header */ + ack_cp = mtod(m_ack, struct sctp_asconf_ack_chunk *); + ack_cp->ch.chunk_type = SCTP_ASCONF_ACK; + ack_cp->ch.chunk_flags = 0; + ack_cp->serial_number = htonl(serial_num); + /* set initial lengths (eg. just an ASCONF-ACK), ntohx at the end! */ + m_ack->m_len = sizeof(struct sctp_asconf_ack_chunk); + ack_cp->ch.chunk_length = sizeof(struct sctp_asconf_ack_chunk); + m_ack->m_pkthdr.len = sizeof(struct sctp_asconf_ack_chunk); + + /* skip the lookup address parameter */ + offset += sizeof(struct sctp_asconf_chunk); + p_addr = (struct sctp_ipv6addr_param *)sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr), (uint8_t *)&aparam_buf); + if (p_addr == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf: couldn't get lookup addr!\n"); + } +#endif /* SCTP_DEBUG */ + + /* respond with a missing/invalid mandatory parameter error */ + return; + } + /* param_length is already validated in process_control... */ + offset += ntohs(p_addr->ph.param_length); /* skip lookup addr */ + + /* get pointer to first asconf param in ASCONF */ + aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, sizeof(struct sctp_asconf_paramhdr), (uint8_t *)&aparam_buf); + /* get pointer to first asconf param in ASCONF-ACK */ + if (aph == NULL) { + printf("Gak in asconf\n"); + return; + } + ack_aph = (struct sctp_asconf_paramhdr *)(mtod(m_ack, caddr_t) + sizeof(struct sctp_asconf_ack_chunk)); + if (ack_aph == NULL) { + printf("Gak in asconf2\n"); + return; + } + + /* process through all parameters */ + while (aph != NULL) { + unsigned int param_length, param_type; + + param_type = ntohs(aph->ph.param_type); + param_length = ntohs(aph->ph.param_length); + if (offset + param_length > asconf_limit) { + /* parameter goes beyond end of chunk! */ + sctp_m_freem(m_ack); + return; + } + m_result = NULL; + + if (param_length > sizeof(aparam_buf)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf: param length (%u) larger than buffer size!\n", param_length); + } +#endif /* SCTP_DEBUG */ + sctp_m_freem(m_ack); + return; + } + if (param_length <= sizeof(struct sctp_paramhdr)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf: param length (%u) too short\n", param_length); + } +#endif /* SCTP_DEBUG */ + sctp_m_freem(m_ack); + } + + /* get the entire parameter */ + aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, param_length, aparam_buf); + if (aph == NULL) { + printf("Gag\n"); + sctp_m_freem(m_ack); + return; + } + switch (param_type) { + case SCTP_ADD_IP_ADDRESS: + asoc->peer_supports_asconf = 1; + m_result = sctp_process_asconf_add_ip(aph, stcb, error); + break; + case SCTP_DEL_IP_ADDRESS: + asoc->peer_supports_asconf = 1; + m_result = sctp_process_asconf_delete_ip(m, aph, stcb, + error); + break; + case SCTP_ERROR_CAUSE_IND: + /* not valid in an ASCONF chunk */ + break; + case SCTP_SET_PRIM_ADDR: + asoc->peer_supports_asconf_setprim = 1; + m_result = sctp_process_asconf_set_primary(aph, stcb, + error); + break; + case SCTP_SUCCESS_REPORT: + /* not valid in an ASCONF chunk */ + break; + case SCTP_ULP_ADAPTION: + /* FIX */ + break; + default: + if ((param_type & 0x8000) == 0) { + /* Been told to STOP at this param */ + asconf_limit = offset; + /* FIX FIX - We need to call sctp_arethere_unrecognized_parameters() + * to get a operr and send it for any param's with the + * 0x4000 bit set OR do it here ourselves... note we still + * must STOP if the 0x8000 bit is clear. + */ + } + /* unknown/invalid param type */ + break; + } /* switch */ + + /* add any (error) result to the reply mbuf chain */ + if (m_result != NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf: adding reply...\n"); + } +#endif /* SCTP_DEBUG */ + m_tail->m_next = m_result; + m_tail = m_result; + /* update lengths, make sure it's aligned too */ + m_result->m_len = SCTP_SIZE32(m_result->m_len); + m_ack->m_pkthdr.len += m_result->m_len; + ack_cp->ch.chunk_length += m_result->m_len; + /* set flag to force success reports */ + error = 1; + } + + offset += SCTP_SIZE32(param_length); + /* update remaining ASCONF message length to process */ + if (offset >= asconf_limit) { + /* no more data in the mbuf chain */ + break; + } + /* get pointer to next asconf param */ + aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, + sizeof(struct sctp_asconf_paramhdr), + (uint8_t *)&aparam_buf); + if (aph == NULL) { + /* can't get an asconf paramhdr */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf: can't get asconf param hdr!\n"); + } +#endif /* SCTP_DEBUG */ + /* FIX ME - add error here... */ + } + } /* while */ + + ack_cp->ch.chunk_length = htons(ack_cp->ch.chunk_length); + /* save the ASCONF-ACK reply */ + asoc->last_asconf_ack_sent = m_ack; + /* and send (a new one) it out... */ + sctp_send_asconf_ack(stcb, 0); +} + +/* + * does the address match? + * returns 0 if not, 1 if so + */ +static uint32_t +sctp_asconf_addr_match(struct sctp_asconf_addr *aa, struct sockaddr *sa) +{ +#ifdef INET6 + if (sa->sa_family == AF_INET6) { + /* IPv6 sa address */ + /* XXX scopeid */ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + if ((aa->ap.addrp.ph.param_type == SCTP_IPV6_ADDRESS) && + (memcmp(&aa->ap.addrp.addr, &sin6->sin6_addr, + sizeof(struct in6_addr)) == 0)) { + return (1); + } + } else +#endif /* INET6 */ + if (sa->sa_family == AF_INET) { + /* IPv4 sa address */ + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + if ((aa->ap.addrp.ph.param_type == SCTP_IPV4_ADDRESS) && + (memcmp(&aa->ap.addrp.addr, &sin->sin_addr, + sizeof(struct in_addr)) == 0)) { + return (1); + } + } + return (0); +} + +/* + * Cleanup for non-responded/OP ERR'd ASCONF + */ +void +sctp_asconf_cleanup(struct sctp_tcb *stcb, struct sctp_nets *net) +{ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_cleanup: marking peer ASCONF incapable and cleaning up\n"); + } +#endif /* SCTP_DEBUG */ + /* mark peer as ASCONF incapable */ + stcb->asoc.peer_supports_asconf = 0; + stcb->asoc.peer_supports_asconf_setprim = 0; + /* + * clear out any existing asconfs going out + */ + sctp_timer_stop(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep, stcb, net); + stcb->asoc.asconf_seq_out++; + /* remove the old ASCONF on our outbound queue */ + sctp_toss_old_asconf(stcb); +} + +/* + * process an ADD/DELETE IP ack from peer + * ifa: corresponding ifaddr to the address being added/deleted + * type: SCTP_ADD_IP_ADDRESS or SCTP_DEL_IP_ADDRESS + * flag: 1=success, 0=failure + */ +static void +sctp_asconf_addr_mgmt_ack(struct sctp_tcb *stcb, struct ifaddr *addr, + uint16_t type, uint32_t flag) +{ + + /* + * do the necessary asoc list work- + * if we get a failure indication, leave the address on the + * "do not use" asoc list + * if we get a success indication, remove the address from + * the list + */ + /* + * Note: this will only occur for ADD_IP_ADDRESS, since + * DEL_IP_ADDRESS is never actually added to the list... + */ + if (flag) { + /* success case, so remove from the list */ + sctp_del_local_addr_assoc(stcb, addr); + } + /* else, leave it on the list */ +} + +/* + * add an asconf add/delete IP address parameter to the queue + * type = SCTP_ADD_IP_ADDRESS, SCTP_DEL_IP_ADDRESS, SCTP_SET_PRIM_ADDR + * returns 0 if completed, non-zero if not completed + * NOTE: if adding, but delete already scheduled (and not yet + * sent out), simply remove from queue. Same for deleting + * an address already scheduled for add. If a duplicate + * operation is found, ignore the new one. + */ +static uint32_t +sctp_asconf_queue_add(struct sctp_tcb *stcb, struct ifaddr *ifa, uint16_t type) +{ + struct sctp_asconf_addr *aa, *aa_next; +#ifdef SCTP_DEBUG + char buf[128]; /* for address in string format */ +#endif /* SCTP_DEBUG */ + + /* see if peer supports ASCONF */ + if (stcb->asoc.peer_supports_asconf == 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add: peer doesn't support ASCONF\n"); + } +#endif /* SCTP_DEBUG */ + return (-1); + } + + /* make sure the request isn't already in the queue */ + for (aa=TAILQ_FIRST(&stcb->asoc.asconf_queue); aa!=NULL; aa=aa_next) { + aa_next = TAILQ_NEXT(aa, next); + /* address match? */ + if (sctp_asconf_addr_match(aa, ifa->ifa_addr) == 0) + continue; + /* is the request already in queue (sent or not) */ + if (aa->ap.aph.ph.param_type == type) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add: request already exists\n"); + } +#endif /* SCTP_DEBUG */ + return (-1); + } + /* is the negative request already in queue, and not sent */ + if (aa->sent == 0 && + /* add requested, delete already queued */ + ((type == SCTP_ADD_IP_ADDRESS && + aa->ap.aph.ph.param_type == SCTP_DEL_IP_ADDRESS) || + /* delete requested, add already queued */ + (type == SCTP_DEL_IP_ADDRESS && + aa->ap.aph.ph.param_type == SCTP_ADD_IP_ADDRESS))) { + /* delete the existing entry in the queue */ + TAILQ_REMOVE(&stcb->asoc.asconf_queue, aa, next); + /* take the entry off the appropriate list */ + sctp_asconf_addr_mgmt_ack(stcb, aa->ifa, type, 1); + /* free the entry */ + FREE(aa, M_PCB); + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add: removing 'opposite' queued request\n"); + } +#endif /* SCTP_DEBUG */ + return (-1); + } + } /* for each aa */ + + /* adding new request to the queue */ + MALLOC(aa, struct sctp_asconf_addr *, sizeof(*aa), M_PCB, M_NOWAIT); + if (aa == NULL) { + /* didn't get memory */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add: failed to get memory!\n"); + } +#endif /* SCTP_DEBUG */ + return (-1); + } + /* fill in asconf address parameter fields */ + /* top level elements are "networked" during send */ + aa->ap.aph.ph.param_type = type; + aa->ifa = ifa; + /* correlation_id filled in during send routine later... */ + if (ifa->ifa_addr->sa_family == AF_INET6) { + /* IPv6 address */ + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + aa->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS; + aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv6addr_param)); + aa->ap.aph.ph.param_length = + sizeof(struct sctp_asconf_paramhdr) + + sizeof(struct sctp_ipv6addr_param); + memcpy(&aa->ap.addrp.addr, &sin6->sin6_addr, + sizeof(struct in6_addr)); +#ifdef SCTP_DEBUG + strlcpy(buf, ip6_sprintf(&sin6->sin6_addr), sizeof(buf)); +#endif /* SCTP_DEBUG */ + + } else if (ifa->ifa_addr->sa_family == AF_INET) { + /* IPv4 address */ + struct sockaddr_in *sin = (struct sockaddr_in *)ifa->ifa_addr; + aa->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS; + aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv4addr_param)); + aa->ap.aph.ph.param_length = + sizeof(struct sctp_asconf_paramhdr) + + sizeof(struct sctp_ipv4addr_param); + memcpy(&aa->ap.addrp.addr, &sin->sin_addr, + sizeof(struct in_addr)); +#ifdef SCTP_DEBUG + strlcpy(buf, inet_ntoa(sin->sin_addr), sizeof(buf)); +#endif /* SCTP_DEBUG */ + } else { + /* invalid family! */ + return (-1); + } + aa->sent = 0; /* clear sent flag */ + + /* + * if we are deleting an address it should go out last + * otherwise, add it to front of the pending queue + */ + if (type == SCTP_ADD_IP_ADDRESS) { + /* add goes to the front of the queue */ + TAILQ_INSERT_HEAD(&stcb->asoc.asconf_queue, aa, next); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add: appended asconf ADD_IP_ADDRESS: %s\n", buf); + } +#endif /* SCTP_DEBUG */ + } else { + /* delete and set primary goes to the back of the queue */ + TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + if (type == SCTP_DEL_IP_ADDRESS) { + printf("asconf_queue_add: inserted asconf DEL_IP_ADDRESS: %s\n", buf); + } else { + printf("asconf_queue_add: inserted asconf SET_PRIM_ADDR: %s\n", buf); + } + } +#endif /* SCTP_DEBUG */ + } + + return (0); +} + +/* + * add an asconf add/delete IP address parameter to the queue by addr + * type = SCTP_ADD_IP_ADDRESS, SCTP_DEL_IP_ADDRESS, SCTP_SET_PRIM_ADDR + * returns 0 if completed, non-zero if not completed + * NOTE: if adding, but delete already scheduled (and not yet + * sent out), simply remove from queue. Same for deleting + * an address already scheduled for add. If a duplicate + * operation is found, ignore the new one. + */ +static uint32_t +sctp_asconf_queue_add_sa(struct sctp_tcb *stcb, struct sockaddr *sa, + uint16_t type) +{ + struct sctp_asconf_addr *aa, *aa_next; + + /* see if peer supports ASCONF */ + if (stcb->asoc.peer_supports_asconf == 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add_sa: peer doesn't support ASCONF\n"); + } +#endif /* SCTP_DEBUG */ + return (-1); + } + + /* make sure the request isn't already in the queue */ + for (aa = TAILQ_FIRST(&stcb->asoc.asconf_queue); aa != NULL; + aa = aa_next) { + aa_next = TAILQ_NEXT(aa, next); + /* address match? */ + if (sctp_asconf_addr_match(aa, sa) == 0) + continue; + /* is the request already in queue (sent or not) */ + if (aa->ap.aph.ph.param_type == type) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add_sa: request already exists\n"); + } +#endif /* SCTP_DEBUG */ + return (-1); + } + + /* is the negative request already in queue, and not sent */ + if (aa->sent == 1) + continue; + if (type == SCTP_ADD_IP_ADDRESS && + aa->ap.aph.ph.param_type == SCTP_DEL_IP_ADDRESS) { + /* add requested, delete already queued */ + + /* delete the existing entry in the queue */ + TAILQ_REMOVE(&stcb->asoc.asconf_queue, aa, next); + /* free the entry */ + FREE(aa, M_PCB); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add_sa: removing queued delete request\n"); + } +#endif /* SCTP_DEBUG */ + return (-1); + } else if (type == SCTP_DEL_IP_ADDRESS && + aa->ap.aph.ph.param_type == SCTP_ADD_IP_ADDRESS) { + /* delete requested, add already queued */ + + /* delete the existing entry in the queue */ + TAILQ_REMOVE(&stcb->asoc.asconf_queue, aa, next); + /* take the entry off the appropriate list */ + sctp_asconf_addr_mgmt_ack(stcb, aa->ifa, type, 1); + /* free the entry */ + FREE(aa, M_PCB); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add_sa: removing queued add request\n"); + } +#endif /* SCTP_DEBUG */ + return (-1); + } + } /* for each aa */ + + /* adding new request to the queue */ + MALLOC(aa, struct sctp_asconf_addr *, sizeof(*aa), M_PCB, M_NOWAIT); + if (aa == NULL) { + /* didn't get memory */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add_sa: failed to get memory!\n"); + } +#endif /* SCTP_DEBUG */ + return (-1); + } + /* fill in asconf address parameter fields */ + /* top level elements are "networked" during send */ + aa->ap.aph.ph.param_type = type; + aa->ifa = sctp_find_ifa_by_addr(sa); + /* correlation_id filled in during send routine later... */ + if (sa->sa_family == AF_INET6) { + /* IPv6 address */ + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)sa; + aa->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS; + aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv6addr_param)); + aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_ipv6addr_param); + memcpy(&aa->ap.addrp.addr, &sin6->sin6_addr, + sizeof(struct in6_addr)); + } else if (sa->sa_family == AF_INET) { + /* IPv4 address */ + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + aa->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS; + aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv4addr_param)); + aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_ipv4addr_param); + memcpy(&aa->ap.addrp.addr, &sin->sin_addr, + sizeof(struct in_addr)); + } else { + /* invalid family! */ + return (-1); + } + aa->sent = 0; /* clear sent flag */ + + /* + * if we are deleting an address it should go out last + * otherwise, add it to front of the pending queue + */ + if (type == SCTP_ADD_IP_ADDRESS) { + /* add goes to the front of the queue */ + TAILQ_INSERT_HEAD(&stcb->asoc.asconf_queue, aa, next); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_queue_add_sa: appended asconf ADD_IP_ADDRESS\n"); + } +#endif /* SCTP_DEBUG */ + } else { + /* delete and set primary goes to the back of the queue */ + TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + if (type == SCTP_DEL_IP_ADDRESS) { + printf("asconf_queue_add_sa: inserted asconf DEL_IP_ADDRESS\n"); + } else { + printf("asconf_queue_add_sa: inserted asconf SET_PRIM_ADDR\n"); + } + } +#endif /* SCTP_DEBUG */ + } + + return (0); +} + +/* + * find a specific asconf param on our "sent" queue + */ +static struct sctp_asconf_addr * +sctp_asconf_find_param(struct sctp_tcb *stcb, uint32_t correlation_id) +{ + struct sctp_asconf_addr *aa; + + TAILQ_FOREACH(aa, &stcb->asoc.asconf_queue, next) { + if (aa->ap.aph.correlation_id == correlation_id && + aa->sent == 1) { + /* found it */ + return (aa); + } + } + /* didn't find it */ + return (NULL); +} + +/* + * process an SCTP_ERROR_CAUSE_IND for a ASCONF-ACK parameter + * and do notifications based on the error response + */ +static void +sctp_asconf_process_error(struct sctp_tcb *stcb, + struct sctp_asconf_paramhdr *aph) +{ + struct sctp_error_cause *eh; + struct sctp_paramhdr *ph; + uint16_t param_type; + uint16_t error_code; + + eh = (struct sctp_error_cause *)(aph + 1); + ph = (struct sctp_paramhdr *)(eh + 1); + /* validate lengths */ + if (htons(eh->length) + sizeof(struct sctp_error_cause) > + htons(aph->ph.param_length)) { + /* invalid error cause length */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_process_error: cause element too long\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + if (htons(ph->param_length) + sizeof(struct sctp_paramhdr) > + htons(eh->length)) { + /* invalid included TLV length */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("asconf_process_error: included TLV too long\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + + /* which error code ? */ + error_code = ntohs(eh->code); + param_type = ntohs(aph->ph.param_type); + /* FIX: this should go back up the REMOTE_ERROR ULP notify */ + switch (error_code) { + case SCTP_ERROR_RESOURCE_SHORTAGE: + /* we allow ourselves to "try again" for this error */ + break; + default: + /* peer can't handle it... */ + switch (param_type) { + case SCTP_ADD_IP_ADDRESS: + case SCTP_DEL_IP_ADDRESS: + stcb->asoc.peer_supports_asconf = 0; + break; + case SCTP_SET_PRIM_ADDR: + stcb->asoc.peer_supports_asconf_setprim = 0; + break; + default: + break; + } + } +} + +/* + * process an asconf queue param + * aparam: parameter to process, will be removed from the queue + * flag: 1=success, 0=failure + */ +static void +sctp_asconf_process_param_ack(struct sctp_tcb *stcb, + struct sctp_asconf_addr *aparam, uint32_t flag) +{ + uint16_t param_type; + + /* process this param */ + param_type = aparam->ap.aph.ph.param_type; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_param_ack: handling asconf parameter type=%xh\n", param_type); + } +#endif /* SCTP_DEBUG */ + switch (param_type) { + case SCTP_ADD_IP_ADDRESS: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_param_ack: added IP address\n"); + } +#endif /* SCTP_DEBUG */ + sctp_asconf_addr_mgmt_ack(stcb, aparam->ifa, param_type, flag); + break; + case SCTP_DEL_IP_ADDRESS: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_param_ack: deleted IP address\n"); + } +#endif /* SCTP_DEBUG */ + /* nothing really to do... lists already updated */ + break; + case SCTP_SET_PRIM_ADDR: + /* nothing to do... peer may start using this addr */ + if (flag == 0) + stcb->asoc.peer_supports_asconf_setprim = 0; + break; + default: + /* should NEVER happen */ + break; + } /* switch */ + + /* remove the param and free it */ + TAILQ_REMOVE(&stcb->asoc.asconf_queue, aparam, next); + FREE(aparam, M_PCB); +} + +/* + * cleanup from a bad asconf ack parameter + */ +static void +sctp_asconf_ack_clear(struct sctp_tcb *stcb) +{ + /* assume peer doesn't really know how to do asconfs */ + stcb->asoc.peer_supports_asconf = 0; + stcb->asoc.peer_supports_asconf_setprim = 0; + /* XXX we could free the pending queue here */ +} + +void +sctp_handle_asconf_ack(struct mbuf *m, int offset, + struct sctp_asconf_ack_chunk *cp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_association *asoc; + uint32_t serial_num; + uint16_t ack_length; + struct sctp_asconf_paramhdr *aph; + struct sctp_asconf_addr *aa, *aa_next; + uint32_t last_error_id = 0; /* last error correlation id */ + uint32_t id; + struct sctp_asconf_addr *ap; + /* asconf param buffer */ + static u_int8_t aparam_buf[DEFAULT_PARAM_BUFFER]; + + /* verify minimum length */ + if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_asconf_ack_chunk)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf_ack: chunk too small = %xh\n", + ntohs(cp->ch.chunk_length)); + } +#endif /* SCTP_DEBUG */ + return; + } + + asoc = &stcb->asoc; + serial_num = ntohl(cp->serial_number); + + /* + * NOTE: we may want to handle this differently- currently, we + * will abort when we get an ack for the expected serial number + 1 + * (eg. we didn't send it), process an ack normally if it is the + * expected serial number, and re-send the previous ack for *ALL* + * other serial numbers + */ + + /* + * if the serial number is the next expected, but I didn't send it, + * abort the asoc, since someone probably just hijacked us... + */ + if (serial_num == (asoc->asconf_seq_out + 1)) { + sctp_abort_an_association(stcb->sctp_ep, stcb, + SCTP_ERROR_ILLEGAL_ASCONF_ACK, NULL); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf_ack: got unexpected next serial number! Aborting asoc!\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + + if (serial_num != asoc->asconf_seq_out) { + /* got a duplicate/unexpected ASCONF-ACK */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("handle_asconf_ack: got duplicate/unexpected serial number = %xh (expected = %xh)\n", serial_num, asoc->asconf_seq_out); + } +#endif /* SCTP_DEBUG */ + return; + } + /* stop our timer */ + sctp_timer_stop(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep, stcb, net); + + /* process the ASCONF-ACK contents */ + ack_length = ntohs(cp->ch.chunk_length) - + sizeof(struct sctp_asconf_ack_chunk); + offset += sizeof(struct sctp_asconf_ack_chunk); + /* process through all parameters */ + while (ack_length >= sizeof(struct sctp_asconf_paramhdr)) { + unsigned int param_length, param_type; + + /* get pointer to next asconf parameter */ + aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, + sizeof(struct sctp_asconf_paramhdr), aparam_buf); + if (aph == NULL) { + /* can't get an asconf paramhdr */ + sctp_asconf_ack_clear(stcb); + return; + } + param_type = ntohs(aph->ph.param_type); + param_length = ntohs(aph->ph.param_length); + if (param_length > ack_length) { + sctp_asconf_ack_clear(stcb); + return; + } + if (param_length < sizeof(struct sctp_paramhdr)) { + sctp_asconf_ack_clear(stcb); + return; + } + + /* get the complete parameter... */ + if (param_length > sizeof(aparam_buf)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("param length (%u) larger than buffer size!\n", param_length); + } +#endif /* SCTP_DEBUG */ + sctp_asconf_ack_clear(stcb); + return; + } + aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, param_length, aparam_buf); + if (aph == NULL) { + sctp_asconf_ack_clear(stcb); + return; + } + /* correlation_id is transparent to peer, no ntohl needed */ + id = aph->correlation_id; + + switch (param_type) { + case SCTP_ERROR_CAUSE_IND: + last_error_id = id; + /* find the corresponding asconf param in our queue */ + ap = sctp_asconf_find_param(stcb, id); + if (ap == NULL) { + /* hmm... can't find this in our queue! */ + break; + } + /* process the parameter, failed flag */ + sctp_asconf_process_param_ack(stcb, ap, 0); + /* process the error response */ + sctp_asconf_process_error(stcb, aph); + break; + case SCTP_SUCCESS_REPORT: + /* find the corresponding asconf param in our queue */ + ap = sctp_asconf_find_param(stcb, id); + if (ap == NULL) { + /* hmm... can't find this in our queue! */ + break; + } + /* process the parameter, success flag */ + sctp_asconf_process_param_ack(stcb, ap, 1); + break; + default: + break; + } /* switch */ + + /* update remaining ASCONF-ACK message length to process */ + ack_length -= SCTP_SIZE32(param_length); + if (ack_length <= 0) { + /* no more data in the mbuf chain */ + break; + } + offset += SCTP_SIZE32(param_length); + } /* while */ + + /* + * if there are any "sent" params still on the queue, these are + * implicitly "success", or "failed" (if we got an error back) + * ... so process these appropriately + * + * we assume that the correlation_id's are monotonically increasing + * beginning from 1 and that we don't have *that* many outstanding + * at any given time + */ + if (last_error_id == 0) + last_error_id--; /* set to "max" value */ + for (aa = TAILQ_FIRST(&stcb->asoc.asconf_queue); aa != NULL; + aa = aa_next) { + aa_next = TAILQ_NEXT(aa, next); + if (aa->sent == 1) { + /* + * implicitly successful or failed + * if correlation_id < last_error_id, then success + * else, failure + */ + if (aa->ap.aph.correlation_id < last_error_id) + sctp_asconf_process_param_ack(stcb, aa, + SCTP_SUCCESS_REPORT); + else + sctp_asconf_process_param_ack(stcb, aa, + SCTP_ERROR_CAUSE_IND); + } else { + /* + * since we always process in order (FIFO queue) + * if we reach one that hasn't been sent, the + * rest should not have been sent either. + * so, we're done... + */ + break; + } + } + + /* update the next sequence number to use */ + asoc->asconf_seq_out++; + /* remove the old ASCONF on our outbound queue */ + sctp_toss_old_asconf(stcb); + /* clear the sent flag to allow new ASCONFs */ + asoc->asconf_sent = 0; + if (!TAILQ_EMPTY(&stcb->asoc.asconf_queue)) { + /* we have more params, so restart our timer */ + sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep, + stcb, net); + } +} + +/* is this an interface that we care about at all? */ +static uint32_t +sctp_is_desired_interface_type(struct ifaddr *ifa) +{ + int result; + + /* check the interface type to see if it's one we care about */ + switch (ifa->ifa_ifp->if_type) { + case IFT_ETHER: + case IFT_ISO88023: + case IFT_ISO88025: + case IFT_STARLAN: + case IFT_P10: + case IFT_P80: + case IFT_HY: + case IFT_FDDI: + case IFT_PPP: + case IFT_XETHER: + case IFT_SLIP: + case IFT_GIF: + result = 1; + break; + default: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("ignoring interface type = %u\n", + ifa->ifa_ifp->if_type); + } +#endif /* SCTP_DEBUG */ + result = 0; + } /* end switch */ + + return (result); +} + +static uint32_t +sctp_is_scopeid_in_nets(struct sctp_tcb *stcb, struct sockaddr *sa) +{ + struct sockaddr_in6 *sin6, *net6; + struct sctp_nets *net; + + if (sa->sa_family != AF_INET6) { + /* wrong family */ + return (0); + } + + sin6 = (struct sockaddr_in6 *)sa; + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) == 0) { + /* not link local address */ + return (0); + } + + /* hunt through our destination nets list for this scope_id */ + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + if (((struct sockaddr *)(&net->ro._l_addr))->sa_family != + AF_INET6) + continue; + net6 = (struct sockaddr_in6 *)&net->ro._l_addr; + if (IN6_IS_ADDR_LINKLOCAL(&net6->sin6_addr) == 0) + continue; + if (sctp_is_same_scope(sin6, net6)) { + /* found one */ + return (1); + } + } + /* didn't find one */ + return (0); +} + +/* + * address management functions + */ +static void +sctp_addr_mgmt_assoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct ifaddr *ifa, uint16_t type) +{ + int status; +#ifdef SCTP_DEBUG + char buf[128]; /* for address in string format */ +#endif /* SCTP_DEBUG */ + + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0 && + (inp->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) == 0) { + /* subset bound, no ASCONF allowed case, so ignore */ + return; + } + + /* + * note: we know this is not the subset bound, no ASCONF case + * eg. this is boundall or subset bound w/ASCONF allowed + */ + + /* first, make sure it's a good address family */ + if (ifa->ifa_addr->sa_family != AF_INET6 && + ifa->ifa_addr->sa_family != AF_INET) { + return; + } + + /* make sure we're "allowed" to add this type of addr */ + if (ifa->ifa_addr->sa_family == AF_INET6) { + struct in6_ifaddr *ifa6; + + /* invalid if we're not a v6 endpoint */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) + return; + /* is the v6 addr really valid ? */ + ifa6 = (struct in6_ifaddr *)ifa; + if (IFA6_IS_DEPRECATED(ifa6) || + (ifa6->ia6_flags & + (IN6_IFF_DETACHED | IN6_IFF_ANYCAST | IN6_IFF_NOTREADY))) { + /* can't use an invalid address */ + return; + } + } + + /* put this address on the "pending/do not use yet" list */ + /* + * Note: we do this primarily for the subset bind case + * We don't have scoping flags at the EP level, so we must + * add link local/site local addresses to the EP, then need + * to "negate" them here. Recall that this routine is only + * called for the subset bound w/ASCONF allowed case. + */ + + /* + * do a scope_id check against any link local addresses + * in the destination nets list to see if we should put + * this local address on the pending list or not + * eg. don't put on the list if we have a link local + * destination with the same scope_id + */ + if (type == SCTP_ADD_IP_ADDRESS) { + if (sctp_is_scopeid_in_nets(stcb, ifa->ifa_addr) == 0) { + sctp_add_local_addr_assoc(stcb, ifa); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("addr_mgmt_assoc: added to pending list "); + sctp_print_address(ifa->ifa_addr); + } +#endif /* SCTP_DEBUG */ + } + } + /* + * check address scope + * if address is out of scope, don't queue anything... + * note: this would leave the address on both inp and asoc lists + */ + if (ifa->ifa_addr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; +#ifdef SCTP_DEBUG + strlcpy(buf, ip6_sprintf(&sin6->sin6_addr), sizeof(buf)); +#endif /* SCTP_DEBUG */ + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + /* we skip unspecifed addresses */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("addr_mgmt_assoc: unspecified IPv6 addr\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { + if (stcb->asoc.local_scope == 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("addr_mgmt_assoc: skipping link local IPv6 addr: %s\n", buf); + } +#endif /* SCTP_DEBUG */ + return; + } + /* is it the right link local scope? */ + if (sctp_is_scopeid_in_nets(stcb, ifa->ifa_addr) == 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("addr_mgmt_assoc: skipping link local IPv6 addr: %s, wrong scope_id\n", buf); + } +#endif /* SCTP_DEBUG */ + return; + } + } + if (stcb->asoc.site_scope == 0 && + IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("addr_mgmt_assoc: skipping site local IPv6 addr: %s\n", buf); + } +#endif /* SCTP_DEBUG */ + return; + } + } else if (ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *sin; + struct in6pcb *inp6; + + inp6 = (struct in6pcb *)&inp->ip_inp.inp; + /* invalid if we are a v6 only endpoint */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) && +#if defined(__OpenBSD__) + (0) /* we always do dual bind */ +#elif defined (__NetBSD__) + (inp6->in6p_flags & IN6P_IPV6_V6ONLY) +#else + (inp6->inp_flags & IN6P_IPV6_V6ONLY) +#endif + ) + return; + + sin = (struct sockaddr_in *)ifa->ifa_addr; +#ifdef SCTP_DEBUG + strlcpy(buf, inet_ntoa(sin->sin_addr), sizeof(buf)); +#endif /* SCTP_DEBUG */ + if (sin->sin_addr.s_addr == 0) { + /* we skip unspecifed addresses */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("addr_mgmt_assoc: unspecified IPv4 addr\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + if (stcb->asoc.ipv4_local_scope == 0 && + IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("addr_mgmt_assoc: skipping private IPv4 addr: %s\n", buf); + } +#endif /* SCTP_DEBUG */ + return; + } + } else { + /* else, not AF_INET or AF_INET6, so skip */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("addr_mgmt_assoc: not AF_INET or AF_INET6\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + + /* queue an asconf for this address add/delete */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) { + /* does the peer do asconf? */ + if (stcb->asoc.peer_supports_asconf) { + /* queue an asconf for this addr */ + status = sctp_asconf_queue_add(stcb, ifa, type); + /* + * if queued ok, and in correct state, set the + * ASCONF timer + * if in non-open state, we will set this timer + * when the state does go open and do all the + * asconf's + */ + if (status == 0 && + SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) { + sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, + stcb, stcb->asoc.primary_destination); + } + } + } else { + /* this is the boundall, no ASCONF case */ +#if 0 /* assume kernel will delete this very shortly; add done above */ + if (type == SCTP_DEL_IP_ADDRESS) { + /* if deleting, add this addr to the do not use list */ + sctp_add_local_addr_assoc(stcb, ifa); + } +#endif + } +} + +static void +sctp_addr_mgmt_ep(struct sctp_inpcb *inp, struct ifaddr *ifa, uint16_t type) +{ + struct sctp_tcb *stcb; + int s; + + SCTP_INP_WLOCK(inp); + /* make sure we're "allowed" to add this type of addr */ + if (ifa->ifa_addr->sa_family == AF_INET6) { + struct in6_ifaddr *ifa6; + + /* invalid if we're not a v6 endpoint */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) { + SCTP_INP_WUNLOCK(inp); + return; + } + /* is the v6 addr really valid ? */ + ifa6 = (struct in6_ifaddr *)ifa; + if (IFA6_IS_DEPRECATED(ifa6) || + (ifa6->ia6_flags & + (IN6_IFF_DETACHED | IN6_IFF_ANYCAST | IN6_IFF_NOTREADY))) { + /* can't use an invalid address */ + SCTP_INP_WUNLOCK(inp); + return; + } + } else if (ifa->ifa_addr->sa_family == AF_INET) { + /* invalid if we are a v6 only endpoint */ + struct in6pcb *inp6; + inp6 = (struct in6pcb *)&inp->ip_inp.inp; + + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) && +#if defined(__OpenBSD__) + (0) /* we always do dual bind */ +#elif defined (__NetBSD__) + (inp6->in6p_flags & IN6P_IPV6_V6ONLY) +#else + (inp6->inp_flags & IN6P_IPV6_V6ONLY) +#endif + ) { + SCTP_INP_WUNLOCK(inp); + return; + } + } else { + /* invalid address family */ + SCTP_INP_WUNLOCK(inp); + return; + } + /* is this endpoint subset bound ? */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) { + /* subset bound endpoint */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) == 0) { + /* + * subset bound, but ASCONFs not allowed... + * if adding, nothing to do, since not allowed + * if deleting, remove address from endpoint + * peer will have to "timeout" this addr + */ + if (type == SCTP_DEL_IP_ADDRESS) { + sctp_del_local_addr_ep(inp, ifa); + } + /* no asconfs to queue for this inp... */ + SCTP_INP_WUNLOCK(inp); + return; + } else { + /* + * subset bound, ASCONFs allowed... + * if adding, add address to endpoint list + * if deleting, remove address from endpoint + */ + if (type == SCTP_ADD_IP_ADDRESS) { + sctp_add_local_addr_ep(inp, ifa); + } else { + sctp_del_local_addr_ep(inp, ifa); + } + /* drop through and notify all asocs */ + } + } + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + /* process for all associations for this endpoint */ + LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { + SCTP_TCB_LOCK(stcb); + sctp_addr_mgmt_assoc(inp, stcb, ifa, type); + SCTP_TCB_UNLOCK(stcb); + } /* for each stcb */ + splx(s); + SCTP_INP_WUNLOCK(inp); +} + +/* + * restrict the use of this address + */ +static void +sctp_addr_mgmt_restrict_ep(struct sctp_inpcb *inp, struct ifaddr *ifa) +{ + struct sctp_tcb *stcb; + int s; + + /* is this endpoint bound to all? */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) { + /* + * Nothing to do for subset bound case. + * Allow sctp_bindx() to manage the address lists + */ + return; + } + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + SCTP_INP_RLOCK(inp); + /* process for all associations for this endpoint */ + LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { + /* put this address on the "pending/do not use yet" list */ + SCTP_TCB_LOCK(stcb); + sctp_add_local_addr_assoc(stcb, ifa); + SCTP_TCB_UNLOCK(stcb); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("restrict_ep: added addr to unusable list\n"); + } +#endif /* SCTP_DEBUG */ + } /* for each stcb */ + splx(s); + SCTP_INP_RUNLOCK(inp); +} + +/* + * this is only called for kernel initiated address changes + * eg. it will check the PCB_FLAGS_AUTO_ASCONF flag + */ +static void +sctp_addr_mgmt(struct ifaddr *ifa, uint16_t type) { + struct sockaddr *sa; + struct sctp_inpcb *inp; + + /* make sure we care about this interface... */ + if (!sctp_is_desired_interface_type(ifa)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("sctp_addr_mgmt: ignoring this interface\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + + sa = ifa->ifa_addr; + if (sa->sa_family != AF_INET && sa->sa_family != AF_INET6) + return; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + if (type == SCTP_ADD_IP_ADDRESS) + printf("sctp_addr_mgmt: kernel adds "); + else + printf("sctp_addr_mgmt: kernel deletes "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ + + /* go through all our PCB's */ + LIST_FOREACH(inp, &sctppcbinfo.listhead, sctp_list) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_AUTO_ASCONF) { + sctp_addr_mgmt_ep(inp, ifa, type); + } else { + /* this address is going away anyways... */ + if (type == SCTP_DEL_IP_ADDRESS) + return; + /* (temporarily) restrict this address */ + sctp_addr_mgmt_restrict_ep(inp, ifa); + } + /* else, not allowing automatic asconf's, so ignore */ + } /* for each inp */ +} + +/* + * add/delete IP address requests from kernel (via routing change) + * assumed that the address is non-broadcast, non-multicast + * all addresses are passed from any type of interface-- need to filter + * duplicate addresses may get requested + */ + +void +sctp_add_ip_address(struct ifaddr *ifa) +{ + sctp_addr_mgmt(ifa, SCTP_ADD_IP_ADDRESS); +} + +void +sctp_delete_ip_address(struct ifaddr *ifa) +{ + struct sctp_inpcb *inp; + + /* process the delete */ + sctp_addr_mgmt(ifa, SCTP_DEL_IP_ADDRESS); + + /* + * need to remove this ifaddr from any cached routes + * and also any from any assoc "restricted/pending" lists + */ + /* make sure we care about this interface... */ + if (!sctp_is_desired_interface_type(ifa)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("sctp_delete_ip_address: ignoring this interface\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + + /* go through all our PCB's */ + SCTP_INP_INFO_RLOCK(); + LIST_FOREACH(inp, &sctppcbinfo.listhead, sctp_list) { + struct sctp_tcb *stcb; + struct sctp_laddr *laddr, *laddr_next; + + /* process for all associations for this endpoint */ + SCTP_INP_RLOCK(inp); + LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { + struct sctp_nets *net; + + /* process through the nets list */ + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + struct rtentry *rt; + /* delete this address if cached */ + rt = net->ro.ro_rt; + if (rt != NULL && rt->rt_ifa == ifa) { +/* RTFREE(rt);*/ + net->ro.ro_rt = NULL; + } + } /* for each net */ + /* process through the asoc "pending" list */ + laddr = LIST_FIRST(&stcb->asoc.sctp_local_addr_list); + while (laddr != NULL) { + laddr_next = LIST_NEXT(laddr, sctp_nxt_addr); + /* remove if in use */ + if (laddr->ifa == ifa) { + sctp_remove_laddr(laddr); + } + laddr = laddr_next; + } /* while */ + } /* for each stcb */ + /* process through the inp bound addr list */ + laddr = LIST_FIRST(&inp->sctp_addr_list); + while (laddr != NULL) { + laddr_next = LIST_NEXT(laddr, sctp_nxt_addr); + /* remove if in use */ + if (laddr->ifa == ifa) { + sctp_remove_laddr(laddr); + } + laddr = laddr_next; + } /* while */ + SCTP_INP_RUNLOCK(inp); + } /* for each inp */ + SCTP_INP_INFO_RUNLOCK(); +} + +/* + * sa is the sockaddr to ask the peer to set primary to + * returns: 0 = completed, -1 = error + */ +int32_t +sctp_set_primary_ip_address_sa(struct sctp_tcb *stcb, struct sockaddr *sa) +{ + /* NOTE: we currently don't check the validity of the address! */ + + /* queue an ASCONF:SET_PRIM_ADDR to be sent */ + if (!sctp_asconf_queue_add_sa(stcb, sa, SCTP_SET_PRIM_ADDR)) { + /* set primary queuing succeeded */ + if (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) { + sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, + stcb->sctp_ep, stcb, + stcb->asoc.primary_destination); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("set_primary_ip_address_sa: queued on tcb=%p, ", + stcb); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ + } else { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("set_primary_ip_address_sa: failed to add to queue on tcb=%p, ", + stcb); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ + return (-1); + } + return (0); +} + +void +sctp_set_primary_ip_address(struct ifaddr *ifa) +{ + struct sctp_inpcb *inp; + + /* make sure we care about this interface... */ + if (!sctp_is_desired_interface_type(ifa)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("set_primary_ip_address: ignoring this interface\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + + /* go through all our PCB's */ + LIST_FOREACH(inp, &sctppcbinfo.listhead, sctp_list) { + struct sctp_tcb *stcb; + + /* process for all associations for this endpoint */ + LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { + /* queue an ASCONF:SET_PRIM_ADDR to be sent */ + if (!sctp_asconf_queue_add(stcb, ifa, + SCTP_SET_PRIM_ADDR)) { + /* set primary queuing succeeded */ + if (SCTP_GET_STATE(&stcb->asoc) == + SCTP_STATE_OPEN) { + sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, + stcb->sctp_ep, stcb, + stcb->asoc.primary_destination); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("set_primary_ip_address: queued on stcb=%p, ", + stcb); + sctp_print_address(ifa->ifa_addr); + } +#endif /* SCTP_DEBUG */ + } else { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("set_primary_ip_address: failed to add to queue, "); + sctp_print_address(ifa->ifa_addr); + } +#endif /* SCTP_DEBUG */ + } + } /* for each stcb */ + } /* for each inp */ +} + +static struct sockaddr * +sctp_find_valid_localaddr(struct sctp_tcb *stcb) +{ + struct ifnet *ifn; + struct ifaddr *ifa; + + TAILQ_FOREACH(ifn, &ifnet, if_list) { + if (stcb->asoc.loopback_scope == 0 && ifn->if_type == IFT_LOOP) { + /* Skip if loopback_scope not set */ + continue; + } + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (ifa->ifa_addr->sa_family == AF_INET && + stcb->asoc.ipv4_addr_legal) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)ifa->ifa_addr; + if (sin->sin_addr.s_addr == 0) { + /* skip unspecifed addresses */ + continue; + } + if (stcb->asoc.ipv4_local_scope == 0 && + IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) + continue; + + if (sctp_is_addr_restricted(stcb, + ifa->ifa_addr)) + continue; + /* found a valid local v4 address to use */ + return (ifa->ifa_addr); + } else if (ifa->ifa_addr->sa_family == AF_INET6 && + stcb->asoc.ipv6_addr_legal) { + struct sockaddr_in6 *sin6; + struct in6_ifaddr *ifa6; + + ifa6 = (struct in6_ifaddr *)ifa; + if (IFA6_IS_DEPRECATED(ifa6) || + (ifa6->ia6_flags & (IN6_IFF_DETACHED | + IN6_IFF_ANYCAST | IN6_IFF_NOTREADY))) + continue; + + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + /* we skip unspecifed addresses */ + continue; + } + if (stcb->asoc.local_scope == 0 && + IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + continue; + if (stcb->asoc.site_scope == 0 && + IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) + continue; + + /* found a valid local v6 address to use */ + return (ifa->ifa_addr); + } + } + } + /* no valid addresses found */ + return (NULL); +} + +static struct sockaddr * +sctp_find_valid_localaddr_ep(struct sctp_tcb *stcb) +{ + struct sctp_laddr *laddr; + + LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("find_valid_localaddr_ep: laddr error\n"); + } +#endif /* SCTP_DEBUG */ + continue; + } + if (laddr->ifa->ifa_addr == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("find_valid_localaddr_ep: laddr->ifa error\n"); + } +#endif /* SCTP_DEBUG */ + continue; + } + /* is the address restricted ? */ + if (sctp_is_addr_restricted(stcb, laddr->ifa->ifa_addr)) + continue; + + /* found a valid local address to use */ + return (laddr->ifa->ifa_addr); + } + /* no valid addresses found */ + return (NULL); +} + +/* + * builds an ASCONF chunk from queued ASCONF params + * returns NULL on error (no mbuf, no ASCONF params queued, etc) + */ +struct mbuf * +sctp_compose_asconf(struct sctp_tcb *stcb) +{ + struct mbuf *m_asconf, *m_asconf_chk; + struct sctp_asconf_addr *aa; + struct sctp_asconf_chunk *acp; + struct sctp_asconf_paramhdr *aph; + struct sctp_asconf_addr_param *aap; + uint32_t p_length; + uint32_t correlation_id = 1; /* 0 is reserved... */ + caddr_t ptr, lookup_ptr; + uint8_t lookup_used = 0; + + /* are there any asconf params to send? */ + if (TAILQ_EMPTY(&stcb->asoc.asconf_queue)) { + return (NULL); + } + + /* + * get a chunk header mbuf and a cluster for the asconf params + * since it's simpler to fill in the asconf chunk header lookup + * address on the fly + */ + m_asconf_chk = NULL; + MGETHDR(m_asconf_chk, M_DONTWAIT, MT_DATA); + if (m_asconf_chk == NULL) { + /* no mbuf's */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) + printf("compose_asconf: couldn't get chunk mbuf!\n"); +#endif /* SCTP_DEBUG */ + return (NULL); + } + m_asconf = NULL; + MGETHDR(m_asconf, M_DONTWAIT, MT_HEADER); + if (m_asconf == NULL) { + /* no mbuf's */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) + printf("compose_asconf: couldn't get mbuf!\n"); +#endif /* SCTP_DEBUG */ + sctp_m_freem(m_asconf_chk); + return (NULL); + } + MCLGET(m_asconf, M_DONTWAIT); + if ((m_asconf->m_flags & M_EXT) != M_EXT) { + /* failed to get cluster buffer */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) + printf("compose_asconf: couldn't get cluster!\n"); +#endif /* SCTP_DEBUG */ + sctp_m_freem(m_asconf_chk); + sctp_m_freem(m_asconf); + return (NULL); + } + + m_asconf_chk->m_len = sizeof(struct sctp_asconf_chunk); + m_asconf->m_len = 0; + acp = mtod(m_asconf_chk, struct sctp_asconf_chunk *); + bzero(acp, sizeof(struct sctp_asconf_chunk)); + /* save pointers to lookup address and asconf params */ + lookup_ptr = (caddr_t)(acp + 1); /* after the header */ + ptr = mtod(m_asconf, caddr_t); /* beginning of cluster */ + + /* fill in chunk header info */ + acp->ch.chunk_type = SCTP_ASCONF; + acp->ch.chunk_flags = 0; + acp->serial_number = htonl(stcb->asoc.asconf_seq_out); + + /* add parameters... up to smallest MTU allowed */ + TAILQ_FOREACH(aa, &stcb->asoc.asconf_queue, next) { + /* get the parameter length */ + p_length = SCTP_SIZE32(aa->ap.aph.ph.param_length); + /* will it fit in current chunk? */ + if (m_asconf->m_len + p_length > stcb->asoc.smallest_mtu) { + /* won't fit, so we're done with this chunk */ + break; + } + /* assign (and store) a correlation id */ + aa->ap.aph.correlation_id = correlation_id++; + + /* + * fill in address if we're doing a delete + * this is a simple way for us to fill in the correlation + * address, which should only be used by the peer if we're + * deleting our source address and adding a new address + * (e.g. renumbering case) + */ + if (lookup_used == 0 && + aa->ap.aph.ph.param_type == SCTP_DEL_IP_ADDRESS) { + struct sctp_ipv6addr_param *lookup; + uint16_t p_size, addr_size; + + lookup = (struct sctp_ipv6addr_param *)lookup_ptr; + lookup->ph.param_type = + htons(aa->ap.addrp.ph.param_type); + if (aa->ap.addrp.ph.param_type == SCTP_IPV6_ADDRESS) { + /* copy IPv6 address */ + p_size = sizeof(struct sctp_ipv6addr_param); + addr_size = sizeof(struct in6_addr); + } else { + /* copy IPv4 address */ + p_size = sizeof(struct sctp_ipv4addr_param); + addr_size = sizeof(struct in_addr); + } + lookup->ph.param_length = htons(SCTP_SIZE32(p_size)); + memcpy(lookup->addr, &aa->ap.addrp.addr, addr_size); + m_asconf_chk->m_len += SCTP_SIZE32(p_size); + lookup_used = 1; + } + + /* copy into current space */ + memcpy(ptr, &aa->ap, p_length); + + /* network elements and update lengths */ + aph = (struct sctp_asconf_paramhdr *) ptr; + aap = (struct sctp_asconf_addr_param *) ptr; + /* correlation_id is transparent to peer, no htonl needed */ + aph->ph.param_type = htons(aph->ph.param_type); + aph->ph.param_length = htons(aph->ph.param_length); + aap->addrp.ph.param_type = htons(aap->addrp.ph.param_type); + aap->addrp.ph.param_length = htons(aap->addrp.ph.param_length); + + m_asconf->m_len += SCTP_SIZE32(p_length); + ptr += SCTP_SIZE32(p_length); + + /* + * these params are removed off the pending list upon + * getting an ASCONF-ACK back from the peer, just set flag + */ + aa->sent = 1; + } + /* check to see if the lookup addr has been populated yet */ + if (lookup_used == 0) { + /* NOTE: if the address param is optional, can skip this... */ + /* add any valid (existing) address... */ + struct sctp_ipv6addr_param *lookup; + uint16_t p_size, addr_size; + struct sockaddr *found_addr; + caddr_t addr_ptr; + + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) + found_addr = sctp_find_valid_localaddr(stcb); + else + found_addr = sctp_find_valid_localaddr_ep(stcb); + + lookup = (struct sctp_ipv6addr_param *)lookup_ptr; + if (found_addr != NULL) { + if (found_addr->sa_family == AF_INET6) { + /* copy IPv6 address */ + lookup->ph.param_type = + htons(SCTP_IPV6_ADDRESS); + p_size = sizeof(struct sctp_ipv6addr_param); + addr_size = sizeof(struct in6_addr); + addr_ptr = (caddr_t)&((struct sockaddr_in6 *) + found_addr)->sin6_addr; + } else { + /* copy IPv4 address */ + lookup->ph.param_type = + htons(SCTP_IPV4_ADDRESS); + p_size = sizeof(struct sctp_ipv4addr_param); + addr_size = sizeof(struct in_addr); + addr_ptr = (caddr_t)&((struct sockaddr_in *) + found_addr)->sin_addr; + } + lookup->ph.param_length = htons(SCTP_SIZE32(p_size)); + memcpy(lookup->addr, addr_ptr, addr_size); + m_asconf_chk->m_len += SCTP_SIZE32(p_size); + lookup_used = 1; + } else { + /* uh oh... don't have any address?? */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) + printf("compose_asconf: no lookup addr!\n"); +#endif /* SCTP_DEBUG */ + /* for now, we send a IPv4 address of 0.0.0.0 */ + lookup->ph.param_type = htons(SCTP_IPV4_ADDRESS); + lookup->ph.param_length = htons(SCTP_SIZE32(sizeof(struct sctp_ipv4addr_param))); + bzero(lookup->addr, sizeof(struct in_addr)); + m_asconf_chk->m_len += SCTP_SIZE32(sizeof(struct sctp_ipv4addr_param)); + lookup_used = 1; + } + } + + /* chain it all together */ + m_asconf_chk->m_next = m_asconf; + m_asconf_chk->m_pkthdr.len = m_asconf_chk->m_len + m_asconf->m_len; + acp->ch.chunk_length = ntohs(m_asconf_chk->m_pkthdr.len); + + /* update "sent" flag */ + stcb->asoc.asconf_sent++; + + return (m_asconf_chk); +} + +/* + * section to handle address changes before an association is up + * eg. changes during INIT/INIT-ACK/COOKIE-ECHO handshake + */ + +/* + * processes the (local) addresses in the INIT-ACK chunk + */ +static void +sctp_process_initack_addresses(struct sctp_tcb *stcb, struct mbuf *m, + unsigned int offset, unsigned int length) +{ + struct sctp_paramhdr tmp_param, *ph; + uint16_t plen, ptype; + struct sctp_ipv6addr_param addr_store; + struct sockaddr_in6 sin6; + struct sockaddr_in sin; + struct sockaddr *sa; + struct ifaddr *ifa; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("processing init-ack addresses\n"); + } +#endif /* SCTP_DEBUG */ + + /* convert to upper bound */ + length += offset; + + if ((offset + sizeof(struct sctp_paramhdr)) > length) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_initack_addrs: invalid offset?\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + + /* init the addresses */ + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(sin6); + sin6.sin6_port = stcb->rport; + + bzero(&sin, sizeof(sin)); + sin.sin_len = sizeof(sin); + sin.sin_family = AF_INET; + sin.sin_port = stcb->rport; + + /* go through the addresses in the init-ack */ + ph = (struct sctp_paramhdr *)sctp_m_getptr(m, offset, + sizeof(struct sctp_paramhdr), (uint8_t *)&tmp_param); + while (ph != NULL) { + ptype = ntohs(ph->param_type); + plen = ntohs(ph->param_length); + if (ptype == SCTP_IPV6_ADDRESS) { + struct sctp_ipv6addr_param *a6p; + /* get the entire IPv6 address param */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("process_initack_addrs: checking IPv6 param\n"); + } +#endif /* SCTP_DEBUG */ + a6p = (struct sctp_ipv6addr_param *) + sctp_m_getptr(m, offset, + sizeof(struct sctp_ipv6addr_param), + (uint8_t *)&addr_store); + if (plen != sizeof(struct sctp_ipv6addr_param) || + a6p == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_initack_addrs: invalid IPv6 param length\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + memcpy(&sin6.sin6_addr, a6p->addr, + sizeof(struct in6_addr)); + sa = (struct sockaddr *)&sin6; + } else if (ptype == SCTP_IPV4_ADDRESS) { + struct sctp_ipv4addr_param *a4p; + /* get the entire IPv4 address param */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("process_initack_addrs: checking IPv4 param\n"); + } +#endif /* SCTP_DEBUG */ + a4p = (struct sctp_ipv4addr_param *)sctp_m_getptr(m, offset, sizeof(struct sctp_ipv4addr_param), (uint8_t *)&addr_store); + if (plen != sizeof(struct sctp_ipv4addr_param) || + a4p == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("process_initack_addrs: invalid IPv4 param length\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + sin.sin_addr.s_addr = a4p->addr; + sa = (struct sockaddr *)&sin; + } else { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("process_initack_addrs: skipping param type=%xh\n", ptype); + } +#endif /* SCTP_DEBUG */ + goto next_addr; + } + + /* see if this address really (still) exists */ + ifa = sctp_find_ifa_by_addr(sa); + if (ifa == NULL) { + /* address doesn't exist anymore */ + int status; + /* are ASCONFs allowed ? */ + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) && + stcb->asoc.peer_supports_asconf) { + /* queue an ASCONF DEL_IP_ADDRESS */ + status = sctp_asconf_queue_add_sa(stcb, sa, + SCTP_DEL_IP_ADDRESS); + /* + * if queued ok, and in correct state, + * set the ASCONF timer + */ + if (status == 0 && + SCTP_GET_STATE(&stcb->asoc) == + SCTP_STATE_OPEN) { + sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, + stcb->sctp_ep, stcb, + stcb->asoc.primary_destination); + } + } + } else { + /* address still exists */ + /* + * if subset bound, ep addr's managed by default + * if not doing ASCONF, add the address to the assoc + */ + if ((stcb->sctp_ep->sctp_flags & + SCTP_PCB_FLAGS_BOUNDALL) == 0 && + (stcb->sctp_ep->sctp_flags & + SCTP_PCB_FLAGS_DO_ASCONF) == 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("process_initack_addrs: adding local addr to asoc\n"); + } +#endif /* SCTP_DEBUG */ + sctp_add_local_addr_assoc(stcb, ifa); + } + } + + next_addr: + /* get next parameter */ + offset += SCTP_SIZE32(plen); + if ((offset + sizeof(struct sctp_paramhdr)) > length) + return; + ph = (struct sctp_paramhdr *)sctp_m_getptr(m, offset, + sizeof(struct sctp_paramhdr), (uint8_t *)&tmp_param); + } /* while */ +} + +/* FIX ME: need to verify return result for v6 address type if v6 disabled */ +/* + * checks to see if a specific address is in the initack address list + * returns 1 if found, 0 if not + */ +static uint32_t +sctp_addr_in_initack(struct sctp_tcb *stcb, struct mbuf *m, unsigned int offset, + unsigned int length, struct sockaddr *sa) +{ + struct sctp_paramhdr tmp_param, *ph; + uint16_t plen, ptype; + struct sctp_ipv6addr_param addr_store; + struct sockaddr_in *sin; + struct sctp_ipv4addr_param *a4p; +#ifdef INET6 + struct sockaddr_in6 *sin6, sin6_tmp; + struct sctp_ipv6addr_param *a6p; +#endif /* INET6 */ + + if ( +#ifdef INET6 + (sa->sa_family != AF_INET6) && +#endif /* INET6 */ + (sa->sa_family != AF_INET)) + return (0); + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("find_initack_addr: starting search for "); + sctp_print_address(sa); + } +#endif /* SCTP_DEBUG */ + /* convert to upper bound */ + length += offset; + + if ((offset + sizeof(struct sctp_paramhdr)) > length) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("find_initack_addr: invalid offset?\n"); + } +#endif /* SCTP_DEBUG */ + return (0); + } + + /* go through the addresses in the init-ack */ + ph = (struct sctp_paramhdr *)sctp_m_getptr(m, offset, + sizeof(struct sctp_paramhdr), (uint8_t *)&tmp_param); + while (ph != NULL) { + ptype = ntohs(ph->param_type); + plen = ntohs(ph->param_length); +#ifdef INET6 + if (ptype == SCTP_IPV6_ADDRESS && sa->sa_family == AF_INET6) { + /* get the entire IPv6 address param */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("addr_in_initack: checking IPv6 param\n"); + } +#endif /* SCTP_DEBUG */ + a6p = (struct sctp_ipv6addr_param *) + sctp_m_getptr(m, offset, + sizeof(struct sctp_ipv6addr_param), + (uint8_t *)&addr_store); + if (plen != sizeof(struct sctp_ipv6addr_param) || + ph == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("addr_in_initack: invalid IPv6 param length\n"); + } +#endif /* SCTP_DEBUG */ + return (0); + } + sin6 = (struct sockaddr_in6 *)sa; + if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) { + /* create a copy and clear scope */ + memcpy(&sin6_tmp, sin6, + sizeof(struct sockaddr_in6)); + sin6 = &sin6_tmp; + in6_clearscope(&sin6->sin6_addr); + } + if (memcmp(&sin6->sin6_addr, a6p->addr, + sizeof(struct in6_addr)) == 0) { + /* found it */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("addr_in_initack: found IPv6 addr\n"); + } +#endif /* SCTP_DEBUG */ + return (1); + } + } else +#endif /* INET6 */ + + if (ptype == SCTP_IPV4_ADDRESS && + sa->sa_family == AF_INET) { + /* get the entire IPv4 address param */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("addr_in_initack: checking IPv4 param\n"); + } +#endif /* SCTP_DEBUG */ + a4p = (struct sctp_ipv4addr_param *)sctp_m_getptr(m, + offset, sizeof(struct sctp_ipv4addr_param), + (uint8_t *)&addr_store); + if (plen != sizeof(struct sctp_ipv4addr_param) || + ph == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("addr_in_initack: invalid IPv4 param length\n"); + } +#endif /* SCTP_DEBUG */ + return (0); + } + sin = (struct sockaddr_in *)sa; + if (sin->sin_addr.s_addr == a4p->addr) { + /* found it */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("addr_in_initack: found IPv4 addr\n"); + } +#endif /* SCTP_DEBUG */ + return (1); + } + } else { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("addr_in_initack: skipping param type=%xh\n", ptype); + } +#endif /* SCTP_DEBUG */ + } + /* get next parameter */ + offset += SCTP_SIZE32(plen); + if (offset + sizeof(struct sctp_paramhdr) > length) + return (0); + ph = (struct sctp_paramhdr *) + sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr), + (uint8_t *)&tmp_param); + } /* while */ + /* not found! */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("addr_in_initack: not found!\n"); + } +#endif /* SCTP_DEBUG */ + return (0); +} + +/* + * makes sure that the current endpoint local addr list is consistent + * with the new association (eg. subset bound, asconf allowed) + * adds addresses as necessary + */ +static void +sctp_check_address_list_ep(struct sctp_tcb *stcb, struct mbuf *m, int offset, + int length, struct sockaddr *init_addr) +{ + struct sctp_laddr *laddr; + + /* go through the endpoint list */ + LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list, sctp_nxt_addr) { + /* be paranoid and validate the laddr */ + if (laddr->ifa == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("check_addr_list_ep: laddr->ifa is NULL"); + } +#endif + continue; + } + if (laddr->ifa->ifa_addr == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("check_addr_list_ep: laddr->ifa->ifa_addr is NULL"); + } +#endif + continue; + } + /* do i have it implicitly? */ + if (sctp_cmpaddr(laddr->ifa->ifa_addr, init_addr)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("check_address_list_all: skipping "); + sctp_print_address(laddr->ifa->ifa_addr); + } +#endif /* SCTP_DEBUG */ + continue; + } + /* check to see if in the init-ack */ + if (!sctp_addr_in_initack(stcb, m, offset, length, + laddr->ifa->ifa_addr)) { + /* try to add it */ + sctp_addr_mgmt_assoc(stcb->sctp_ep, stcb, laddr->ifa, + SCTP_ADD_IP_ADDRESS); + } + } +} + +/* + * makes sure that the current kernel address list is consistent + * with the new association (with all addrs bound) + * adds addresses as necessary + */ +static void +sctp_check_address_list_all(struct sctp_tcb *stcb, struct mbuf *m, int offset, + int length, struct sockaddr *init_addr, uint16_t local_scope, + uint16_t site_scope, uint16_t ipv4_scope, uint16_t loopback_scope) +{ + struct ifnet *ifn; + struct ifaddr *ifa; + + /* go through all our known interfaces */ + TAILQ_FOREACH(ifn, &ifnet, if_list) { + if (loopback_scope == 0 && ifn->if_type == IFT_LOOP) { + /* skip loopback interface */ + continue; + } + + /* go through each interface address */ + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + /* do i have it implicitly? */ + if (sctp_cmpaddr(ifa->ifa_addr, init_addr)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF2) { + printf("check_address_list_all: skipping "); + sctp_print_address(ifa->ifa_addr); + } +#endif /* SCTP_DEBUG */ + continue; + } + /* check to see if in the init-ack */ + if (!sctp_addr_in_initack(stcb, m, offset, length, + ifa->ifa_addr)) { + /* try to add it */ + sctp_addr_mgmt_assoc(stcb->sctp_ep, stcb, + ifa, SCTP_ADD_IP_ADDRESS); + } + } /* end foreach ifa */ + } /* end foreach ifn */ +} + +/* + * validates an init-ack chunk (from a cookie-echo) with current addresses + * adds addresses from the init-ack into our local address list, if needed + * queues asconf adds/deletes addresses as needed and makes appropriate + * list changes for source address selection + * m, offset: points to the start of the address list in an init-ack chunk + * length: total length of the address params only + * init_addr: address where my INIT-ACK was sent from + */ +void +sctp_check_address_list(struct sctp_tcb *stcb, struct mbuf *m, int offset, + int length, struct sockaddr *init_addr, uint16_t local_scope, + uint16_t site_scope, uint16_t ipv4_scope, uint16_t loopback_scope) +{ + + /* process the local addresses in the initack */ + sctp_process_initack_addresses(stcb, m, offset, length); + + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + /* bound all case */ + sctp_check_address_list_all(stcb, m, offset, length, init_addr, + local_scope, site_scope, ipv4_scope, loopback_scope); + } else { + /* subset bound case */ + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) { + /* asconf's allowed */ + sctp_check_address_list_ep(stcb, m, offset, length, + init_addr); + } + /* else, no asconfs allowed, so what we sent is what we get */ + } +} + +/* + * sctp_bindx() support + */ +uint32_t +sctp_addr_mgmt_ep_sa(struct sctp_inpcb *inp, struct sockaddr *sa, uint16_t type) +{ + struct ifaddr *ifa; + + if (sa->sa_len == 0) + return (EINVAL); + + ifa = sctp_find_ifa_by_addr(sa); + if (ifa != NULL) { +#ifdef INET6 + if (ifa->ifa_addr->sa_family == AF_INET6) { + struct in6_ifaddr *ifa6; + ifa6 = (struct in6_ifaddr *)ifa; + if (IFA6_IS_DEPRECATED(ifa6) || + (ifa6->ia6_flags & (IN6_IFF_DETACHED | + IN6_IFF_ANYCAST | IN6_IFF_NOTREADY))) { + /* Can't bind a non-existent addr. */ + return (EINVAL); + } + } +#endif /* INET6 */ + /* add this address */ + sctp_addr_mgmt_ep(inp, ifa, type); + } else { + /* invalid address! */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_ASCONF1) { + printf("addr_mgmt_ep_sa: got invalid address!\n"); + } +#endif /* SCTP_DEBUG */ + return (EADDRNOTAVAIL); + } + return (0); +} diff --git a/sys/netinet/sctp_asconf.h b/sys/netinet/sctp_asconf.h new file mode 100644 index 0000000000..505194f7db --- /dev/null +++ b/sys/netinet/sctp_asconf.h @@ -0,0 +1,66 @@ +/* $KAME: sctp_asconf.h,v 1.7 2004/08/17 04:06:16 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_asconf.h,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +#ifndef _NETINET_SCTP_ASCONF_H_ +#define _NETINET_SCTP_ASCONF_H_ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include + +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) + +extern void sctp_asconf_cleanup(struct sctp_tcb *, struct sctp_nets *); + +extern struct mbuf *sctp_compose_asconf(struct sctp_tcb *); + +extern void sctp_handle_asconf(struct mbuf *, unsigned int, struct sctp_asconf_chunk *, + struct sctp_tcb *, struct sctp_nets *); + +extern void sctp_handle_asconf_ack(struct mbuf *, int, + struct sctp_asconf_ack_chunk *, struct sctp_tcb *, struct sctp_nets *); + +extern uint32_t sctp_addr_mgmt_ep_sa(struct sctp_inpcb *, struct sockaddr *, + uint16_t); + +extern void sctp_add_ip_address(struct ifaddr *); + +extern void sctp_delete_ip_address(struct ifaddr *); + +extern int32_t sctp_set_primary_ip_address_sa(struct sctp_tcb *, + struct sockaddr *); + +extern void sctp_set_primary_ip_address(struct ifaddr *); + +extern void sctp_check_address_list(struct sctp_tcb *, struct mbuf *, int, int, + struct sockaddr *, uint16_t, uint16_t, uint16_t, uint16_t); + +#endif /* _KERNEL */ + +#endif /* !_NETINET_SCTP_ASCONF_H_ */ diff --git a/sys/netinet/sctp_callout.h b/sys/netinet/sctp_callout.h new file mode 100644 index 0000000000..ac9e336692 --- /dev/null +++ b/sys/netinet/sctp_callout.h @@ -0,0 +1,67 @@ +/* $KAME: sctp_callout.h,v 1.8 2005/01/25 07:35:42 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_callout.h,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +#ifndef __SCTP_CALLOUT__ +#define __SCTP_CALLOUT__ + +/* + * Copyright (C) 2002, 2003, 2004 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define _SCTP_NEEDS_CALLOUT_ 1 + +#ifndef __NetBSD__ +struct callout { + TAILQ_ENTRY(callout) tqe; + int c_time; /* ticks to the event */ + void *c_arg; /* function argument */ + void (*c_func) __P((void *)); /* function to call */ + int c_flags; /* state of this entry */ +}; +#endif +#define SCTP_TICKS_PER_FASTTIMO 20 /* we get called about */ + /* every 20ms */ + +TAILQ_HEAD(calloutlist, callout); + +#define CALLOUT_ACTIVE 0x0002 /* callout is currently active */ +#ifndef __NetBSD__ +#define CALLOUT_PENDING 0x0004 /* callout is waiting for timeout */ +#define CALLOUT_FIRED 0x0008 /* it expired */ +#endif + +#define callout_active(c) ((c)->c_flags & CALLOUT_ACTIVE) +#define callout_deactivate(c) ((c)->c_flags &= ~CALLOUT_ACTIVE) +void callout_init __P((struct callout *)); +#define callout_pending(c) ((c)->c_flags & CALLOUT_PENDING) + +void callout_reset __P((struct callout *, int, void (*)(void *), void *)); +#ifndef __NetBSD__ +int callout_stop __P((struct callout *)); +#endif +#endif diff --git a/sys/netinet/sctp_constants.h b/sys/netinet/sctp_constants.h new file mode 100644 index 0000000000..f0d88833c3 --- /dev/null +++ b/sys/netinet/sctp_constants.h @@ -0,0 +1,832 @@ +/* $KAME: sctp_constants.h,v 1.16 2004/08/17 04:06:16 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_constants.h,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +#ifndef __sctp_constants_h__ +#define __sctp_constants_h__ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#define SCTP_VERSION_STRING "KAME-BSD 1.1" +/*#define SCTP_AUDITING_ENABLED 1 used for debug/auditing */ +#define SCTP_AUDIT_SIZE 256 +#define SCTP_STAT_LOG_SIZE 80000 + +/* Places that CWND log can happen from */ +#define SCTP_CWND_LOG_FROM_FR 1 +#define SCTP_CWND_LOG_FROM_RTX 2 +#define SCTP_CWND_LOG_FROM_BRST 3 +#define SCTP_CWND_LOG_FROM_SS 4 +#define SCTP_CWND_LOG_FROM_CA 5 +#define SCTP_CWND_LOG_FROM_SAT 6 +#define SCTP_BLOCK_LOG_INTO_BLK 7 +#define SCTP_BLOCK_LOG_OUTOF_BLK 8 +#define SCTP_BLOCK_LOG_CHECK 9 +#define SCTP_STR_LOG_FROM_INTO_STRD 10 +#define SCTP_STR_LOG_FROM_IMMED_DEL 11 +#define SCTP_STR_LOG_FROM_INSERT_HD 12 +#define SCTP_STR_LOG_FROM_INSERT_MD 13 +#define SCTP_STR_LOG_FROM_INSERT_TL 14 +#define SCTP_STR_LOG_FROM_MARK_TSN 15 +#define SCTP_STR_LOG_FROM_EXPRS_DEL 16 +#define SCTP_FR_LOG_BIGGEST_TSNS 17 +#define SCTP_FR_LOG_STRIKE_TEST 18 +#define SCTP_FR_LOG_STRIKE_CHUNK 19 +#define SCTP_FR_T3_TIMEOUT 20 +#define SCTP_MAP_PREPARE_SLIDE 21 +#define SCTP_MAP_SLIDE_FROM 22 +#define SCTP_MAP_SLIDE_RESULT 23 +#define SCTP_MAP_SLIDE_CLEARED 24 +#define SCTP_MAP_SLIDE_NONE 25 +#define SCTP_FR_T3_MARK_TIME 26 +#define SCTP_FR_T3_MARKED 27 +#define SCTP_FR_T3_STOPPED 28 +#define SCTP_FR_MARKED 30 +#define SCTP_CWND_LOG_NOADV_SS 31 +#define SCTP_CWND_LOG_NOADV_CA 32 +#define SCTP_MAX_BURST_APPLIED 33 +#define SCTP_MAX_IFP_APPLIED 34 +#define SCTP_MAX_BURST_ERROR_STOP 35 +#define SCTP_INCREASE_PEER_RWND 36 +#define SCTP_DECREASE_PEER_RWND 37 +#define SCTP_SET_PEER_RWND_VIA_SACK 38 +#define SCTP_LOG_MBCNT_INCREASE 39 +#define SCTP_LOG_MBCNT_DECREASE 40 +#define SCTP_LOG_MBCNT_CHKSET 41 +/* + * To turn on various logging, you must first define SCTP_STAT_LOGGING. + * Then to get something to log you define one of the logging defines i.e. + * + * SCTP_CWND_LOGGING + * SCTP_BLK_LOGGING + * SCTP_STR_LOGGING + * SCTP_FR_LOGGING + * + * Any one or a combination of the logging can be turned on. + */ +#define SCTP_LOG_EVENT_CWND 1 +#define SCTP_LOG_EVENT_BLOCK 2 +#define SCTP_LOG_EVENT_STRM 3 +#define SCTP_LOG_EVENT_FR 4 +#define SCTP_LOG_EVENT_MAP 5 +#define SCTP_LOG_EVENT_MAXBURST 6 +#define SCTP_LOG_EVENT_RWND 7 +#define SCTP_LOG_EVENT_MBCNT 8 + + +/* number of associations by default for zone allocation */ +#define SCTP_MAX_NUM_OF_ASOC 40000 +/* how many addresses per assoc remote and local */ +#define SCTP_SCALE_FOR_ADDR 2 + +/* default AUTO_ASCONF mode enable(1)/disable(0) value (sysctl) */ +#define SCTP_DEFAULT_AUTO_ASCONF 0 + +/* + * If you wish to use MD5 instead of SLA uncomment the line below. + * Why you would like to do this: + * a) There may be IPR on SHA-1, or so the FIP-180-1 page says, + * b) MD5 is 3 times faster (has coded here). + * + * The disadvantage is it is thought that MD5 has been cracked... see RFC2104. + */ +/*#define USE_MD5 1 */ +/* + * Note: I can't seem to get this to compile now for some reason- the + * kernel can't link in the md5 crypto + */ + +/* DEFINE HERE WHAT CRC YOU WANT TO USE */ +#define SCTP_USECRC_RFC2960 1 +/*#define SCTP_USECRC_FLETCHER 1*/ +/*#define SCTP_USECRC_SSHCRC32 1*/ +/*#define SCTP_USECRC_FASTCRC32 1*/ +/*#define SCTP_USECRC_CRC32 1*/ +/*#define SCTP_USECRC_TCP32 1*/ +/*#define SCTP_USECRC_CRC16SMAL 1*/ +/*#define SCTP_USECRC_CRC16 1 */ +/*#define SCTP_USECRC_MODADLER 1*/ + +#ifndef SCTP_ADLER32_BASE +#define SCTP_ADLER32_BASE 65521 +#endif + +#define SCTP_CWND_POSTS_LIST 256 +/* + * the SCTP protocol signature + * this includes the version number encoded in the last 4 bits + * of the signature. + */ +#define PROTO_SIGNATURE_A 0x30000000 +#define SCTP_VERSION_NUMBER 0x3 + +#define MAX_TSN 0xffffffff +#define MAX_SEQ 0xffff + +/* how many executions every N tick's */ +#define SCTP_MAX_ITERATOR_AT_ONCE 20 + +/* number of clock ticks between iterator executions */ +#define SCTP_ITERATOR_TICKS 1 + +/* option: + * If you comment out the following you will receive the old + * behavior of obeying cwnd for the fast retransmit algorithm. + * With this defined a FR happens right away with-out waiting + * for the flightsize to drop below the cwnd value (which is + * reduced by the FR to 1/2 the inflight packets). + */ +#define SCTP_IGNORE_CWND_ON_FR 1 + +/* + * Adds implementors guide behavior to only use newest highest + * update in SACK gap ack's to figure out if you need to stroke + * a chunk for FR. + */ +#define SCTP_NO_FR_UNLESS_SEGMENT_SMALLER 1 + +/* default max I can burst out after a fast retransmit */ +#define SCTP_DEF_MAX_BURST 8 + +/* Packet transmit states in the sent field */ +#define SCTP_DATAGRAM_UNSENT 0 +#define SCTP_DATAGRAM_SENT 1 +#define SCTP_DATAGRAM_RESEND1 2 /* not used (in code, but may hit this value) */ +#define SCTP_DATAGRAM_RESEND2 3 /* not used (in code, but may hit this value) */ +#define SCTP_DATAGRAM_RESEND3 4 /* not used (in code, but may hit this value) */ +#define SCTP_DATAGRAM_RESEND 5 +#define SCTP_DATAGRAM_ACKED 10010 +#define SCTP_DATAGRAM_INBOUND 10011 +#define SCTP_READY_TO_TRANSMIT 10012 +#define SCTP_DATAGRAM_MARKED 20010 +#define SCTP_FORWARD_TSN_SKIP 30010 + +/* SCTP chunk types */ +/* Moved to sctp.h so f/w and natd + * boxes can find the chunk types. + */ + +/* align to 32-bit sizes */ +#define SCTP_SIZE32(x) ((((x)+3) >> 2) << 2) + +#define IS_SCTP_CONTROL(a) ((a)->chunk_type != SCTP_DATA) +#define IS_SCTP_DATA(a) ((a)->chunk_type == SCTP_DATA) + +/* SCTP parameter types */ +#define SCTP_HEARTBEAT_INFO 0x0001 +#define SCTP_IPV4_ADDRESS 0x0005 +#define SCTP_IPV6_ADDRESS 0x0006 +#define SCTP_STATE_COOKIE 0x0007 +#define SCTP_UNRECOG_PARAM 0x0008 +#define SCTP_COOKIE_PRESERVE 0x0009 +#define SCTP_HOSTNAME_ADDRESS 0x000b +#define SCTP_SUPPORTED_ADDRTYPE 0x000c +#define SCTP_ECN_CAPABLE 0x8000 +/* draft-ietf-stewart-strreset-xxx */ +#define SCTP_STR_RESET_REQUEST 0x000d +#define SCTP_STR_RESET_RESPONSE 0x000e + +/* ECN Nonce: draft-ladha-sctp-ecn-nonce */ +#define SCTP_ECN_NONCE_SUPPORTED 0x8001 +/* + * draft-ietf-stewart-strreset-xxx + * param=0x8001 len=0xNNNN + * Byte | Byte | Byte | Byte + * Byte | Byte ... + * + * Where each Byte is a chunk type + * extension supported so for example + * to support all chunks one would have (in hex): + * + * 80 01 00 09 + * C0 C1 80 81 + * 82 00 00 00 + * + * Has the parameter. + * C0 = PR-SCTP (RFC3758) + * C1, 80 = ASCONF (addip draft) + * 81 = Packet Drop + * 82 = Stream Reset + */ + +/* draft-ietf-tsvwg-prsctp */ +#define SCTP_SUPPORTED_CHUNK_EXT 0x8008 + +/* number of extensions we support */ +#define SCTP_EXT_COUNT 5 /* num of extensions we support chunk wise */ +#define SCTP_PAD_EXT_COUNT 3 /* num of pad bytes needed to get to 32 bit boundary */ + + +#define SCTP_PRSCTP_SUPPORTED 0xc000 +/* draft-ietf-tsvwg-addip-sctp */ +#define SCTP_ADD_IP_ADDRESS 0xc001 +#define SCTP_DEL_IP_ADDRESS 0xc002 +#define SCTP_ERROR_CAUSE_IND 0xc003 +#define SCTP_SET_PRIM_ADDR 0xc004 +#define SCTP_SUCCESS_REPORT 0xc005 +#define SCTP_ULP_ADAPTION 0xc006 + +/* Notification error codes */ +#define SCTP_NOTIFY_DATAGRAM_UNSENT 0x0001 +#define SCTP_NOTIFY_DATAGRAM_SENT 0x0002 +#define SCTP_FAILED_THRESHOLD 0x0004 +#define SCTP_HEARTBEAT_SUCCESS 0x0008 +#define SCTP_RESPONSE_TO_USER_REQ 0x000f +#define SCTP_INTERNAL_ERROR 0x0010 +#define SCTP_SHUTDOWN_GUARD_EXPIRES 0x0020 +#define SCTP_RECEIVED_SACK 0x0040 +#define SCTP_PEER_FAULTY 0x0080 + +/* Error causes used in SCTP op-err's and aborts */ +#define SCTP_CAUSE_INV_STRM 0x001 +#define SCTP_CAUSE_MISS_PARAM 0x002 +#define SCTP_CAUSE_STALE_COOKIE 0x003 +#define SCTP_CAUSE_OUT_OF_RESC 0x004 +#define SCTP_CAUSE_UNRESOLV_ADDR 0x005 +#define SCTP_CAUSE_UNRECOG_CHUNK 0x006 +#define SCTP_CAUSE_INVALID_PARAM 0x007 +/* This one is also the same as SCTP_UNRECOG_PARAM above */ +#define SCTP_CAUSE_UNRECOG_PARAM 0x008 +#define SCTP_CAUSE_NOUSER_DATA 0x009 +#define SCTP_CAUSE_COOKIE_IN_SHUTDOWN 0x00a +#define SCTP_CAUSE_RESTART_W_NEWADDR 0x00b +#define SCTP_CAUSE_USER_INITIATED_ABT 0x00c +#define SCTP_CAUSE_PROTOCOL_VIOLATION 0x00d + +/* Error's from add ip */ +#define SCTP_CAUSE_DELETEING_LAST_ADDR 0x100 +#define SCTP_CAUSE_OPERATION_REFUSED 0x101 +#define SCTP_CAUSE_DELETING_SRC_ADDR 0x102 +#define SCTP_CAUSE_ILLEGAL_ASCONF 0x103 + +/* bits for TOS field */ +#define SCTP_ECT0_BIT 0x02 +#define SCTP_ECT1_BIT 0x01 +#define SCTP_CE_BITS 0x03 + +/* below turns off above */ +#define SCTP_FLEXIBLE_ADDRESS 0x20 +#define SCTP_NO_HEARTBEAT 0x40 + +/* mask to get sticky */ +#define SCTP_STICKY_OPTIONS_MASK 0x0c + +/* MTU discovery flags */ +#define SCTP_DONT_FRAGMENT 0x0100 +#define SCTP_FRAGMENT_OK 0x0200 +#define SCTP_PR_SCTP_ENABLED 0x0400 +#define SCTP_PR_SCTP_BUFFER 0x0800 + +/* Chunk flags */ +#define SCTP_WINDOW_PROBE 0x01 + +/* + * SCTP states for internal state machine + * XXX (should match "user" values) + */ +#define SCTP_STATE_EMPTY 0x0000 +#define SCTP_STATE_INUSE 0x0001 +#define SCTP_STATE_COOKIE_WAIT 0x0002 +#define SCTP_STATE_COOKIE_ECHOED 0x0004 +#define SCTP_STATE_OPEN 0x0008 +#define SCTP_STATE_SHUTDOWN_SENT 0x0010 +#define SCTP_STATE_SHUTDOWN_RECEIVED 0x0020 +#define SCTP_STATE_SHUTDOWN_ACK_SENT 0x0040 +#define SCTP_STATE_SHUTDOWN_PENDING 0x0080 +#define SCTP_STATE_CLOSED_SOCKET 0x0100 +#define SCTP_STATE_MASK 0x007f + +#define SCTP_GET_STATE(asoc) ((asoc)->state & SCTP_STATE_MASK) + +/* SCTP reachability state for each address */ +#define SCTP_ADDR_REACHABLE 0x001 +#define SCTP_ADDR_NOT_REACHABLE 0x002 +#define SCTP_ADDR_NOHB 0x004 +#define SCTP_ADDR_BEING_DELETED 0x008 +#define SCTP_ADDR_NOT_IN_ASSOC 0x010 +#define SCTP_ADDR_WAS_PRIMARY 0x020 +#define SCTP_ADDR_SWITCH_PRIMARY 0x040 +#define SCTP_ADDR_OUT_OF_SCOPE 0x080 +#define SCTP_ADDR_DOUBLE_SWITCH 0x100 +#define SCTP_ADDR_UNCONFIRMED 0x200 + +#define SCTP_REACHABLE_MASK 0x203 + +/* bound address types (e.g. valid address types to allow) */ +#define SCTP_BOUND_V6 0x01 +#define SCTP_BOUND_V4 0x02 + +/* How long a cookie lives in seconds */ +#define SCTP_DEFAULT_COOKIE_LIFE 60 + +/* resource limit of streams */ +#define MAX_SCTP_STREAMS 2048 + +/* Maximum the mapping array will grow to (TSN mapping array) */ +#define SCTP_MAPPING_ARRAY 512 + +/* size of the inital malloc on the mapping array */ +#define SCTP_INITIAL_MAPPING_ARRAY 16 +/* how much we grow the mapping array each call */ +#define SCTP_MAPPING_ARRAY_INCR 32 + +/* + * Here we define the timer types used by the implementation + * as arguments in the set/get timer type calls. + */ +#define SCTP_TIMER_INIT 0 +#define SCTP_TIMER_RECV 1 +#define SCTP_TIMER_SEND 2 +#define SCTP_TIMER_HEARTBEAT 3 +#define SCTP_TIMER_PMTU 4 +#define SCTP_TIMER_MAXSHUTDOWN 5 +#define SCTP_TIMER_SIGNATURE 6 +/* + * number of timer types in the base SCTP structure used in + * the set/get and has the base default. + */ +#define SCTP_NUM_TMRS 7 + +/* timer types */ +#define SCTP_TIMER_TYPE_NONE 0 +#define SCTP_TIMER_TYPE_SEND 1 +#define SCTP_TIMER_TYPE_INIT 2 +#define SCTP_TIMER_TYPE_RECV 3 +#define SCTP_TIMER_TYPE_SHUTDOWN 4 +#define SCTP_TIMER_TYPE_HEARTBEAT 5 +#define SCTP_TIMER_TYPE_COOKIE 6 +#define SCTP_TIMER_TYPE_NEWCOOKIE 7 +#define SCTP_TIMER_TYPE_PATHMTURAISE 8 +#define SCTP_TIMER_TYPE_SHUTDOWNACK 9 +#define SCTP_TIMER_TYPE_ASCONF 10 +#define SCTP_TIMER_TYPE_SHUTDOWNGUARD 11 +#define SCTP_TIMER_TYPE_AUTOCLOSE 12 +#define SCTP_TIMER_TYPE_EVENTWAKE 13 +#define SCTP_TIMER_TYPE_STRRESET 14 +#define SCTP_TIMER_TYPE_INPKILL 15 + +/* + * Number of ticks before the soxwakeup() event that + * is delayed is sent AFTER the accept() call + */ +#define SCTP_EVENTWAKEUP_WAIT_TICKS 3000 + +/* + * Of course we really don't collect stale cookies, being folks + * of decerning taste. However we do count them, if we get too + * many before the association comes up.. we give up. Below is + * the constant that dictates when we give it up...this is a + * implemenation dependent treatment. In ours we do not ask for + * a extension of time, but just retry this many times... + */ +#define SCTP_MAX_STALE_COOKIES_I_COLLECT 10 + +/* max number of TSN's dup'd that I will hold */ +#define SCTP_MAX_DUP_TSNS 20 + +#define SCTP_TIMER_TYPE_ITERATOR 16 +/* + * Here we define the types used when setting the retry amounts. + */ +/* constants for type of set */ +#define SCTP_MAXATTEMPT_INIT 2 +#define SCTP_MAXATTEMPT_SEND 3 + +/* Maximum TSN's we will summarize in a drop report */ + +#define SCTP_MAX_DROP_REPORT 16 + +/* How many drop re-attempts we make on INIT/COOKIE-ECHO */ +#define SCTP_RETRY_DROPPED_THRESH 4 + +/* And the max we will keep a history of in the tcb + * which MUST be lower than 256. + */ + +#define SCTP_MAX_DROP_SAVE_REPORT 16 + +/* + * Here we define the default timers and the default number + * of attemts we make for each respective side (send/init). + */ + +/* Maxmium number of chunks a single association can have + * on it. Note that this is a squishy number since + * the count can run over this if the user sends a large + * message down .. the fragmented chunks don't count until + * AFTER the message is on queue.. it would be the next + * send that blocks things. This number will get tuned + * up at boot in the sctp_init and use the number + * of clusters as a base. This way high bandwidth + * environments will not get impacted by the lower + * bandwidth sending a bunch of 1 byte chunks + */ +#define SCTP_ASOC_MAX_CHUNKS_ON_QUEUE 512 + +#define MSEC_TO_TICKS(x) (((x) * hz) / 1000) +#define TICKS_TO_MSEC(x) (((x) * 1000) / hz) +#define SEC_TO_TICKS(x) ((x) * hz) + +/* init timer def = 1 sec */ +#define SCTP_INIT_SEC 1 + +/* send timer def = 1 seconds */ +#define SCTP_SEND_SEC 1 + +/* recv timer def = 200ms */ +#define SCTP_RECV_MSEC 200 + +/* 30 seconds + RTO (in ms) */ +#define SCTP_HB_DEFAULT_MSEC 30000 + +/* Max time I will wait for Shutdown to complete */ +#define SCTP_DEF_MAX_SHUTDOWN_SEC 180 + + +/* This is how long a secret lives, NOT how long a cookie lives + * how many ticks the current secret will live. + */ +#define SCTP_DEFAULT_SECRET_LIFE_SEC 3600 + +#define SCTP_RTO_UPPER_BOUND (60000) /* 60 sec in ms */ +#define SCTP_RTO_UPPER_BOUND_SEC 60 /* for the init timer */ +#define SCTP_RTO_LOWER_BOUND (1000) /* 1 sec in ms */ +#define SCTP_RTO_INITIAL (3000) /* 3 sec in ms */ + + +#define SCTP_INP_KILL_TIMEOUT 1000 /* number of ms to retry kill of inpcb*/ + +#define SCTP_DEF_MAX_INIT 8 +#define SCTP_DEF_MAX_SEND 10 + +#define SCTP_DEF_PMTU_RAISE_SEC 600 /* 10 min between raise attempts */ +#define SCTP_DEF_PMTU_MIN 600 + +#define SCTP_MSEC_IN_A_SEC 1000 +#define SCTP_USEC_IN_A_SEC 1000000 +#define SCTP_NSEC_IN_A_SEC 1000000000 + +#define SCTP_MAX_OUTSTANDING_DG 10000 + +/* How many streams I request initally by default */ +#define SCTP_OSTREAM_INITIAL 10 + +#define SCTP_SEG_TO_RWND_UPD 32 /* How many smallest_mtu's need to increase before + * a window update sack is sent (should be a + * power of 2). + */ +#define SCTP_SCALE_OF_RWND_TO_UPD 4 /* Incr * this > hiwat, send + * window update. Should be a + * power of 2. + */ +#define SCTP_MINIMAL_RWND (4096) /* minimal rwnd */ + +#define SCTP_ADDRMAX 20 + + +/* SCTP DEBUG Switch parameters */ +#define SCTP_DEBUG_TIMER1 0x00000001 +#define SCTP_DEBUG_TIMER2 0x00000002 +#define SCTP_DEBUG_TIMER3 0x00000004 +#define SCTP_DEBUG_TIMER4 0x00000008 +#define SCTP_DEBUG_OUTPUT1 0x00000010 +#define SCTP_DEBUG_OUTPUT2 0x00000020 +#define SCTP_DEBUG_OUTPUT3 0x00000040 +#define SCTP_DEBUG_OUTPUT4 0x00000080 +#define SCTP_DEBUG_UTIL1 0x00000100 +#define SCTP_DEBUG_UTIL2 0x00000200 +#define SCTP_DEBUG_INPUT1 0x00001000 +#define SCTP_DEBUG_INPUT2 0x00002000 +#define SCTP_DEBUG_INPUT3 0x00004000 +#define SCTP_DEBUG_INPUT4 0x00008000 +#define SCTP_DEBUG_ASCONF1 0x00010000 +#define SCTP_DEBUG_ASCONF2 0x00020000 +#define SCTP_DEBUG_OUTPUT5 0x00040000 +#define SCTP_DEBUG_PCB1 0x00100000 +#define SCTP_DEBUG_PCB2 0x00200000 +#define SCTP_DEBUG_PCB3 0x00400000 +#define SCTP_DEBUG_PCB4 0x00800000 +#define SCTP_DEBUG_INDATA1 0x01000000 +#define SCTP_DEBUG_INDATA2 0x02000000 +#define SCTP_DEBUG_INDATA3 0x04000000 +#define SCTP_DEBUG_INDATA4 0x08000000 +#define SCTP_DEBUG_USRREQ1 0x10000000 +#define SCTP_DEBUG_USRREQ2 0x20000000 +#define SCTP_DEBUG_PEEL1 0x40000000 +#define SCTP_DEBUG_ALL 0x7ff3f3ff +#define SCTP_DEBUG_NOISY 0x00040000 + +/* What sender needs to see to avoid SWS or we consider peers rwnd 0 */ +#define SCTP_SWS_SENDER_DEF 1420 + +/* + * SWS is scaled to the sb_hiwat of the socket. + * A value of 2 is hiwat/4, 1 would be hiwat/2 etc. + */ +/* What receiver needs to see in sockbuf or we tell peer its 1 */ +#define SCTP_SWS_RECEIVER_DEF 3000 + + +#define SCTP_INITIAL_CWND 4380 + +/* amount peer is obligated to have in rwnd or I will abort */ +#define SCTP_MIN_RWND 1500 + +#define SCTP_WINDOW_MIN 1500 /* smallest rwnd can be */ +#define SCTP_WINDOW_MAX 1048576 /* biggest I can grow rwnd to + * My playing around suggests a + * value greater than 64k does not + * do much, I guess via the kernel + * limitations on the stream/socket. + */ + +#define SCTP_MAX_BUNDLE_UP 256 /* max number of chunks to bundle */ + +/* I can handle a 1meg re-assembly */ +#define SCTP_DEFAULT_MAXMSGREASM 1048576 + +#define SCTP_DEFAULT_MAXSEGMENT 65535 + +#define DEFAULT_CHUNK_BUFFER 2048 +#define DEFAULT_PARAM_BUFFER 512 + +#define SCTP_DEFAULT_MINSEGMENT 512 /* MTU size ... if no mtu disc */ +#define SCTP_HOW_MANY_SECRETS 2 /* how many secrets I keep */ + +#define SCTP_NUMBER_OF_SECRETS 8 /* or 8 * 4 = 32 octets */ +#define SCTP_SECRET_SIZE 32 /* number of octets in a 256 bits */ + +#ifdef USE_MD5 +#define SCTP_SIGNATURE_SIZE 16 /* size of a MD5 signature */ +#else +#define SCTP_SIGNATURE_SIZE 20 /* size of a SLA-1 signature */ +#endif /* USE_MD5 */ + +#define SCTP_SIGNATURE_ALOC_SIZE 20 + +/* + * SCTP upper layer notifications + */ +#define SCTP_NOTIFY_ASSOC_UP 1 +#define SCTP_NOTIFY_ASSOC_DOWN 2 +#define SCTP_NOTIFY_INTERFACE_DOWN 3 +#define SCTP_NOTIFY_INTERFACE_UP 4 +#define SCTP_NOTIFY_DG_FAIL 5 +#define SCTP_NOTIFY_STRDATA_ERR 6 +#define SCTP_NOTIFY_ASSOC_ABORTED 7 +#define SCTP_NOTIFY_PEER_OPENED_STREAM 8 +#define SCTP_NOTIFY_STREAM_OPENED_OK 9 +#define SCTP_NOTIFY_ASSOC_RESTART 10 +#define SCTP_NOTIFY_HB_RESP 11 +#define SCTP_NOTIFY_ASCONF_SUCCESS 12 +#define SCTP_NOTIFY_ASCONF_FAILED 13 +#define SCTP_NOTIFY_PEER_SHUTDOWN 14 +#define SCTP_NOTIFY_ASCONF_ADD_IP 15 +#define SCTP_NOTIFY_ASCONF_DELETE_IP 16 +#define SCTP_NOTIFY_ASCONF_SET_PRIMARY 17 +#define SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION 18 +#define SCTP_NOTIFY_ADAPTION_INDICATION 19 +#define SCTP_NOTIFY_INTERFACE_CONFIRMED 20 +#define SCTP_NOTIFY_STR_RESET_RECV 21 +#define SCTP_NOTIFY_STR_RESET_SEND 22 +#define SCTP_NOTIFY_MAX 22 + + + + + +/* clock variance is 10ms */ +#define SCTP_CLOCK_GRANULARITY 10 + +#define IP_HDR_SIZE 40 /* we use the size of a IP6 header here + * this detracts a small amount for ipv4 + * but it simplifies the ipv6 addition + */ + +#ifndef IPPROTO_SCTP +#define IPPROTO_SCTP 132 /* the Official IANA number :-) */ +#endif /* !IPPROTO_SCTP */ + +#define SCTP_MAX_DATA_BUNDLING 256 +#define SCTP_MAX_CONTROL_BUNDLING 20 + +/* modular comparison */ +/* True if a > b (mod = M) */ +#define compare_with_wrap(a, b, M) (((a > b) && ((a - b) < ((M >> 1) + 1))) || \ + ((b > a) && ((b - a) > ((M >> 1) + 1)))) + + +/* Mapping array manipulation routines */ +#define SCTP_IS_TSN_PRESENT(arry, gap) ((arry[(gap >> 3)] >> (gap & 0x07)) & 0x01) +#define SCTP_SET_TSN_PRESENT(arry, gap) (arry[(gap >> 3)] |= (0x01 << ((gap & 0x07)))) +#define SCTP_UNSET_TSN_PRESENT(arry, gap) (arry[(gap >> 3)] &= ((~(0x01 << ((gap & 0x07)))) & 0xff)) + +/* pegs */ +#define SCTP_NUMBER_OF_PEGS 96 +/* peg index's */ +#define SCTP_PEG_SACKS_SEEN 0 +#define SCTP_PEG_SACKS_SENT 1 +#define SCTP_PEG_TSNS_SENT 2 +#define SCTP_PEG_TSNS_RCVD 3 +#define SCTP_DATAGRAMS_SENT 4 +#define SCTP_DATAGRAMS_RCVD 5 +#define SCTP_RETRANTSN_SENT 6 +#define SCTP_DUPTSN_RECVD 7 +#define SCTP_HB_RECV 8 +#define SCTP_HB_ACK_RECV 9 +#define SCTP_HB_SENT 10 +#define SCTP_WINDOW_PROBES 11 +#define SCTP_DATA_DG_RECV 12 +#define SCTP_TMIT_TIMER 13 +#define SCTP_RECV_TIMER 14 +#define SCTP_HB_TIMER 15 +#define SCTP_FAST_RETRAN 16 +#define SCTP_TIMERS_EXP 17 +#define SCTP_FR_INAWINDOW 18 +#define SCTP_RWND_BLOCKED 19 +#define SCTP_CWND_BLOCKED 20 +#define SCTP_RWND_DROPS 21 +#define SCTP_BAD_STRMNO 22 +#define SCTP_BAD_SSN_WRAP 23 +#define SCTP_DROP_NOMEMORY 24 +#define SCTP_DROP_FRAG 25 +#define SCTP_BAD_VTAGS 26 +#define SCTP_BAD_CSUM 27 +#define SCTP_INPKTS 28 +#define SCTP_IN_MCAST 29 +#define SCTP_HDR_DROPS 30 +#define SCTP_NOPORTS 31 +#define SCTP_CWND_NOFILL 32 +#define SCTP_CALLS_TO_CO 33 +#define SCTP_CO_NODATASNT 34 +#define SCTP_CWND_NOUSE_SS 35 +#define SCTP_MAX_BURST_APL 36 +#define SCTP_EXPRESS_ROUTE 37 +#define SCTP_NO_COPY_IN 38 +#define SCTP_CACHED_SRC 39 +#define SCTP_CWND_NOCUM 40 +#define SCTP_CWND_SS 41 +#define SCTP_CWND_CA 42 +#define SCTP_CWND_SKIP 43 +#define SCTP_CWND_NOUSE_CA 44 +#define SCTP_MAX_CWND 45 +#define SCTP_CWND_DIFF_CA 46 +#define SCTP_CWND_DIFF_SA 47 +#define SCTP_OQS_AT_SS 48 +#define SCTP_SQQ_AT_SS 49 +#define SCTP_OQS_AT_CA 50 +#define SCTP_SQQ_AT_CA 51 +#define SCTP_MOVED_MTU 52 +#define SCTP_MOVED_QMAX 53 +#define SCTP_SQC_AT_SS 54 +#define SCTP_SQC_AT_CA 55 +#define SCTP_MOVED_MAX 56 +#define SCTP_MOVED_NLEF 57 +#define SCTP_NAGLE_NOQ 58 +#define SCTP_NAGLE_OFF 59 +#define SCTP_OUTPUT_FRM_SND 60 +#define SCTP_SOS_NOSNT 61 +#define SCTP_NOS_NOSNT 62 +#define SCTP_SOSE_NOSNT 63 +#define SCTP_NOSE_NOSNT 64 +#define SCTP_DATA_OUT_ERR 65 +#define SCTP_DUP_SSN_RCVD 66 +#define SCTP_DUP_FR 67 +#define SCTP_VTAG_EXPR 68 +#define SCTP_VTAG_BOGUS 69 +#define SCTP_T3_SAFEGRD 70 +#define SCTP_PDRP_FMBOX 71 +#define SCTP_PDRP_FEHOS 72 +#define SCTP_PDRP_MB_DA 73 +#define SCTP_PDRP_MB_CT 74 +#define SCTP_PDRP_BWRPT 75 +#define SCTP_PDRP_CRUPT 76 +#define SCTP_PDRP_NEDAT 77 +#define SCTP_PDRP_PDBRK 78 +#define SCTP_PDRP_TSNNF 79 +#define SCTP_PDRP_DNFND 80 +#define SCTP_PDRP_DIWNP 81 +#define SCTP_PDRP_DIZRW 82 +#define SCTP_PDRP_BADD 83 +#define SCTP_PDRP_MARK 84 +#define SCTP_ECNE_RCVD 85 +#define SCTP_CWR_PERFO 86 +#define SCTP_ECNE_SENT 87 +#define SCTP_MSGC_DROP 88 +#define SCTP_SEND_QUEUE_POP 89 +#define SCTP_ERROUT_FRM_USR 90 +#define SCTP_SENDTO_FULL_CWND 91 +#define SCTP_QUEONLY_BURSTLMT 92 +#define SCTP_IFP_QUEUE_FULL 93 +#define SCTP_RESV2 94 +#define SCTP_RESV3 95 + +/* + * This value defines the number of vtag block time wait entry's + * per list element. Each entry will take 2 4 byte ints (and of + * course the overhead of the next pointer as well). Using 15 as + * an example will yield * ((8 * 15) + 8) or 128 bytes of overhead + * for each timewait block that gets initialized. Increasing it to + * 31 would yeild 256 bytes per block. + */ +/* Undef the following turns on per EP behavior */ +#define SCTP_VTAG_TIMEWAIT_PER_STACK 1 +#ifdef SCTP_VTAG_TIMEWAIT_PER_STACK +#define SCTP_NUMBER_IN_VTAG_BLOCK 15 +#else +/* The hash list is smaller if we are on a ep basis */ +#define SCTP_NUMBER_IN_VTAG_BLOCK 3 +#endif +/* + * If we use the STACK option, we have an array of this size head + * pointers. This array is mod'd the with the size to find which + * bucket and then all entries must be searched to see if the tag + * is in timed wait. If so we reject it. + */ +#define SCTP_STACK_VTAG_HASH_SIZE 31 + +/* + * If we use the per-endpoint model than we do not have a hash + * table of entries but instead have a single head pointer and + * we must crawl through the entire list. + */ + +/* + * Number of seconds of time wait, tied to MSL value (2 minutes), + * so 2 * MSL = 4 minutes or 480 seconds. + */ +#define SCTP_TIME_WAIT 480 + +#define IN4_ISPRIVATE_ADDRESS(a) \ + ((((u_char *)&(a)->s_addr)[0] == 10) || \ + ((((u_char *)&(a)->s_addr)[0] == 172) && \ + (((u_char *)&(a)->s_addr)[1] >= 16) && \ + (((u_char *)&(a)->s_addr)[1] <= 32)) || \ + ((((u_char *)&(a)->s_addr)[0] == 192) && \ + (((u_char *)&(a)->s_addr)[1] == 168))) + +#define IN4_ISLOOPBACK_ADDRESS(a) \ + ((((u_char *)&(a)->s_addr)[0] == 127) && \ + (((u_char *)&(a)->s_addr)[1] == 0) && \ + (((u_char *)&(a)->s_addr)[2] == 0) && \ + (((u_char *)&(a)->s_addr)[3] == 1)) + + +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) + +#if defined(__FreeBSD__) || defined(__APPLE__) +#define SCTP_GETTIME_TIMEVAL(x) (microuptime(x)) +#define SCTP_GETTIME_TIMESPEC(x) (nanouptime(x)) +#else +#define SCTP_GETTIME_TIMEVAL(x) (microtime(x)) +#define SCTP_GETTIME_TIMESPEC(x) (nanotime(x)) +#endif /* __FreeBSD__ */ + +#define sctp_sowwakeup(inp, so) \ +do { \ + if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { \ + inp->sctp_flags |= SCTP_PCB_FLAGS_WAKEOUTPUT; \ + } else { \ + sowwakeup(so); \ + } \ +} while (0) + +#define sctp_sorwakeup(inp, so) \ +do { \ + if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { \ + inp->sctp_flags |= SCTP_PCB_FLAGS_WAKEINPUT; \ + } else { \ + sorwakeup(so); \ + } \ +} while (0) + +#endif /* _KERNEL */ +#endif diff --git a/sys/netinet/sctp_crc32.c b/sys/netinet/sctp_crc32.c new file mode 100644 index 0000000000..02a526c84e --- /dev/null +++ b/sys/netinet/sctp_crc32.c @@ -0,0 +1,181 @@ +/* $KAME: sctp_crc32.c,v 1.11 2004/08/17 04:06:16 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_crc32.c,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif +#include +#include + +#ifndef SCTP_USE_ADLER32 + +#define SCTP_CRC32C_POLY 0x1EDC6F41 +#define SCTP_CRC32C(c, d) (c = ((c) >> 8) ^ sctp_crc_c[((c) ^ (d)) & 0xFF]) + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ +/* Copyright 2001, D. Otis. Use this program, code or tables */ +/* extracted from it, as desired without restriction. */ +/* */ +/* 32 Bit Reflected CRC table generation for SCTP. */ +/* To accommodate serial byte data being shifted out least */ +/* significant bit first, the table's 32 bit words are reflected */ +/* which flips both byte and bit MS and LS positions. The CRC */ +/* is calculated MS bits first from the perspective of the serial*/ +/* stream. The x^32 term is implied and the x^0 term may also */ +/* be shown as +1. The polynomial code used is 0x1EDC6F41. */ +/* Castagnoli93 */ +/* x^32+x^28+x^27+x^26+x^25+x^23+x^22+x^20+x^19+x^18+x^14+x^13+ */ +/* x^11+x^10+x^9+x^8+x^6+x^0 */ +/* Guy Castagnoli Stefan Braeuer and Martin Herrman */ +/* "Optimization of Cyclic Redundancy-Check Codes */ +/* with 24 and 32 Parity Bits", */ +/* IEEE Transactions on Communications, Vol.41, No.6, June 1993 */ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +unsigned long sctp_crc_c[256] = { + 0x00000000L, 0xF26B8303L, 0xE13B70F7L, 0x1350F3F4L, + 0xC79A971FL, 0x35F1141CL, 0x26A1E7E8L, 0xD4CA64EBL, + 0x8AD958CFL, 0x78B2DBCCL, 0x6BE22838L, 0x9989AB3BL, + 0x4D43CFD0L, 0xBF284CD3L, 0xAC78BF27L, 0x5E133C24L, + 0x105EC76FL, 0xE235446CL, 0xF165B798L, 0x030E349BL, + 0xD7C45070L, 0x25AFD373L, 0x36FF2087L, 0xC494A384L, + 0x9A879FA0L, 0x68EC1CA3L, 0x7BBCEF57L, 0x89D76C54L, + 0x5D1D08BFL, 0xAF768BBCL, 0xBC267848L, 0x4E4DFB4BL, + 0x20BD8EDEL, 0xD2D60DDDL, 0xC186FE29L, 0x33ED7D2AL, + 0xE72719C1L, 0x154C9AC2L, 0x061C6936L, 0xF477EA35L, + 0xAA64D611L, 0x580F5512L, 0x4B5FA6E6L, 0xB93425E5L, + 0x6DFE410EL, 0x9F95C20DL, 0x8CC531F9L, 0x7EAEB2FAL, + 0x30E349B1L, 0xC288CAB2L, 0xD1D83946L, 0x23B3BA45L, + 0xF779DEAEL, 0x05125DADL, 0x1642AE59L, 0xE4292D5AL, + 0xBA3A117EL, 0x4851927DL, 0x5B016189L, 0xA96AE28AL, + 0x7DA08661L, 0x8FCB0562L, 0x9C9BF696L, 0x6EF07595L, + 0x417B1DBCL, 0xB3109EBFL, 0xA0406D4BL, 0x522BEE48L, + 0x86E18AA3L, 0x748A09A0L, 0x67DAFA54L, 0x95B17957L, + 0xCBA24573L, 0x39C9C670L, 0x2A993584L, 0xD8F2B687L, + 0x0C38D26CL, 0xFE53516FL, 0xED03A29BL, 0x1F682198L, + 0x5125DAD3L, 0xA34E59D0L, 0xB01EAA24L, 0x42752927L, + 0x96BF4DCCL, 0x64D4CECFL, 0x77843D3BL, 0x85EFBE38L, + 0xDBFC821CL, 0x2997011FL, 0x3AC7F2EBL, 0xC8AC71E8L, + 0x1C661503L, 0xEE0D9600L, 0xFD5D65F4L, 0x0F36E6F7L, + 0x61C69362L, 0x93AD1061L, 0x80FDE395L, 0x72966096L, + 0xA65C047DL, 0x5437877EL, 0x4767748AL, 0xB50CF789L, + 0xEB1FCBADL, 0x197448AEL, 0x0A24BB5AL, 0xF84F3859L, + 0x2C855CB2L, 0xDEEEDFB1L, 0xCDBE2C45L, 0x3FD5AF46L, + 0x7198540DL, 0x83F3D70EL, 0x90A324FAL, 0x62C8A7F9L, + 0xB602C312L, 0x44694011L, 0x5739B3E5L, 0xA55230E6L, + 0xFB410CC2L, 0x092A8FC1L, 0x1A7A7C35L, 0xE811FF36L, + 0x3CDB9BDDL, 0xCEB018DEL, 0xDDE0EB2AL, 0x2F8B6829L, + 0x82F63B78L, 0x709DB87BL, 0x63CD4B8FL, 0x91A6C88CL, + 0x456CAC67L, 0xB7072F64L, 0xA457DC90L, 0x563C5F93L, + 0x082F63B7L, 0xFA44E0B4L, 0xE9141340L, 0x1B7F9043L, + 0xCFB5F4A8L, 0x3DDE77ABL, 0x2E8E845FL, 0xDCE5075CL, + 0x92A8FC17L, 0x60C37F14L, 0x73938CE0L, 0x81F80FE3L, + 0x55326B08L, 0xA759E80BL, 0xB4091BFFL, 0x466298FCL, + 0x1871A4D8L, 0xEA1A27DBL, 0xF94AD42FL, 0x0B21572CL, + 0xDFEB33C7L, 0x2D80B0C4L, 0x3ED04330L, 0xCCBBC033L, + 0xA24BB5A6L, 0x502036A5L, 0x4370C551L, 0xB11B4652L, + 0x65D122B9L, 0x97BAA1BAL, 0x84EA524EL, 0x7681D14DL, + 0x2892ED69L, 0xDAF96E6AL, 0xC9A99D9EL, 0x3BC21E9DL, + 0xEF087A76L, 0x1D63F975L, 0x0E330A81L, 0xFC588982L, + 0xB21572C9L, 0x407EF1CAL, 0x532E023EL, 0xA145813DL, + 0x758FE5D6L, 0x87E466D5L, 0x94B49521L, 0x66DF1622L, + 0x38CC2A06L, 0xCAA7A905L, 0xD9F75AF1L, 0x2B9CD9F2L, + 0xFF56BD19L, 0x0D3D3E1AL, 0x1E6DCDEEL, 0xEC064EEDL, + 0xC38D26C4L, 0x31E6A5C7L, 0x22B65633L, 0xD0DDD530L, + 0x0417B1DBL, 0xF67C32D8L, 0xE52CC12CL, 0x1747422FL, + 0x49547E0BL, 0xBB3FFD08L, 0xA86F0EFCL, 0x5A048DFFL, + 0x8ECEE914L, 0x7CA56A17L, 0x6FF599E3L, 0x9D9E1AE0L, + 0xD3D3E1ABL, 0x21B862A8L, 0x32E8915CL, 0xC083125FL, + 0x144976B4L, 0xE622F5B7L, 0xF5720643L, 0x07198540L, + 0x590AB964L, 0xAB613A67L, 0xB831C993L, 0x4A5A4A90L, + 0x9E902E7BL, 0x6CFBAD78L, 0x7FAB5E8CL, 0x8DC0DD8FL, + 0xE330A81AL, 0x115B2B19L, 0x020BD8EDL, 0xF0605BEEL, + 0x24AA3F05L, 0xD6C1BC06L, 0xC5914FF2L, 0x37FACCF1L, + 0x69E9F0D5L, 0x9B8273D6L, 0x88D28022L, 0x7AB90321L, + 0xAE7367CAL, 0x5C18E4C9L, 0x4F48173DL, 0xBD23943EL, + 0xF36E6F75L, 0x0105EC76L, 0x12551F82L, 0xE03E9C81L, + 0x34F4F86AL, 0xC69F7B69L, 0xD5CF889DL, 0x27A40B9EL, + 0x79B737BAL, 0x8BDCB4B9L, 0x988C474DL, 0x6AE7C44EL, + 0xBE2DA0A5L, 0x4C4623A6L, 0x5F16D052L, 0xAD7D5351L, +}; + +u_int32_t +update_crc32(u_int32_t crc32, + unsigned char *buffer, + unsigned int length) +{ + unsigned int i; + + for (i = 0; i < length; i++) { + SCTP_CRC32C(crc32, buffer[i]); + } + return (crc32); +} + + +u_int32_t +sctp_csum_finalize(u_int32_t crc32) +{ + u_int32_t result; +#if BYTE_ORDER == BIG_ENDIAN + u_int8_t byte0, byte1, byte2, byte3; +#endif + /* Complement the result */ + result = ~crc32; +#if BYTE_ORDER == BIG_ENDIAN + /* + * For BIG-ENDIAN.. aka Motorola byte order the result is in + * little-endian form. So we must manually swap the bytes. Then + * we can call htonl() which does nothing... + */ + byte0 = result & 0x000000ff; + byte1 = (result >> 8) & 0x000000ff; + byte2 = (result >> 16) & 0x000000ff; + byte3 = (result >> 24) & 0x000000ff; + result = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3); + crc32 = htonl(result); +#else + /* + * For INTEL platforms the result comes out in network order. + * No htonl is required or the swap above. So we optimize out + * both the htonl and the manual swap above. + */ + crc32 = result; +#endif + return (crc32); +} + +#endif diff --git a/sys/netinet/sctp_crc32.h b/sys/netinet/sctp_crc32.h new file mode 100644 index 0000000000..e38703e631 --- /dev/null +++ b/sys/netinet/sctp_crc32.h @@ -0,0 +1,49 @@ +/* $KAME: sctp_crc32.h,v 1.4 2003/11/25 06:40:52 ono Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_crc32.h,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +#ifndef __crc32c_h__ +#define __crc32c_h__ + +/* + * Copyright (c) 2001, 2002, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include + +#ifndef SCTP_USE_ADLER32 +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) +u_int32_t update_crc32(u_int32_t, unsigned char *, unsigned int); + +u_int32_t sctp_csum_finalize(u_int32_t); + +#endif /* _KERNEL */ +#endif /* !SCTP_USE_ADLER32 */ +#endif /* __crc32c_h__ */ diff --git a/sys/netinet/sctp_hashdriver.c b/sys/netinet/sctp_hashdriver.c new file mode 100644 index 0000000000..4ff817906b --- /dev/null +++ b/sys/netinet/sctp_hashdriver.c @@ -0,0 +1,262 @@ +/* $KAME: sctp_hashdriver.c,v 1.5 2004/01/19 09:48:26 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_hashdriver.c,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_MD5 +#include +#else +#include +#endif /* USE_MD5 */ +#include + +/* + * Main driver for SCTP's hashing. + * passing a two pointers and two lengths, returning a digest pointer + * filled. The md5 code was taken directly from the RFC (2104) so to + * understand it you may want to go look at the RFC referenced in the + * SCTP spec. We did modify this code to either user OURs implementation + * of SLA1 or the MD5 that comes from its RFC. SLA1 may have IPR issues + * so you need to check in to this if you wish to use it... Or at least + * that is what the FIP-180.1 web page says. + */ + +void sctp_hash_digest(char *key, int key_len, char *text, int text_len, + unsigned char *digest) +{ +#ifdef USE_MD5 + md5_ctxt context; +#else + struct sha1_context context; +#endif /* USE_MD5 */ + /* inner padding - key XORd with ipad */ + unsigned char k_ipad[65]; + /* outer padding - key XORd with opad */ + unsigned char k_opad[65]; + unsigned char tk[20]; + int i; + + if (key_len > 64) { +#ifdef USE_MD5 + md5_ctxt tctx; + MD5Init(&tctx); + MD5Update(&tctx, key, key_len); + MD5Final(tk, &tctx); + key = tk; + key_len = 16; +#else + struct sha1_context tctx; + SHA1_Init(&tctx); + SHA1_Process(&tctx, key, key_len); + SHA1_Final(&tctx, tk); + key = tk; + key_len = 20; +#endif /* USE_MD5 */ + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + bzero(k_ipad, sizeof k_ipad); + bzero(k_opad, sizeof k_opad); + bcopy(key, k_ipad, key_len); + bcopy(key, k_opad, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + /* + * perform inner MD5 + */ +#ifdef USE_MD5 + MD5Init(&context); /* init context for 1st pass */ + MD5Update(&context, k_ipad, 64); /* start with inner pad */ + MD5Update(&context, text, text_len); /* then text of datagram */ + MD5Final(digest, &context); /* finish up 1st pass */ +#else + SHA1_Init(&context); /* init context for 1st pass */ + SHA1_Process(&context, k_ipad, 64); /* start with inner pad */ + SHA1_Process(&context, text, text_len); /* then text of datagram */ + SHA1_Final(&context, digest); /* finish up 1st pass */ +#endif /* USE_MD5 */ + + /* + * perform outer MD5 + */ +#ifdef USE_MD5 + MD5Init(&context); /* init context for 2nd pass */ + MD5Update(&context, k_opad, 64); /* start with outer pad */ + MD5Update(&context, digest, 16); /* then results of 1st hash */ + MD5Final(digest, &context); /* finish up 2nd pass */ +#else + SHA1_Init(&context); /* init context for 2nd pass */ + SHA1_Process(&context, k_opad, 64); /* start with outer pad */ + SHA1_Process(&context, digest, 20); /* then results of 1st hash */ + SHA1_Final(&context, digest); /* finish up 2nd pass */ +#endif /* USE_MD5 */ +} + +void sctp_hash_digest_m(char *key, int key_len, struct mbuf *m, int offset, + unsigned char *digest) +{ + struct mbuf *m_at; +#ifdef USE_MD5 + md5_ctxt context; +#else + struct sha1_context context; +#endif /* USE_MD5 */ + /* inner padding - key XORd with ipad */ + unsigned char k_ipad[65]; + /* outer padding - key XORd with opad */ + unsigned char k_opad[65]; + unsigned char tk[20]; + int i; + + if (key_len > 64) { +#ifdef USE_MD5 + md5_ctxt tctx; + MD5Init(&tctx); + MD5Update(&tctx, key, key_len); + MD5Final(tk, &tctx); + key = tk; + key_len = 16; +#else + struct sha1_context tctx; + SHA1_Init(&tctx); + SHA1_Process(&tctx, key, key_len); + SHA1_Final(&tctx, tk); + key = tk; + key_len = 20; +#endif /* USE_MD5 */ + } + + /* + * the HMAC_MD5 transform looks like: + * + * MD5(K XOR opad, MD5(K XOR ipad, text)) + * + * where K is an n byte key + * ipad is the byte 0x36 repeated 64 times + * opad is the byte 0x5c repeated 64 times + * and text is the data being protected + */ + + /* start out by storing key in pads */ + bzero(k_ipad, sizeof k_ipad); + bzero(k_opad, sizeof k_opad); + bcopy(key, k_ipad, key_len); + bcopy(key, k_opad, key_len); + + /* XOR key with ipad and opad values */ + for (i = 0; i < 64; i++) { + k_ipad[i] ^= 0x36; + k_opad[i] ^= 0x5c; + } + + /* find the correct mbuf and offset into mbuf */ + m_at = m; + while ((m_at != NULL) && (offset > m_at->m_len)) { + offset -= m_at->m_len; /* update remaining offset left */ + m_at = m_at->m_next; + } + /* + * perform inner MD5 + */ +#ifdef USE_MD5 + MD5Init(&context); /* init context for 1st pass */ + MD5Update(&context, k_ipad, 64); /* start with inner pad */ + /******/ + while (m_at != NULL) { + /* then text of datagram... */ + MD5Update(&context, mtod(m_at, char *)+offset, + m_at->m_len-offset); + /* only offset on the first mbuf */ + offset = 0; + m_at = m_at->m_next; + } + /******/ + MD5Final(digest, &context); /* finish up 1st pass */ +#else + SHA1_Init(&context); /* init context for 1st pass */ + SHA1_Process(&context, k_ipad, 64); /* start with inner pad */ + /******/ + while (m_at != NULL) { + /* then text of datagram */ + SHA1_Process(&context, mtod(m_at, char *)+offset, + m_at->m_len-offset); + /* only offset on the first mbuf */ + offset = 0; + m_at = m_at->m_next; + } + /******/ + SHA1_Final(&context, digest); /* finish up 1st pass */ +#endif /* USE_MD5 */ + + /* + * perform outer MD5 + */ +#ifdef USE_MD5 + MD5Init(&context); /* init context for 2nd pass */ + MD5Update(&context, k_opad, 64); /* start with outer pad */ + MD5Update(&context, digest, 16); /* then results of 1st hash */ + MD5Final(digest, &context); /* finish up 2nd pass */ +#else + SHA1_Init(&context); /* init context for 2nd pass */ + SHA1_Process(&context, k_opad, 64); /* start with outer pad */ + SHA1_Process(&context, digest, 20); /* then results of 1st hash */ + SHA1_Final(&context, digest); /* finish up 2nd pass */ +#endif /* USE_MD5 */ +} diff --git a/sys/netinet/sctp_hashdriver.h b/sys/netinet/sctp_hashdriver.h new file mode 100644 index 0000000000..412f041d7e --- /dev/null +++ b/sys/netinet/sctp_hashdriver.h @@ -0,0 +1,43 @@ +/* $KAME: sctp_hashdriver.h,v 1.3 2002/10/09 18:01:21 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_hashdriver.h,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +#ifndef __sctp_hashdriver_h__ +#define __sctp_hashdriver_h__ + +/* + * Copyright (c) 2001, 2002 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +void sctp_hash_digest(char *, int, char *, int, unsigned char *); + +void sctp_hash_digest_m(char *, int, struct mbuf *, int, unsigned char *); + +#endif diff --git a/sys/netinet/sctp_header.h b/sys/netinet/sctp_header.h new file mode 100644 index 0000000000..ddc26794d6 --- /dev/null +++ b/sys/netinet/sctp_header.h @@ -0,0 +1,489 @@ +/* $KAME: sctp_header.h,v 1.13 2004/08/17 04:06:16 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_header.h,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +#ifndef __sctp_header_h__ +#define __sctp_header_h__ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include + +/* + * Parameter structures + */ +struct sctp_ipv4addr_param { + struct sctp_paramhdr ph; /* type=SCTP_IPV4_PARAM_TYPE, len=8 */ + u_int32_t addr; /* IPV4 address */ +}; + +struct sctp_ipv6addr_param { + struct sctp_paramhdr ph; /* type=SCTP_IPV6_PARAM_TYPE, len=20 */ + u_int8_t addr[16]; /* IPV6 address */ +}; + +/* Cookie Preservative */ +struct sctp_cookie_perserve_param { + struct sctp_paramhdr ph; /* type=SCTP_COOKIE_PRESERVE, len=8 */ + u_int32_t time; /* time in ms to extend cookie */ +}; + +/* Host Name Address */ +struct sctp_host_name_param { + struct sctp_paramhdr ph; /* type=SCTP_HOSTNAME_ADDRESS */ + char name[1]; /* host name */ +}; + +/* supported address type */ +struct sctp_supported_addr_param { + struct sctp_paramhdr ph; /* type=SCTP_SUPPORTED_ADDRTYPE */ + u_int16_t addr_type[1]; /* array of supported address types */ +}; + +/* ECN parameter */ +struct sctp_ecn_supported_param { + struct sctp_paramhdr ph; /* type=SCTP_ECN_CAPABLE */ +}; + + +/* heartbeat info parameter */ +struct sctp_heartbeat_info_param { + struct sctp_paramhdr ph; + u_int32_t time_value_1; + u_int32_t time_value_2; + u_int32_t random_value1; + u_int32_t random_value2; + u_int16_t user_req; + u_int8_t addr_family; + u_int8_t addr_len; + char address[SCTP_ADDRMAX]; +}; + + +/* draft-ietf-tsvwg-prsctp */ +/* PR-SCTP supported parameter */ +struct sctp_prsctp_supported_param { + struct sctp_paramhdr ph; +}; + + +/* draft-ietf-tsvwg-addip-sctp */ +struct sctp_asconf_paramhdr { /* an ASCONF "parameter" */ + struct sctp_paramhdr ph; /* a SCTP parameter header */ + u_int32_t correlation_id; /* correlation id for this param */ +}; + +struct sctp_asconf_addr_param { /* an ASCONF address parameter */ + struct sctp_asconf_paramhdr aph; /* asconf "parameter" */ + struct sctp_ipv6addr_param addrp; /* max storage size */ +}; + +struct sctp_asconf_addrv4_param { /* an ASCONF address (v4) parameter */ + struct sctp_asconf_paramhdr aph; /* asconf "parameter" */ + struct sctp_ipv4addr_param addrp; /* max storage size */ +}; + + +/* ECN Nonce: draft-ladha-sctp-ecn-nonce */ +struct sctp_ecn_nonce_supported_param { + struct sctp_paramhdr ph; /* type = 0x8001 len = 4 */ +}; + +struct sctp_supported_chunk_types_param { + struct sctp_paramhdr ph; /* type = 0x8002 len = x */ + u_int8_t chunk_types[0]; +}; + +/* + * Structures for DATA chunks + */ +struct sctp_data { + u_int32_t tsn; + u_int16_t stream_id; + u_int16_t stream_sequence; + u_int32_t protocol_id; + /* user data follows */ +}; + +struct sctp_data_chunk { + struct sctp_chunkhdr ch; + struct sctp_data dp; +}; + +/* + * Structures for the control chunks + */ + +/* Initiate (INIT)/Initiate Ack (INIT ACK) */ +struct sctp_init { + u_int32_t initiate_tag; /* initiate tag */ + u_int32_t a_rwnd; /* a_rwnd */ + u_int16_t num_outbound_streams; /* OS */ + u_int16_t num_inbound_streams; /* MIS */ + u_int32_t initial_tsn; /* I-TSN */ + /* optional param's follow */ +}; + +/* state cookie header */ +struct sctp_state_cookie { /* this is our definition... */ + u_int8_t identification[16]; /* id of who we are */ + u_int32_t cookie_life; /* life I will award this cookie */ + u_int32_t tie_tag_my_vtag; /* my tag in old association */ + u_int32_t tie_tag_peer_vtag; /* peers tag in old association */ + u_int32_t peers_vtag; /* peers tag in INIT (for quick ref) */ + u_int32_t my_vtag; /* my tag in INIT-ACK (for quick ref) */ + struct timeval time_entered; /* the time I built cookie */ + u_int32_t address[4]; /* 4 ints/128 bits */ + u_int32_t addr_type; /* address type */ + u_int32_t laddress[4]; /* my local from address */ + u_int32_t laddr_type; /* my local from address type */ + u_int32_t scope_id; /* v6 scope id for link-locals */ + u_int16_t peerport; /* port address of the peer in the INIT */ + u_int16_t myport; /* my port address used in the INIT */ + u_int8_t ipv4_addr_legal; /* Are V4 addr legal? */ + u_int8_t ipv6_addr_legal; /* Are V6 addr legal? */ + u_int8_t local_scope; /* IPv6 local scope flag */ + u_int8_t site_scope; /* IPv6 site scope flag */ + u_int8_t ipv4_scope; /* IPv4 private addr scope */ + u_int8_t loopback_scope; /* loopback scope information */ + u_int16_t reserved; + /* + * at the end is tacked on the INIT chunk and the + * INIT-ACK chunk (minus the cookie). + */ +}; + +struct sctp_inv_mandatory_param { + u_int16_t cause; + u_int16_t length; + u_int32_t num_param; + u_int16_t param; + /* + * We include this to 0 it since only a missing cookie + * will cause this error. + */ + u_int16_t resv; +}; + +struct sctp_unresolv_addr { + u_int16_t cause; + u_int16_t length; + u_int16_t addr_type; + u_int16_t reserved; /* Only one invalid addr type */ +}; + +/* state cookie parameter */ +struct sctp_state_cookie_param { + struct sctp_paramhdr ph; + struct sctp_state_cookie cookie; +}; + +struct sctp_init_chunk { + struct sctp_chunkhdr ch; + struct sctp_init init; +}; + +struct sctp_init_msg { + struct sctphdr sh; + struct sctp_init_chunk msg; +}; +/* ... used for both INIT and INIT ACK */ +#define sctp_init_ack sctp_init +#define sctp_init_ack_chunk sctp_init_chunk +#define sctp_init_ack_msg sctp_init_msg + + +/* Selective Ack (SACK) */ +struct sctp_gap_ack_block { + u_int16_t start; /* Gap Ack block start */ + u_int16_t end; /* Gap Ack block end */ +}; + +struct sctp_sack { + u_int32_t cum_tsn_ack; /* cumulative TSN Ack */ + u_int32_t a_rwnd; /* updated a_rwnd of sender */ + u_int16_t num_gap_ack_blks; /* number of Gap Ack blocks */ + u_int16_t num_dup_tsns; /* number of duplicate TSNs */ + /* struct sctp_gap_ack_block's follow */ + /* u_int32_t duplicate_tsn's follow */ +}; + +struct sctp_sack_chunk { + struct sctp_chunkhdr ch; + struct sctp_sack sack; +}; + + +/* Heartbeat Request (HEARTBEAT) */ +struct sctp_heartbeat { + struct sctp_heartbeat_info_param hb_info; +}; + +struct sctp_heartbeat_chunk { + struct sctp_chunkhdr ch; + struct sctp_heartbeat heartbeat; +}; +/* ... used for Heartbeat Ack (HEARTBEAT ACK) */ +#define sctp_heartbeat_ack sctp_heartbeat +#define sctp_heartbeat_ack_chunk sctp_heartbeat_chunk + + +/* Abort Asssociation (ABORT) */ +struct sctp_abort_chunk { + struct sctp_chunkhdr ch; + /* optional error cause may follow */ +}; + +struct sctp_abort_msg { + struct sctphdr sh; + struct sctp_abort_chunk msg; +}; + + +/* Shutdown Association (SHUTDOWN) */ +struct sctp_shutdown_chunk { + struct sctp_chunkhdr ch; + u_int32_t cumulative_tsn_ack; +}; + + +/* Shutdown Acknowledgment (SHUTDOWN ACK) */ +struct sctp_shutdown_ack_chunk { + struct sctp_chunkhdr ch; +}; + + +/* Operation Error (ERROR) */ +struct sctp_error_chunk { + struct sctp_chunkhdr ch; + /* optional error causes follow */ +}; + + +/* Cookie Echo (COOKIE ECHO) */ +struct sctp_cookie_echo_chunk { + struct sctp_chunkhdr ch; + struct sctp_state_cookie cookie; +}; + +/* Cookie Acknowledgment (COOKIE ACK) */ +struct sctp_cookie_ack_chunk { + struct sctp_chunkhdr ch; +}; + +/* Explicit Congestion Notification Echo (ECNE) */ +struct sctp_ecne_chunk { + struct sctp_chunkhdr ch; + u_int32_t tsn; +}; + +/* Congestion Window Reduced (CWR) */ +struct sctp_cwr_chunk { + struct sctp_chunkhdr ch; + u_int32_t tsn; +}; + +/* Shutdown Complete (SHUTDOWN COMPLETE) */ +struct sctp_shutdown_complete_chunk { + struct sctp_chunkhdr ch; +}; + +/* Oper error holding a stale cookie */ +struct sctp_stale_cookie_msg { + struct sctp_paramhdr ph; /* really an error cause */ + u_int32_t time_usec; +}; + +struct sctp_adaption_layer_indication { + struct sctp_paramhdr ph; + u_int32_t indication; +}; + +struct sctp_cookie_while_shutting_down { + struct sctphdr sh; + struct sctp_chunkhdr ch; + struct sctp_paramhdr ph; /* really an error cause */ +}; + +struct sctp_shutdown_complete_msg { + struct sctphdr sh; + struct sctp_shutdown_complete_chunk shut_cmp; +}; + +/* draft-ietf-tsvwg-addip-sctp */ +/* Address/Stream Configuration Change (ASCONF) */ +struct sctp_asconf_chunk { + struct sctp_chunkhdr ch; + u_int32_t serial_number; + /* lookup address parameter (mandatory) */ + /* asconf parameters follow */ +}; + +/* Address/Stream Configuration Acknowledge (ASCONF ACK) */ +struct sctp_asconf_ack_chunk { + struct sctp_chunkhdr ch; + u_int32_t serial_number; + /* asconf parameters follow */ +}; + +/* draft-ietf-tsvwg-prsctp */ +/* Forward Cumulative TSN (FORWARD TSN) */ +struct sctp_forward_tsn_chunk { + struct sctp_chunkhdr ch; + u_int32_t new_cumulative_tsn; + /* stream/sequence pairs (sctp_strseq) follow */ +}; + +struct sctp_strseq { + u_int16_t stream; + u_int16_t sequence; +}; + +struct sctp_forward_tsn_msg { + struct sctphdr sh; + struct sctp_forward_tsn_chunk msg; +}; + +/* should be a multiple of 4 - 1 aka 3/7/11 etc. */ + +#define SCTP_NUM_DB_TO_VERIFY 3 + +struct sctp_chunk_desc { + u_int8_t chunk_type; + u_int8_t data_bytes[SCTP_NUM_DB_TO_VERIFY]; + u_int32_t tsn_ifany; +}; + + +struct sctp_pktdrop_chunk { + struct sctp_chunkhdr ch; + u_int32_t bottle_bw; + u_int32_t current_onq; + u_int16_t trunc_len; + u_int16_t reserved; + u_int8_t data[0]; +}; + +#define SCTP_RESET_YOUR 0x01 /* reset your streams and send response */ +#define SCTP_RESET_ALL 0x02 /* reset all of your streams */ +#define SCTP_RECIPRICAL 0x04 /* reset my streams too */ + +struct sctp_stream_reset_request { + struct sctp_paramhdr ph; + u_int8_t reset_flags; /* actual request */ + u_int8_t reset_pad[3]; + u_int32_t reset_req_seq; /* monotonically increasing seq no */ + u_int16_t list_of_streams[0]; /* if not all list of streams */ +}; + +#define SCTP_RESET_PERFORMED 0x01 /* Peers sending str was reset */ +#define SCTP_RESET_DENIED 0x02 /* Asked for but refused */ + +struct sctp_stream_reset_response { + struct sctp_paramhdr ph; + u_int8_t reset_flags; /* actual request */ + u_int8_t reset_pad[3]; + u_int32_t reset_req_seq_resp; /* copied from reset_req reset_req_seq */ + u_int32_t reset_at_tsn; /* resetters next TSN to be assigned send wise */ + u_int32_t cumulative_tsn; /* resetters cum-ack point receive wise */ + u_int16_t list_of_streams[0]; /* if not all list of streams */ +}; + +/* convience structures, note that if you + * are making a request for specific streams + * then the request will need to be an overlay + * structure. + */ + +struct sctp_stream_reset_req { + struct sctp_chunkhdr ch; + struct sctp_stream_reset_request sr_req; +}; + +struct sctp_stream_reset_resp { + struct sctp_chunkhdr ch; + struct sctp_stream_reset_response sr_resp; +}; + + +/* + * we pre-reserve enough room for a ECNE or CWR AND a SACK with no + * missing pieces. If ENCE is missing we could have a couple of blocks. + * This way we optimize so we MOST likely can bundle a SACK/ECN with + * the smallest size data chunk I will split into. We could increase + * throughput slightly by taking out these two but the 24-sack/8-CWR + * i.e. 32 bytes I pre-reserve I feel is worth it for now. + */ +#ifndef SCTP_MAX_OVERHEAD +#ifdef AF_INET6 +#define SCTP_MAX_OVERHEAD (sizeof(struct sctp_data_chunk) + \ + sizeof(struct sctphdr) + \ + sizeof(struct sctp_ecne_chunk) + \ + sizeof(struct sctp_sack_chunk) + \ + sizeof(struct ip6_hdr)) + +#define SCTP_MED_OVERHEAD (sizeof(struct sctp_data_chunk) + \ + sizeof(struct sctphdr) + \ + sizeof(struct ip6_hdr)) + + +#define SCTP_MIN_OVERHEAD (sizeof(struct ip6_hdr) + \ + sizeof(struct sctphdr)) + +#else +#define SCTP_MAX_OVERHEAD (sizeof(struct sctp_data_chunk) + \ + sizeof(struct sctphdr) + \ + sizeof(struct sctp_ecne_chunk) + \ + sizeof(struct sctp_sack_chunk) + \ + sizeof(struct ip)) + +#define SCTP_MED_OVERHEAD (sizeof(struct sctp_data_chunk) + \ + sizeof(struct sctphdr) + \ + sizeof(struct ip)) + + +#define SCTP_MIN_OVERHEAD (sizeof(struct ip) + \ + sizeof(struct sctphdr)) + +#endif /* AF_INET6 */ +#endif /* !SCTP_MAX_OVERHEAD */ + +#define SCTP_MED_V4_OVERHEAD (sizeof(struct sctp_data_chunk) + \ + sizeof(struct sctphdr) + \ + sizeof(struct ip)) + +#define SCTP_MIN_V4_OVERHEAD (sizeof(struct ip) + \ + sizeof(struct sctphdr)) + +#endif /* !__sctp_header_h__ */ diff --git a/sys/netinet/sctp_indata.c b/sys/netinet/sctp_indata.c new file mode 100644 index 0000000000..776557d25e --- /dev/null +++ b/sys/netinet/sctp_indata.c @@ -0,0 +1,4751 @@ +/* $KAME: sctp_indata.c,v 1.35 2004/08/17 04:06:17 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_indata.c,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +/* + * Copyright (C) 2002, 2003, 2004 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if !(defined(__OpenBSD__) || defined(__APPLE__)) +#include "opt_ipsec.h" +#endif +#if defined(__FreeBSD__) +#include "opt_inet6.h" +#include "opt_inet.h" +#endif +#if defined(__NetBSD__) +#include "opt_inet.h" +#endif + +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +#include +#else +#include +#endif +#include + +#include +#include +#include +#ifdef INET6 +#include +#endif /* INET6 */ +#include +#include +#include +#ifdef INET6 +#include +#endif /* INET6 */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef IPSEC +#ifndef __OpenBSD__ +#include +#include +#else +#undef IPSEC +#endif +#endif /*IPSEC*/ + +#include + +#ifdef SCTP_DEBUG +extern u_int32_t sctp_debug_on; +#endif + +/* + * NOTES: On the outbound side of things I need to check the sack timer to + * see if I should generate a sack into the chunk queue (if I have data to + * send that is and will be sending it .. for bundling. + * + * The callback in sctp_usrreq.c will get called when the socket is read + * from. This will cause sctp_service_queues() to get called on the top + * entry in the list. + */ + +extern int sctp_strict_sacks; + +void +sctp_set_rwnd(struct sctp_tcb *stcb, struct sctp_association *asoc) +{ + u_int32_t calc, calc_w_oh; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA4) { + printf("cc:%lu hiwat:%lu lowat:%lu mbcnt:%lu mbmax:%lu\n", + (u_long)stcb->sctp_socket->so_rcv.sb_cc, + (u_long)stcb->sctp_socket->so_rcv.sb_hiwat, + (u_long)stcb->sctp_socket->so_rcv.sb_lowat, + (u_long)stcb->sctp_socket->so_rcv.sb_mbcnt, + (u_long)stcb->sctp_socket->so_rcv.sb_mbmax); + printf("Setting rwnd to: sb:%ld - (del:%d + reasm:%d str:%d)\n", + sctp_sbspace(&stcb->sctp_socket->so_rcv), + asoc->size_on_delivery_queue, + asoc->size_on_reasm_queue, + asoc->size_on_all_streams); + } +#endif + if (stcb->sctp_socket->so_rcv.sb_cc == 0 && + asoc->size_on_delivery_queue == 0 && + asoc->size_on_reasm_queue == 0 && + asoc->size_on_all_streams == 0) { + /* Full rwnd granted */ + asoc->my_rwnd = max(stcb->sctp_socket->so_rcv.sb_hiwat, + SCTP_MINIMAL_RWND); + return; + } + /* get actual space */ + calc = (u_int32_t)sctp_sbspace(&stcb->sctp_socket->so_rcv); + + /* take out what has NOT been put on socket queue and + * we yet hold for putting up. + */ + calc = sctp_sbspace_sub(calc, (u_int32_t)asoc->size_on_delivery_queue); + calc = sctp_sbspace_sub(calc, (u_int32_t)asoc->size_on_reasm_queue); + calc = sctp_sbspace_sub(calc, (u_int32_t)asoc->size_on_all_streams); + + /* what is the overhead of all these rwnd's */ + calc_w_oh = sctp_sbspace_sub(calc, stcb->asoc.my_rwnd_control_len); + + asoc->my_rwnd = calc; + if (calc_w_oh == 0) { + /* If our overhead is greater than the advertised + * rwnd, we clamp the rwnd to 1. This lets us + * still accept inbound segments, but hopefully will + * shut the sender down when he finally gets the message. + */ + asoc->my_rwnd = 1; + } else { + /* SWS threshold */ + if (asoc->my_rwnd && + (asoc->my_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_receiver)) { + /* SWS engaged, tell peer none left */ + asoc->my_rwnd = 1; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA4) { + printf(" - SWS zeros\n"); + } + } else { + if (sctp_debug_on & SCTP_DEBUG_INDATA4) { + printf("\n"); + } +#endif + } + } +} + +/* + * Take a chk structure and build it into an mbuf. Hmm should we change things + * so that instead we store the data side in a chunk? + */ +static struct mbuf * +sctp_build_ctl_nchunk(struct sctp_tcb *stcb, uint32_t tsn, uint32_t ppid, + uint32_t context, uint16_t stream_no, uint16_t stream_seq, uint8_t flags) +{ + struct sctp_sndrcvinfo *outinfo; + struct cmsghdr *cmh; + struct mbuf *ret; + + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_RECVDATAIOEVNT) == 0) { + /* user does not want the sndrcv ctl */ + return (NULL); + } + + MGETHDR(ret, M_DONTWAIT, MT_CONTROL); + if (ret == NULL) { + /* No space */ + return (ret); + } + /* We need a CMSG header followed by the struct */ + cmh = mtod(ret, struct cmsghdr *); + outinfo = (struct sctp_sndrcvinfo *)CMSG_DATA(cmh); + cmh->cmsg_level = IPPROTO_SCTP; + cmh->cmsg_type = SCTP_SNDRCV; + cmh->cmsg_len = CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); + outinfo->sinfo_stream = stream_no; + outinfo->sinfo_ssn = stream_seq; + if (flags & SCTP_DATA_UNORDERED) { + outinfo->sinfo_flags = MSG_UNORDERED; + } else { + outinfo->sinfo_flags = 0; + } + outinfo->sinfo_ppid = ppid; + outinfo->sinfo_context = context; + outinfo->sinfo_assoc_id = sctp_get_associd(stcb); + outinfo->sinfo_tsn = tsn; + outinfo->sinfo_cumtsn = stcb->asoc.cumulative_tsn; + ret->m_len = cmh->cmsg_len; + ret->m_pkthdr.len = ret->m_len; + /* + * We track how many control len's have gone upon the sb + * and do not count these in the rwnd calculation. + */ + stcb->asoc.my_rwnd_control_len += + CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); + + return (ret); +} + +/* + * Take a chk structure and build it into an mbuf. Should we change things + * so that instead we store the data side in a chunk? + */ +static +struct mbuf * +sctp_build_ctl(struct sctp_tcb *stcb, struct sctp_tmit_chunk *chk) +{ + struct sctp_sndrcvinfo *outinfo; + struct cmsghdr *cmh; + struct mbuf *ret; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_RECVDATAIOEVNT) == 0) { + /* user does not want the sndrcv ctl */ + return (NULL); + } + MGET(ret, M_DONTWAIT, MT_CONTROL); + if (ret == NULL) { + /* No space */ + return (ret); + } + + /* We need a CMSG header followed by the struct */ + cmh = mtod(ret, struct cmsghdr *); + outinfo = (struct sctp_sndrcvinfo *)CMSG_DATA(cmh); + cmh->cmsg_level = IPPROTO_SCTP; + cmh->cmsg_type = SCTP_SNDRCV; + cmh->cmsg_len = CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); + outinfo->sinfo_stream = chk->rec.data.stream_number; + outinfo->sinfo_ssn = chk->rec.data.stream_seq; + if (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) { + outinfo->sinfo_flags = MSG_UNORDERED; + } else { + outinfo->sinfo_flags = 0; + } + outinfo->sinfo_ppid = chk->rec.data.payloadtype; + outinfo->sinfo_context = chk->rec.data.context; + outinfo->sinfo_assoc_id = sctp_get_associd(stcb); + outinfo->sinfo_tsn = chk->rec.data.TSN_seq; + outinfo->sinfo_cumtsn = stcb->asoc.cumulative_tsn; + ret->m_len = cmh->cmsg_len; + stcb->asoc.my_rwnd_control_len += + CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); + + return (ret); +} + +int +sctp_deliver_data(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_tmit_chunk *chk, int hold_locks) +{ + struct mbuf *control, *m; + int free_it; + struct sockaddr_in6 sin6; + struct sockaddr *to; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("I am now in Deliver data! (%p)\n", chk); + } +#endif + /* get a write lock on the inp if not already */ + if (hold_locks == 0) { + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + } + free_it = 0; + /* We always add it to the queue */ + if (stcb && (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { + /* socket above is long gone */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("gone is gone!\n"); + } +#endif + if (chk != NULL) { + if (chk->data) + sctp_m_freem(chk->data); + chk->data = NULL; + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + } + TAILQ_FOREACH(chk, &asoc->delivery_queue, sctp_next) { + asoc->size_on_delivery_queue -= chk->send_size; + asoc->cnt_on_delivery_queue--; + /* + * Lose the data pointer, since its in the socket buffer + */ + if (chk->data) + sctp_m_freem(chk->data); + chk->data = NULL; + /* Now free the address and data */ + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + } + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return (0); + } + if (chk != NULL) { + TAILQ_INSERT_TAIL(&asoc->delivery_queue, chk, sctp_next); + asoc->size_on_delivery_queue += chk->send_size; + asoc->cnt_on_delivery_queue++; + } + if (asoc->fragmented_delivery_inprogress) { + /* + * oh oh, fragmented delivery in progress + * return out of here. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Fragmented delivery in progress?\n"); + } +#endif + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return (0); + } + /* Now grab the first one */ + chk = TAILQ_FIRST(&asoc->delivery_queue); + if (chk == NULL) { + /* Nothing in queue */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Nothing in queue?\n"); + } +#endif + asoc->size_on_delivery_queue = 0; + asoc->cnt_on_delivery_queue = 0; + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return (0); + } + + if (stcb->sctp_socket->so_rcv.sb_cc >= stcb->sctp_socket->so_rcv.sb_hiwat) { + /* Boy, there really is NO room */ + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return (0); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Now to the delivery with chk(%p)!\n", chk); + } +#endif + /* XXX need to append PKTHDR to the socket buffer first */ + if ((chk->data->m_flags & M_PKTHDR) == 0) { + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + /* no room! */ + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return (0); + } + m->m_pkthdr.len = chk->send_size; + m->m_len = 0; + m->m_next = chk->data; + chk->data = m; + } + if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) { + if (chk->data->m_next == NULL) { + /* hopefully we hit here most of the time */ + chk->data->m_flags |= M_EOR; + } else { + /* Add the flag to the LAST mbuf in the chain */ + m = chk->data; + while (m->m_next != NULL) { + m = m->m_next; + } + m->m_flags |= M_EOR; + } + } + + if (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) { + struct sockaddr_in6 lsa6; + + control = sctp_build_ctl(stcb, chk); + to = (struct sockaddr *)&chk->whoTo->ro._l_addr; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + to->sa_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)to; + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr.s6_addr16[2] = 0xffff; + bcopy(&sin->sin_addr, &sin6.sin6_addr.s6_addr16[3], + sizeof(sin6.sin6_addr.s6_addr16[3])); + sin6.sin6_port = sin->sin_port; + to = (struct sockaddr *)&sin6; + } + /* check and strip embedded scope junk */ + to = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)to, + &lsa6); + if (((struct sockaddr_in *)to)->sin_port == 0) { + printf("Huh a, port is %d not net:%x %d?\n", + ((struct sockaddr_in *)to)->sin_port, + (u_int)chk->whoTo, + (int)(ntohs(stcb->rport))); + ((struct sockaddr_in *)to)->sin_port = stcb->rport; + } + if (sctp_sbspace(&stcb->sctp_socket->so_rcv) < (long)chk->send_size) { + /* Gak not enough room */ + if (control) { + sctp_m_freem(control); + stcb->asoc.my_rwnd_control_len -= + CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); + } + goto skip; + } + if (!sbappendaddr_nocheck(&stcb->sctp_socket->so_rcv, + to, chk->data, control, stcb->asoc.my_vtag, + stcb->sctp_ep)) { + /* Gak not enough room */ + if (control) { + sctp_m_freem(control); + stcb->asoc.my_rwnd_control_len -= + CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); + } + } else { + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) { + if (sctp_add_to_socket_q(stcb->sctp_ep, stcb)) { + stcb->asoc.my_rwnd_control_len += + sizeof(struct mbuf); + } + } else { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + free_it = 1; + } + } else { + /* append to a already started message. */ + if (sctp_sbspace(&stcb->sctp_socket->so_rcv) >= + (long)chk->send_size) { + sbappend(&stcb->sctp_socket->so_rcv, chk->data); + free_it = 1; + } + } + skip: + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + /* free up the one we inserted */ + if (free_it) { + /* Pull it off the queue */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Free_it true, doing tickle wakeup\n"); + } +#endif + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); + TAILQ_REMOVE(&asoc->delivery_queue, chk, sctp_next); + asoc->size_on_delivery_queue -= chk->send_size; + asoc->cnt_on_delivery_queue--; + /* Lose the data pointer, since its in the socket buffer */ + chk->data = NULL; + /* Now free the address and data */ + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + } + return (free_it); +} + +/* + * We are delivering currently from the reassembly queue. We must continue to + * deliver until we either: + * 1) run out of space. + * 2) run out of sequential TSN's + * 3) hit the SCTP_DATA_LAST_FRAG flag. + */ +static void +sctp_service_reassembly(struct sctp_tcb *stcb, struct sctp_association *asoc, int hold_locks) +{ + struct sockaddr *to; + struct sockaddr_in6 sin6; + struct sctp_tmit_chunk *chk, *at; + struct mbuf *control, *m; + u_int16_t nxt_todel; + u_int16_t stream_no; + int cntDel; + cntDel = stream_no = 0; + if (hold_locks == 0) { + /* + * you always have the TCB lock, we need + * to have the inp write lock as well. + */ + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + } + if (stcb && (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { + /* socket above is long gone */ + asoc->fragmented_delivery_inprogress = 0; + TAILQ_FOREACH(chk, &asoc->reasmqueue, sctp_next) { + asoc->size_on_delivery_queue -= chk->send_size; + asoc->cnt_on_delivery_queue--; + /* + * Lose the data pointer, since its in the socket buffer + */ + if (chk->data) + sctp_m_freem(chk->data); + chk->data = NULL; + /* Now free the address and data */ + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + } + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + do { + if (stcb->sctp_socket->so_rcv.sb_cc >= + stcb->sctp_socket->so_rcv.sb_hiwat) { + if (cntDel) { + sctp_sorwakeup(stcb->sctp_ep, + stcb->sctp_socket); + } + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + chk = TAILQ_FIRST(&asoc->reasmqueue); + if (chk == NULL) { + if (cntDel) { + sctp_sorwakeup(stcb->sctp_ep, + stcb->sctp_socket); + } + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + if (chk->rec.data.TSN_seq != (asoc->tsn_last_delivered + 1)) { + /* Can't deliver more :< */ + if (cntDel) { + sctp_sorwakeup(stcb->sctp_ep, + stcb->sctp_socket); + } + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + stream_no = chk->rec.data.stream_number; + nxt_todel = asoc->strmin[stream_no].last_sequence_delivered + 1; + if (nxt_todel != chk->rec.data.stream_seq && + (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0) { + /* + * Not the next sequence to deliver in its stream OR + * unordered + */ + if (cntDel) { + sctp_sorwakeup(stcb->sctp_ep, + stcb->sctp_socket); + } + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + + if ((chk->data->m_flags & M_PKTHDR) == 0) { + MGETHDR(m, M_DONTWAIT, MT_DATA); + if (m == NULL) { + /* no room! */ + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + m->m_pkthdr.len = chk->send_size; + m->m_len = 0; + m->m_next = chk->data; + chk->data = m; + } + if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) { + if (chk->data->m_next == NULL) { + /* hopefully we hit here most of the time */ + chk->data->m_flags |= M_EOR; + } else { + /* Add the flag to the LAST mbuf in the chain */ + m = chk->data; + while (m->m_next != NULL) { + m = m->m_next; + } + m->m_flags |= M_EOR; + } + } + if (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) { + struct sockaddr_in6 lsa6; + + control = sctp_build_ctl(stcb, chk); + to = (struct sockaddr *)&chk->whoTo->ro._l_addr; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + to->sa_family == AF_INET) { + struct sockaddr_in *sin; + sin = (struct sockaddr_in *)to; + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr.s6_addr16[2] = 0xffff; + bcopy(&sin->sin_addr, + &sin6.sin6_addr.s6_addr16[3], + sizeof(sin6.sin6_addr.s6_addr16[3])); + sin6.sin6_port = sin->sin_port; + to = (struct sockaddr *)&sin6; + } + /* check and strip embedded scope junk */ + to = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)to, + &lsa6); + if (((struct sockaddr_in *)to)->sin_port == 0) { + printf("Huh b, port is %d not net:%x %d?\n", + ((struct sockaddr_in *)to)->sin_port, + (u_int)chk->whoTo, + (int)(ntohs(stcb->rport))); + ((struct sockaddr_in *)to)->sin_port = stcb->rport; + } + if (sctp_sbspace(&stcb->sctp_socket->so_rcv) < + (long)chk->send_size) { + if (control) { + sctp_m_freem(control); + stcb->asoc.my_rwnd_control_len -= + CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); + } + sctp_sorwakeup(stcb->sctp_ep, + stcb->sctp_socket); + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + if (!sbappendaddr_nocheck(&stcb->sctp_socket->so_rcv, + to, chk->data, control, stcb->asoc.my_vtag, + stcb->sctp_ep)) { + /* Gak not enough room */ + if (control) { + sctp_m_freem(control); + stcb->asoc.my_rwnd_control_len -= + CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); + } + sctp_sorwakeup(stcb->sctp_ep, + stcb->sctp_socket); + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) { + if (sctp_add_to_socket_q(stcb->sctp_ep, stcb)) { + stcb->asoc.my_rwnd_control_len += + sizeof(struct mbuf); + } + } else { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + cntDel++; + } else { + if (sctp_sbspace(&stcb->sctp_socket->so_rcv) >= + (long)chk->send_size) { + sbappend(&stcb->sctp_socket->so_rcv, chk->data); + cntDel++; + } else { + /* out of space in the sb */ + sctp_sorwakeup(stcb->sctp_ep, + stcb->sctp_socket); + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + } + /* pull it we did it */ + TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next); + if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) { + asoc->fragmented_delivery_inprogress = 0; + if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0) { + asoc->strmin[stream_no].last_sequence_delivered++; + } + } + asoc->tsn_last_delivered = chk->rec.data.TSN_seq; + asoc->size_on_reasm_queue -= chk->send_size; + asoc->cnt_on_reasm_queue--; + /* free up the chk */ + sctp_free_remote_addr(chk->whoTo); + chk->data = NULL; + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + if (asoc->fragmented_delivery_inprogress == 0) { + /* + * Now lets see if we can deliver the next one on the + * stream + */ + u_int16_t nxt_todel; + struct sctp_stream_in *strm; + + strm = &asoc->strmin[stream_no]; + nxt_todel = strm->last_sequence_delivered + 1; + chk = TAILQ_FIRST(&strm->inqueue); + if (chk && (nxt_todel == chk->rec.data.stream_seq)) { + while (chk != NULL) { + /* all delivered */ + if (nxt_todel == + chk->rec.data.stream_seq) { + at = TAILQ_NEXT(chk, sctp_next); + TAILQ_REMOVE(&strm->inqueue, + chk, sctp_next); + asoc->size_on_all_streams -= + chk->send_size; + asoc->cnt_on_all_streams--; + strm->last_sequence_delivered++; + /* + * We ignore the return of + * deliver_data here since we + * always can hold the chunk on + * the d-queue. And we have a + * finite number that can be + * delivered from the strq. + */ + sctp_deliver_data(stcb, asoc, chk, 1); + chk = at; + } else { + break; + } + nxt_todel = + strm->last_sequence_delivered + 1; + } + } + if (!TAILQ_EMPTY(&asoc->delivery_queue)) { + /* Here if deliver_data fails, we must break */ + if (sctp_deliver_data(stcb, asoc, NULL, 1) == 0) + break; + } + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + chk = TAILQ_FIRST(&asoc->reasmqueue); + } while (chk); + if (cntDel) { + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); + } + if (hold_locks == 0) + SCTP_INP_WUNLOCK(stcb->sctp_ep); +} + +/* + * Queue the chunk either right into the socket buffer if it is the next one + * to go OR put it in the correct place in the delivery queue. If we do + * append to the so_buf, keep doing so until we are out of order. + * One big question still remains, what to do when the socket buffer is FULL?? + */ +static void +sctp_queue_data_to_stream(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_tmit_chunk *chk, int *abort_flag) +{ + struct sctp_stream_in *strm; + struct sctp_tmit_chunk *at; + int queue_needed; + u_int16_t nxt_todel; + struct mbuf *oper; + +/*** FIX FIX FIX ??? + * Need to add code to deal with 16 bit seq wrap + * without a TSN wrap for ordered delivery (maybe). + * FIX FIX FIX ??? + */ + queue_needed = 1; + asoc->size_on_all_streams += chk->send_size; + asoc->cnt_on_all_streams++; + strm = &asoc->strmin[chk->rec.data.stream_number]; + nxt_todel = strm->last_sequence_delivered + 1; +#ifdef SCTP_STR_LOGGING + sctp_log_strm_del(chk, NULL, SCTP_STR_LOG_FROM_INTO_STRD); +#endif +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("queue to stream called for ssn:%u lastdel:%u nxt:%u\n", + (u_int)chk->rec.data.stream_seq, + (u_int)strm->last_sequence_delivered, (u_int)nxt_todel); + } +#endif + if (compare_with_wrap(strm->last_sequence_delivered, + chk->rec.data.stream_seq, MAX_SEQ) || + (strm->last_sequence_delivered == chk->rec.data.stream_seq)) { + /* The incoming sseq is behind where we last delivered? */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Duplicate S-SEQ:%d delivered:%d from peer, Abort association\n", + chk->rec.data.stream_seq, + strm->last_sequence_delivered); + } +#endif + /* + * throw it in the stream so it gets cleaned up in + * association destruction + */ + TAILQ_INSERT_HEAD(&strm->inqueue, chk, sctp_next); + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, struct sctp_paramhdr *); + ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x00000001); + } + sctp_abort_an_association(stcb->sctp_ep, stcb, + SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + return; + + } + if (nxt_todel == chk->rec.data.stream_seq) { + /* can be delivered right away */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("It's NEXT!\n"); + } +#endif +#ifdef SCTP_STR_LOGGING + sctp_log_strm_del(chk, NULL, SCTP_STR_LOG_FROM_IMMED_DEL); +#endif + queue_needed = 0; + asoc->size_on_all_streams -= chk->send_size; + asoc->cnt_on_all_streams--; + strm->last_sequence_delivered++; + sctp_deliver_data(stcb, asoc, chk, 0); + chk = TAILQ_FIRST(&strm->inqueue); + while (chk != NULL) { + /* all delivered */ + nxt_todel = strm->last_sequence_delivered + 1; + if (nxt_todel == chk->rec.data.stream_seq) { + at = TAILQ_NEXT(chk, sctp_next); + TAILQ_REMOVE(&strm->inqueue, chk, sctp_next); + asoc->size_on_all_streams -= chk->send_size; + asoc->cnt_on_all_streams--; + strm->last_sequence_delivered++; + /* + * We ignore the return of deliver_data here + * since we always can hold the chunk on the + * d-queue. And we have a finite number that + * can be delivered from the strq. + */ +#ifdef SCTP_STR_LOGGING + sctp_log_strm_del(chk, NULL, + SCTP_STR_LOG_FROM_IMMED_DEL); +#endif + sctp_deliver_data(stcb, asoc, chk, 0); + chk = at; + continue; + } + break; + } + } + if (queue_needed) { + /* + * Ok, we did not deliver this guy, find + * the correct place to put it on the queue. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Queue Needed!\n"); + } +#endif + if (TAILQ_EMPTY(&strm->inqueue)) { + /* Empty queue */ +#ifdef SCTP_STR_LOGGING + sctp_log_strm_del(chk, NULL, SCTP_STR_LOG_FROM_INSERT_HD); +#endif + TAILQ_INSERT_HEAD(&strm->inqueue, chk, sctp_next); + } else { + TAILQ_FOREACH(at, &strm->inqueue, sctp_next) { + if (compare_with_wrap(at->rec.data.stream_seq, + chk->rec.data.stream_seq, MAX_SEQ)) { + /* + * one in queue is bigger than the new + * one, insert before this one + */ +#ifdef SCTP_STR_LOGGING + sctp_log_strm_del(chk, at, + SCTP_STR_LOG_FROM_INSERT_MD); +#endif + TAILQ_INSERT_BEFORE(at, chk, sctp_next); + break; + } else if (at->rec.data.stream_seq == + chk->rec.data.stream_seq) { + /* + * Gak, He sent me a duplicate str seq + * number + */ + /* + * foo bar, I guess I will just free + * this new guy, should we abort too? + * FIX ME MAYBE? Or it COULD be that + * the SSN's have wrapped. Maybe I + * should compare to TSN somehow... + * sigh for now just blow away the + * chunk! + */ + + if (chk->data) + sctp_m_freem(chk->data); + chk->data = NULL; + asoc->size_on_all_streams -= chk->send_size; + asoc->cnt_on_all_streams--; + sctp_pegs[SCTP_DUP_SSN_RCVD]++; + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < + 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return; + } else { + if (TAILQ_NEXT(at, sctp_next) == NULL) { + /* + * We are at the end, insert it + * after this one + */ +#ifdef SCTP_STR_LOGGING + sctp_log_strm_del(chk, at, + SCTP_STR_LOG_FROM_INSERT_TL); +#endif + TAILQ_INSERT_AFTER(&strm->inqueue, + at, chk, sctp_next); + break; + } + } + } + } + } else { + /* We delivered some chunks, wake them up */ + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Doing WAKEUP!\n"); + } +#endif + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); + } +} + +/* + * Returns two things: You get the total size of the deliverable parts of the + * first fragmented message on the reassembly queue. And you get a 1 back if + * all of the message is ready or a 0 back if the message is still incomplete + */ +static int +sctp_is_all_msg_on_reasm(struct sctp_association *asoc, int *t_size) +{ + struct sctp_tmit_chunk *chk; + u_int32_t tsn; + + *t_size = 0; + chk = TAILQ_FIRST(&asoc->reasmqueue); + if (chk == NULL) { + /* nothing on the queue */ + return (0); + } + if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == 0) { + /* Not a first on the queue */ + return (0); + } + tsn = chk->rec.data.TSN_seq; + while (chk) { + if (tsn != chk->rec.data.TSN_seq) { + return (0); + } + *t_size += chk->send_size; + if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) { + return (1); + } + tsn++; + chk = TAILQ_NEXT(chk, sctp_next); + } + return (0); +} + +/* + * Dump onto the re-assembly queue, in its proper place. After dumping on + * the queue, see if anthing can be delivered. If so pull it off (or as much + * as we can. If we run out of space then we must dump what we can and set + * the appropriate flag to say we queued what we could. + */ +static void +sctp_queue_data_for_reasm(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_tmit_chunk *chk, int *abort_flag) +{ + struct mbuf *oper; + u_int16_t nxt_todel; + u_int32_t cum_ackp1, last_tsn, prev_tsn, post_tsn; + int tsize; + u_char last_flags; + struct sctp_tmit_chunk *at, *prev, *next; + + prev = next = NULL; + cum_ackp1 = asoc->tsn_last_delivered + 1; + + if (TAILQ_EMPTY(&asoc->reasmqueue)) { + /* This is the first one on the queue */ + TAILQ_INSERT_HEAD(&asoc->reasmqueue, chk, sctp_next); + /* + * we do not check for delivery of anything when + * only one fragment is here + */ + asoc->size_on_reasm_queue = chk->send_size; + asoc->cnt_on_reasm_queue++; + if (chk->rec.data.TSN_seq == cum_ackp1) { + if (asoc->fragmented_delivery_inprogress == 0 && + (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) != + SCTP_DATA_FIRST_FRAG) { + /* + * An empty queue, no delivery inprogress, we + * hit the next one and it does NOT have a + * FIRST fragment mark. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Gak, Evil plot, its not first, no fragmented delivery in progress\n"); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x10000001); + } + sctp_abort_an_association(stcb->sctp_ep, stcb, + SCTP_PEER_FAULTY, oper); + *abort_flag = 1; + } else if (asoc->fragmented_delivery_inprogress && + (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) == SCTP_DATA_FIRST_FRAG) { + /* + * We are doing a partial delivery and the NEXT + * chunk MUST be either the LAST or MIDDLE + * fragment NOT a FIRST + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Gak, Evil plot, it IS a first and fragmented delivery in progress\n"); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x10000002); + } + sctp_abort_an_association(stcb->sctp_ep, stcb, + SCTP_PEER_FAULTY, oper); + *abort_flag = 1; + } else if (asoc->fragmented_delivery_inprogress) { + /* Here we are ok with a MIDDLE or LAST piece */ + if (chk->rec.data.stream_number != + asoc->str_of_pdapi) { + /* Got to be the right STR No */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Gak, Evil plot, it IS not same stream number %d vs %d\n", + chk->rec.data.stream_number, + asoc->str_of_pdapi); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x10000003); + } + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + *abort_flag = 1; + } else if ((asoc->fragment_flags & SCTP_DATA_UNORDERED) != + SCTP_DATA_UNORDERED && + chk->rec.data.stream_seq != + asoc->ssn_of_pdapi) { + /* Got to be the right STR Seq */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Gak, Evil plot, it IS not same stream seq %d vs %d\n", + chk->rec.data.stream_seq, + asoc->ssn_of_pdapi); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x10000004); + } + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + *abort_flag = 1; + } + } + } + return; + } + /* Find its place */ + at = TAILQ_FIRST(&asoc->reasmqueue); + + /* Grab the top flags */ + TAILQ_FOREACH(at, &asoc->reasmqueue, sctp_next) { + if (compare_with_wrap(at->rec.data.TSN_seq, + chk->rec.data.TSN_seq, MAX_TSN)) { + /* + * one in queue is bigger than the new one, insert + * before this one + */ + /* A check */ + asoc->size_on_reasm_queue += chk->send_size; + asoc->cnt_on_reasm_queue++; + next = at; + TAILQ_INSERT_BEFORE(at, chk, sctp_next); + break; + } else if (at->rec.data.TSN_seq == chk->rec.data.TSN_seq) { + /* Gak, He sent me a duplicate str seq number */ + /* + * foo bar, I guess I will just free this new guy, + * should we abort too? FIX ME MAYBE? Or it COULD be + * that the SSN's have wrapped. Maybe I should compare + * to TSN somehow... sigh for now just blow away the + * chunk! + */ + if (chk->data) + sctp_m_freem(chk->data); + chk->data = NULL; + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return; + } else { + last_flags = at->rec.data.rcv_flags; + last_tsn = at->rec.data.TSN_seq; + prev = at; + if (TAILQ_NEXT(at, sctp_next) == NULL) { + /* + * We are at the end, insert it after this one + */ + /* check it first */ + asoc->size_on_reasm_queue += chk->send_size; + asoc->cnt_on_reasm_queue++; + TAILQ_INSERT_AFTER(&asoc->reasmqueue, at, chk, sctp_next); + break; + } + } + } + /* Now the audits */ + if (prev) { + prev_tsn = chk->rec.data.TSN_seq - 1; + if (prev_tsn == prev->rec.data.TSN_seq) { + /* + * Ok the one I am dropping onto the end + * is the NEXT. A bit of valdiation here. + */ + if ((prev->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == + SCTP_DATA_FIRST_FRAG || + (prev->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == + SCTP_DATA_MIDDLE_FRAG) { + /* + * Insert chk MUST be a MIDDLE or LAST fragment + */ + if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == + SCTP_DATA_FIRST_FRAG) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Prev check - It can be a midlle or last but not a first\n"); + printf("Gak, Evil plot, it's a FIRST!\n"); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x10000005); + } + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + *abort_flag = 1; + return; + } + if (chk->rec.data.stream_number != + prev->rec.data.stream_number) { + /* + * Huh, need the correct STR here, they + * must be the same. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Prev check - Gak, Evil plot, ssn:%d not the same as at:%d\n", + chk->rec.data.stream_number, + prev->rec.data.stream_number); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x10000006); + } + + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + return; + } + if ((prev->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0 && + chk->rec.data.stream_seq != + prev->rec.data.stream_seq) { + /* + * Huh, need the correct STR here, they + * must be the same. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Prev check - Gak, Evil plot, sseq:%d not the same as at:%d\n", + chk->rec.data.stream_seq, + prev->rec.data.stream_seq); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x10000007); + } + + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + return; + } + } else if ((prev->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == + SCTP_DATA_LAST_FRAG) { + /* Insert chk MUST be a FIRST */ + if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) != + SCTP_DATA_FIRST_FRAG) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Prev check - Gak, evil plot, its not FIRST and it must be!\n"); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x10000008); + } + + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + return; + } + } + } + } + + if (next) { + post_tsn = chk->rec.data.TSN_seq + 1; + if (post_tsn == next->rec.data.TSN_seq) { + /* + * Ok the one I am inserting ahead of + * is my NEXT one. A bit of valdiation here. + */ + if (next->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) { + /* Insert chk MUST be a last fragment */ + if ((chk->rec.data.rcv_flags&SCTP_DATA_FRAG_MASK) + != SCTP_DATA_LAST_FRAG) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Next chk - Next is FIRST, we must be LAST\n"); + printf("Gak, Evil plot, its not a last!\n"); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x10000009); + } + + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + return; + } + } else if ((next->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == + SCTP_DATA_MIDDLE_FRAG || + (next->rec.data.rcv_flags&SCTP_DATA_FRAG_MASK) == + SCTP_DATA_LAST_FRAG) { + /* Insert chk CAN be MIDDLE or FIRST NOT LAST */ + if ((chk->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) == + SCTP_DATA_LAST_FRAG) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Next chk - Next is a MIDDLE/LAST\n"); + printf("Gak, Evil plot, new prev chunk is a LAST\n"); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x1000000a); + } + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + return; + } + if (chk->rec.data.stream_number != + next->rec.data.stream_number) { + /* + * Huh, need the correct STR here, they + * must be the same. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Next chk - Gak, Evil plot, ssn:%d not the same as at:%d\n", + chk->rec.data.stream_number, + next->rec.data.stream_number); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x1000000b); + } + + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + return; + } + if ((next->rec.data.rcv_flags & SCTP_DATA_UNORDERED) == 0 && + chk->rec.data.stream_seq != + next->rec.data.stream_seq) { + /* + * Huh, need the correct STR here, they + * must be the same. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Next chk - Gak, Evil plot, sseq:%d not the same as at:%d\n", + chk->rec.data.stream_seq, + next->rec.data.stream_seq); + } +#endif + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x1000000c); + } + + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + return; + + } + } + } + } + /* + * now that we have all in there place we must check a number of + * things to see if we can send data to the ULP. + */ + /* we need to do some delivery, if we can */ + chk = TAILQ_FIRST(&asoc->reasmqueue); + if (chk == NULL) { + /* Huh? */ + asoc->size_on_reasm_queue = 0; + asoc->cnt_on_reasm_queue = 0; + return; + } + if (asoc->fragmented_delivery_inprogress == 0) { + nxt_todel = + asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered + 1; + if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) && + (nxt_todel == chk->rec.data.stream_seq || + (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED))) { + /* + * Yep the first one is here and its + * ok to deliver but should we? + */ + if (TAILQ_EMPTY(&asoc->delivery_queue) && + (sctp_is_all_msg_on_reasm(asoc, &tsize) || + (asoc->size_on_reasm_queue >= + (stcb->sctp_socket->so_rcv.sb_hiwat >> 2) && + tsize))) { + /* + * Yes, we setup to + * start reception, by backing down the TSN + * just in case we can't deliver. If we + */ + asoc->fragmented_delivery_inprogress = 1; + asoc->tsn_last_delivered = + chk->rec.data.TSN_seq - 1; + asoc->str_of_pdapi = + chk->rec.data.stream_number; + asoc->ssn_of_pdapi = chk->rec.data.stream_seq; + asoc->fragment_flags = chk->rec.data.rcv_flags; + sctp_service_reassembly(stcb, asoc, 0); + } + } + } else { + sctp_service_reassembly(stcb, asoc, 0); + } +} + +/* + * This is an unfortunate routine. It checks to make sure a evil guy is not + * stuffing us full of bad packet fragments. A broken peer could also do this + * but this is doubtful. It is to bad I must worry about evil crackers sigh + * :< more cycles. + */ +static int +sctp_does_chk_belong_to_reasm(struct sctp_association *asoc, + struct sctp_tmit_chunk *chk) +{ + struct sctp_tmit_chunk *at; + u_int32_t tsn_est; + + TAILQ_FOREACH(at, &asoc->reasmqueue, sctp_next) { + if (compare_with_wrap(chk->rec.data.TSN_seq, + at->rec.data.TSN_seq, MAX_TSN)) { + /* is it one bigger? */ + tsn_est = at->rec.data.TSN_seq + 1; + if (tsn_est == chk->rec.data.TSN_seq) { + /* yep. It better be a last then*/ + if ((at->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) != + SCTP_DATA_LAST_FRAG) { + /* + * Ok this guy belongs next to a guy + * that is NOT last, it should be a + * middle/last, not a complete chunk. + */ + return (1); + } else { + /* + * This guy is ok since its a LAST and + * the new chunk is a fully self- + * contained one. + */ + return (0); + } + } + } else if (chk->rec.data.TSN_seq == at->rec.data.TSN_seq) { + /* Software error since I have a dup? */ + return (1); + } else { + /* + * Ok, 'at' is larger than new chunk but does it + * need to be right before it. + */ + tsn_est = chk->rec.data.TSN_seq + 1; + if (tsn_est == at->rec.data.TSN_seq) { + /* Yep, It better be a first */ + if ((at->rec.data.rcv_flags & SCTP_DATA_FRAG_MASK) != + SCTP_DATA_FIRST_FRAG) { + return (1); + } else { + return (0); + } + } + } + } + return (0); +} + +extern unsigned int sctp_max_chunks_on_queue; +static int +sctp_process_a_data_chunk(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct mbuf **m, int offset, struct sctp_data_chunk *ch, int chk_length, + struct sctp_nets *net, u_int32_t *high_tsn, int *abort_flag, + int *break_flag, int last_chunk) +{ + /* Process a data chunk */ + /* struct sctp_tmit_chunk *chk;*/ + struct sctp_tmit_chunk *chk; + u_int32_t tsn, gap; + struct mbuf *dmbuf; + int indx, the_len; + u_int16_t strmno, strmseq; + struct mbuf *oper; + + chk = NULL; + tsn = ntohl(ch->dp.tsn); +#ifdef SCTP_MAP_LOGGING + sctp_log_map(0, tsn, asoc->cumulative_tsn, SCTP_MAP_PREPARE_SLIDE); +#endif + if (compare_with_wrap(asoc->cumulative_tsn, tsn, MAX_TSN) || + asoc->cumulative_tsn == tsn) { + /* It is a duplicate */ + sctp_pegs[SCTP_DUPTSN_RECVD]++; + if (asoc->numduptsns < SCTP_MAX_DUP_TSNS) { + /* Record a dup for the next outbound sack */ + asoc->dup_tsns[asoc->numduptsns] = tsn; + asoc->numduptsns++; + } + return (0); + } + /* Calculate the number of TSN's between the base and this TSN */ + if (tsn >= asoc->mapping_array_base_tsn) { + gap = tsn - asoc->mapping_array_base_tsn; + } else { + gap = (MAX_TSN - asoc->mapping_array_base_tsn) + tsn + 1; + } + if (gap >= (SCTP_MAPPING_ARRAY << 3)) { + /* Can't hold the bit in the mapping at max array, toss it */ + return (0); + } + if (gap >= (uint32_t)(asoc->mapping_array_size << 3)) { + if (sctp_expand_mapping_array(asoc)) { + /* Can't expand, drop it */ + return (0); + } + } + if (compare_with_wrap(tsn, *high_tsn, MAX_TSN)) { + *high_tsn = tsn; + } + /* See if we have received this one already */ + if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, gap)) { + sctp_pegs[SCTP_DUPTSN_RECVD]++; + if (asoc->numduptsns < SCTP_MAX_DUP_TSNS) { + /* Record a dup for the next outbound sack */ + asoc->dup_tsns[asoc->numduptsns] = tsn; + asoc->numduptsns++; + } + if (!callout_pending(&asoc->dack_timer.timer)) { + /* + * By starting the timer we assure that we + * WILL sack at the end of the packet + * when sctp_sack_check gets called. + */ + sctp_timer_start(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, + stcb, NULL); + } + return (0); + } + /* + * Check to see about the GONE flag, duplicates would cause + * a sack to be sent up above + */ + if (stcb && (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { + /* + * wait a minute, this guy is gone, there is no + * longer a receiver. Send peer an ABORT! + */ + struct mbuf *op_err; + op_err = sctp_generate_invmanparam(SCTP_CAUSE_OUT_OF_RESC); + sctp_abort_an_association(stcb->sctp_ep, stcb, 0, op_err); + *abort_flag = 1; + return (0); + } + /* + * Now before going further we see if there is room. If NOT then + * we MAY let one through only IF this TSN is the one we are + * waiting for on a partial delivery API. + */ + + /* now do the tests */ + if (((asoc->cnt_on_all_streams + + asoc->cnt_on_delivery_queue + + asoc->cnt_on_reasm_queue + + asoc->cnt_msg_on_sb) > sctp_max_chunks_on_queue) || + (((int)asoc->my_rwnd) <= 0)) { + /* + * When we have NO room in the rwnd we check + * to make sure the reader is doing its job... + */ + if (stcb->sctp_socket->so_rcv.sb_cc) { + /* some to read, wake-up */ + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); + } + /* now is it in the mapping array of what we have accepted? */ + if (compare_with_wrap(tsn, + asoc->highest_tsn_inside_map, MAX_TSN)) { + + /* Nope not in the valid range dump it */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("My rwnd overrun1:tsn:%lx rwnd %lu sbspace:%ld delq:%d!\n", + (u_long)tsn, (u_long)asoc->my_rwnd, + sctp_sbspace(&stcb->sctp_socket->so_rcv), + stcb->asoc.cnt_on_delivery_queue); + } +#endif + sctp_set_rwnd(stcb, asoc); + if ((asoc->cnt_on_all_streams + + asoc->cnt_on_delivery_queue + + asoc->cnt_on_reasm_queue + + asoc->cnt_msg_on_sb) > sctp_max_chunks_on_queue) { + sctp_pegs[SCTP_MSGC_DROP]++; + } else { + sctp_pegs[SCTP_RWND_DROPS]++; + } + indx = *break_flag; + *break_flag = 1; + return (0); + } + } + strmno = ntohs(ch->dp.stream_id); + if (strmno >= asoc->streamincnt) { + struct sctp_paramhdr *phdr; + struct mbuf *mb; + + MGETHDR(mb, M_DONTWAIT, MT_DATA); + if (mb != NULL) { + /* add some space up front so prepend will work well */ + mb->m_data += sizeof(struct sctp_chunkhdr); + phdr = mtod(mb, struct sctp_paramhdr *); + /* + * Error causes are just param's and this one has + * two back to back phdr, one with the error type + * and size, the other with the streamid and a rsvd + */ + mb->m_pkthdr.len = mb->m_len = + (sizeof(struct sctp_paramhdr) * 2); + phdr->param_type = htons(SCTP_CAUSE_INV_STRM); + phdr->param_length = + htons(sizeof(struct sctp_paramhdr) * 2); + phdr++; + /* We insert the stream in the type field */ + phdr->param_type = ch->dp.stream_id; + /* And set the length to 0 for the rsvd field */ + phdr->param_length = 0; + sctp_queue_op_err(stcb, mb); + } + sctp_pegs[SCTP_BAD_STRMNO]++; + return (0); + } + /* + * Before we continue lets validate that we are not + * being fooled by an evil attacker. We can only + * have 4k chunks based on our TSN spread allowed + * by the mapping array 512 * 8 bits, so there is + * no way our stream sequence numbers could have wrapped. + * We of course only validate the FIRST fragment so the + * bit must be set. + */ + strmseq = ntohs(ch->dp.stream_sequence); + if ((ch->ch.chunk_flags & SCTP_DATA_FIRST_FRAG) && + (ch->ch.chunk_flags & SCTP_DATA_UNORDERED) == 0 && + (compare_with_wrap(asoc->strmin[strmno].last_sequence_delivered, + strmseq, MAX_SEQ) || + asoc->strmin[strmno].last_sequence_delivered == strmseq)) { + /* The incoming sseq is behind where we last delivered? */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("EVIL/Broken-Dup S-SEQ:%d delivered:%d from peer, Abort!\n", + strmseq, + asoc->strmin[strmno].last_sequence_delivered); + } +#endif + /* + * throw it in the stream so it gets cleaned up in + * association destruction + */ + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, struct sctp_paramhdr *); + ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x20000001); + } + sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, + oper); + sctp_pegs[SCTP_BAD_SSN_WRAP]++; + *abort_flag = 1; + return (0); + } + + the_len = (chk_length-sizeof(struct sctp_data_chunk)); + if (last_chunk == 0) { + dmbuf = sctp_m_copym(*m, + (offset + sizeof(struct sctp_data_chunk)), + the_len, M_DONTWAIT); + } else { + /* We can steal the last chunk */ + dmbuf = *m; + /* lop off the top part */ + m_adj(dmbuf, (offset + sizeof(struct sctp_data_chunk))); + if (dmbuf->m_pkthdr.len > the_len) { + /* Trim the end round bytes off too */ + m_adj(dmbuf, -(dmbuf->m_pkthdr.len-the_len)); + } + sctp_pegs[SCTP_NO_COPY_IN]++; + } + if (dmbuf == NULL) { + sctp_pegs[SCTP_DROP_NOMEMORY]++; + return (0); + } + if ((ch->ch.chunk_flags & SCTP_DATA_NOT_FRAG) == SCTP_DATA_NOT_FRAG && + asoc->fragmented_delivery_inprogress == 0 && + TAILQ_EMPTY(&asoc->delivery_queue) && + ((ch->ch.chunk_flags & SCTP_DATA_UNORDERED) || + ((asoc->strmin[strmno].last_sequence_delivered + 1) == strmseq && + TAILQ_EMPTY(&asoc->strmin[strmno].inqueue))) && + ((long)(stcb->sctp_socket->so_rcv.sb_hiwat - + stcb->sctp_socket->so_rcv.sb_cc) >= (long)the_len)) { + /* Candidate for express delivery */ + /* + * Its not fragmented, + * No PD-API is up, + * Nothing in the delivery queue, + * Its un-ordered OR ordered and the next to deliver AND + * nothing else is stuck on the stream queue, + * And there is room for it in the socket buffer. + * Lets just stuff it up the buffer.... + */ + + struct mbuf *control, *mmm; + struct sockaddr_in6 sin6; + struct sockaddr_in6 lsa6; + struct sockaddr *to; + + /* It would be nice to avoid this copy if we could :< */ + control = sctp_build_ctl_nchunk(stcb, tsn, + ch->dp.protocol_id, 0, strmno, strmseq, + ch->ch.chunk_flags); + /* XXX need to append PKTHDR to the socket buffer first */ + + if ((dmbuf->m_flags & M_PKTHDR) == 0) { + struct mbuf *tmp; + MGETHDR(tmp, M_DONTWAIT, MT_DATA); + if (tmp == NULL) { + + /* no room! */ + if (control) { + sctp_m_freem(control); + stcb->asoc.my_rwnd_control_len -= + CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); + } + + goto failed_express_del; + } + tmp->m_pkthdr.len = the_len; + tmp->m_len = 0; + tmp->m_next = dmbuf; + dmbuf = tmp; + } + to = (struct sockaddr *)&net->ro._l_addr; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + to->sa_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)to; + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr.s6_addr16[2] = 0xffff; + bcopy(&sin->sin_addr, + &sin6.sin6_addr.s6_addr16[3], + sizeof(sin6.sin6_addr.s6_addr16[3])); + sin6.sin6_port = sin->sin_port; + to = (struct sockaddr *)&sin6; + } + + /* check and strip embedded scope junk */ + to = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)to, + &lsa6); + if (((struct sockaddr_in *)to)->sin_port == 0) { + printf("Huh c, port is %d not net:%x %d?\n", + ((struct sockaddr_in *)to)->sin_port, + (u_int)net, + (int)(ntohs(stcb->rport))); + ((struct sockaddr_in *)to)->sin_port = stcb->rport; + } + + mmm = dmbuf; + /* Mark the EOR */ + while (mmm->m_next != NULL) { + mmm = mmm->m_next; + } + mmm->m_flags |= M_EOR; + if (compare_with_wrap(tsn, asoc->highest_tsn_inside_map, MAX_TSN)) { + /* we have a new high score */ + asoc->highest_tsn_inside_map = tsn; +#ifdef SCTP_MAP_LOGGING + sctp_log_map(0, 1, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); +#endif + } + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + if (!sbappendaddr_nocheck(&stcb->sctp_socket->so_rcv, to, dmbuf, + control, stcb->asoc.my_vtag, stcb->sctp_ep)) { + if (control) { + sctp_m_freem(control); + stcb->asoc.my_rwnd_control_len -= + CMSG_LEN(sizeof(struct sctp_sndrcvinfo)); + } + sctp_m_freem(dmbuf); + goto failed_express_del; + } + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) { + if (sctp_add_to_socket_q(stcb->sctp_ep, stcb)) { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + } else { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + SCTP_INP_WUNLOCK(stcb->sctp_ep); + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); + if ((ch->ch.chunk_flags & SCTP_DATA_UNORDERED) == 0) { + + /* for ordered, bump what we delivered */ + asoc->strmin[strmno].last_sequence_delivered++; + } + sctp_pegs[SCTP_EXPRESS_ROUTE]++; +#ifdef SCTP_STR_LOGGING + sctp_log_strm_del_alt(tsn, strmseq, + SCTP_STR_LOG_FROM_EXPRS_DEL); +#endif +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Express Delivery succeeds\n"); + } +#endif + goto finish_express_del; + } + + failed_express_del: + /* If we reach here this is a new chunk */ + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* No memory so we drop the chunk */ + sctp_pegs[SCTP_DROP_NOMEMORY]++; + if (last_chunk == 0) { + /* we copied it, free the copy */ + sctp_m_freem(dmbuf); + } + return (0); + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + chk->rec.data.TSN_seq = tsn; + chk->rec.data.stream_seq = strmseq; + chk->rec.data.stream_number = strmno; + chk->rec.data.payloadtype = ch->dp.protocol_id; + chk->rec.data.context = 0; + chk->rec.data.doing_fast_retransmit = 0; + chk->rec.data.rcv_flags = ch->ch.chunk_flags; + chk->asoc = asoc; + chk->send_size = the_len; + chk->whoTo = net; + net->ref_count++; + chk->data = dmbuf; + + + /* Mark it as received */ + /* Now queue it where it belongs */ + if ((chk->rec.data.rcv_flags & SCTP_DATA_NOT_FRAG) == + SCTP_DATA_NOT_FRAG) { + /* First a sanity check */ + if (asoc->fragmented_delivery_inprogress) { + /* + * Ok, we have a fragmented delivery in progress + * if this chunk is next to deliver OR belongs in + * our view to the reassembly, the peer is evil + * or broken. + */ + u_int32_t estimate_tsn; + estimate_tsn = asoc->tsn_last_delivered + 1; + if (TAILQ_EMPTY(&asoc->reasmqueue) && + (estimate_tsn == chk->rec.data.TSN_seq)) { + /* Evil/Broke peer */ + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x20000002); + } + sctp_abort_an_association(stcb->sctp_ep, stcb, + SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + sctp_pegs[SCTP_DROP_FRAG]++; + return (0); + } else { + if (sctp_does_chk_belong_to_reasm(asoc, chk)) { + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x20000003); + } + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + sctp_pegs[SCTP_DROP_FRAG]++; + return (0); + } + } + } else { + if (!TAILQ_EMPTY(&asoc->reasmqueue)) { + /* + * Reassembly queue is NOT empty + * validate that this chk does not need to + * be in reasembly queue. If it does then + * our peer is broken or evil. + */ + if (sctp_does_chk_belong_to_reasm(asoc, chk)) { + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = + sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, + struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = + htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x20000004); + } + sctp_abort_an_association(stcb->sctp_ep, + stcb, SCTP_PEER_FAULTY, oper); + + *abort_flag = 1; + sctp_pegs[SCTP_DROP_FRAG]++; + return (0); + } + } + } + if (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) { + /* queue directly into socket buffer */ + sctp_deliver_data(stcb, asoc, chk, 0); + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); + } else { + /* Special check for when streams are resetting. + * We could be more smart about this and check the + * actual stream to see if it is not being reset.. that + * way we would not create a HOLB when amongst streams + * being reset and those not being reset. + * + * We take complete messages that have a stream reset + * intervening (aka the TSN is after where our cum-ack needs + * to be) off and put them on a pending_reply_queue. The + * reassembly ones we do not have to worry about since + * they are all sorted and proceessed by TSN order. It + * is only the singletons I must worry about. + */ + if ((asoc->pending_reply) && + ((compare_with_wrap(tsn, ntohl(asoc->pending_reply->reset_at_tsn), MAX_TSN)) || + (tsn == ntohl(asoc->pending_reply->reset_at_tsn))) + ) { + /* yep its past where we need to reset... go ahead and + * queue it. + */ + TAILQ_INSERT_TAIL(&asoc->pending_reply_queue , chk, sctp_next); + } else { + sctp_queue_data_to_stream(stcb, asoc, chk, abort_flag); + } + } + } else { + /* Into the re-assembly queue */ + sctp_queue_data_for_reasm(stcb, asoc, chk, abort_flag); + if (*abort_flag) { + sctp_pegs[SCTP_DROP_FRAG]++; + return (0); + } + } + if (compare_with_wrap(tsn, asoc->highest_tsn_inside_map, MAX_TSN)) { + /* we have a new high score */ + asoc->highest_tsn_inside_map = tsn; +#ifdef SCTP_MAP_LOGGING + sctp_log_map(0, 2, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); +#endif + } + finish_express_del: + if (last_chunk) { + *m = NULL; + } + sctp_pegs[SCTP_PEG_TSNS_RCVD]++; + /* Set it present please */ +#ifdef SCTP_STR_LOGGING + sctp_log_strm_del_alt(tsn, strmseq, SCTP_STR_LOG_FROM_MARK_TSN); +#endif +#ifdef SCTP_MAP_LOGGING + sctp_log_map(asoc->mapping_array_base_tsn, asoc->cumulative_tsn, + asoc->highest_tsn_inside_map, SCTP_MAP_PREPARE_SLIDE); +#endif + SCTP_SET_TSN_PRESENT(asoc->mapping_array, gap); + return (1); +} + +void +sctp_sack_check(struct sctp_tcb *stcb, int ok_to_sack, int was_a_gap, int *abort_flag) +{ + /* + * Now we also need to check the mapping array in a couple of ways. + * 1) Did we move the cum-ack point? + */ + struct sctp_association *asoc; + int i, at; + int m_size, all_ones; + int slide_from, slide_end, lgap, distance; +#ifdef SCTP_MAP_LOGGING + uint32_t old_cumack, old_base, old_highest; + unsigned char aux_array[64]; +#endif + + asoc = &stcb->asoc; + at = 0; + +#ifdef SCTP_MAP_LOGGING + old_cumack = asoc->cumulative_tsn; + old_base = asoc->mapping_array_base_tsn; + old_highest = asoc->highest_tsn_inside_map; + if (asoc->mapping_array_size < 64) + memcpy(aux_array, asoc->mapping_array, + asoc->mapping_array_size); + else + memcpy(aux_array, asoc->mapping_array, 64); +#endif + + /* + * We could probably improve this a small bit by calculating the + * offset of the current cum-ack as the starting point. + */ + all_ones = 1; + m_size = stcb->asoc.mapping_array_size << 3; + for (i = 0; i < m_size; i++) { + if (!SCTP_IS_TSN_PRESENT(asoc->mapping_array, i)) { + /* + * Ok we found the first place that we are + * missing a TSN. + */ + at = i; + all_ones = 0; + asoc->cumulative_tsn = asoc->mapping_array_base_tsn + + (i - 1); + break; + } + } + if (compare_with_wrap(asoc->cumulative_tsn, + asoc->highest_tsn_inside_map, + MAX_TSN)) { + panic("huh, cumack greater than high-tsn in map"); + } + if (all_ones || + (asoc->cumulative_tsn == asoc->highest_tsn_inside_map && at >= 8)) { + /* The complete array was completed by a single FR */ + /* higest becomes the cum-ack */ + int clr; + asoc->cumulative_tsn = asoc->highest_tsn_inside_map; + /* clear the array */ + if (all_ones) + clr = asoc->mapping_array_size; + else { + clr = (at >> 3) + 1; + /* + * this should be the allones case + * but just in case :> + */ + if (clr > asoc->mapping_array_size) + clr = asoc->mapping_array_size; + } + memset(asoc->mapping_array, 0, clr); + /* base becomes one ahead of the cum-ack */ + asoc->mapping_array_base_tsn = asoc->cumulative_tsn + 1; +#ifdef SCTP_MAP_LOGGING + sctp_log_map(old_base, old_cumack, old_highest, + SCTP_MAP_PREPARE_SLIDE); + sctp_log_map(asoc->mapping_array_base_tsn, asoc->cumulative_tsn, + asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_CLEARED); +#endif + } else if (at >= 8) { + /* we can slide the mapping array down */ + /* Calculate the new byte postion we can move down */ + slide_from = at >> 3; + /* now calculate the ceiling of the move using our highest TSN value */ + if (asoc->highest_tsn_inside_map >= asoc->mapping_array_base_tsn) { + lgap = asoc->highest_tsn_inside_map - + asoc->mapping_array_base_tsn; + } else { + lgap = (MAX_TSN - asoc->mapping_array_base_tsn) + + asoc->highest_tsn_inside_map + 1; + } + slide_end = lgap >> 3; + if (slide_end < slide_from) { + panic("impossible slide"); + } + distance = (slide_end-slide_from) + 1; +#ifdef SCTP_MAP_LOGGING + sctp_log_map(old_base, old_cumack, old_highest, + SCTP_MAP_PREPARE_SLIDE); + sctp_log_map((uint32_t)slide_from, (uint32_t)slide_end, + (uint32_t)lgap, SCTP_MAP_SLIDE_FROM); +#endif + if (distance + slide_from > asoc->mapping_array_size || + distance < 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Ugh bad addition.. you can't hrumpp!\n"); + } +#endif + /* + * Here we do NOT slide forward the array so that + * hopefully when more data comes in to fill it up + * we will be able to slide it forward. Really + * I don't think this should happen :-0 + */ + +#ifdef SCTP_MAP_LOGGING + sctp_log_map((uint32_t)distance, (uint32_t)slide_from, + (uint32_t)asoc->mapping_array_size, + SCTP_MAP_SLIDE_NONE); +#endif + } else { + int ii; + for (ii = 0; ii < distance; ii++) { + asoc->mapping_array[ii] = + asoc->mapping_array[slide_from + ii]; + } + for (ii = distance;ii <= slide_end; ii++) { + asoc->mapping_array[ii] = 0; + } + asoc->mapping_array_base_tsn += (slide_from << 3); +#ifdef SCTP_MAP_LOGGING + sctp_log_map(asoc->mapping_array_base_tsn, + asoc->cumulative_tsn, asoc->highest_tsn_inside_map, + SCTP_MAP_SLIDE_RESULT); +#endif + } + } + + /* check the special flag for stream resets */ + if ((asoc->pending_reply) && + ((compare_with_wrap((asoc->cumulative_tsn+1), ntohl(asoc->pending_reply->reset_at_tsn), MAX_TSN)) || + ((asoc->cumulative_tsn+1) == ntohl(asoc->pending_reply->reset_at_tsn))) + ) { + /* we have finished working through the backlogged TSN's now + * time to reset streams. + * 1: call reset function. + * 2: free pending_reply space + * 3: distribute any chunks in pending_reply_queue. + */ + struct sctp_tmit_chunk *chk; + sctp_handle_stream_reset_response(stcb, asoc->pending_reply); + FREE(asoc->pending_reply, M_PCB); + asoc->pending_reply = NULL; + chk = TAILQ_FIRST(&asoc->pending_reply_queue); + while (chk) { + TAILQ_REMOVE(&asoc->pending_reply_queue, chk, sctp_next); + sctp_queue_data_to_stream(stcb, asoc, chk, abort_flag); + if (*abort_flag) { + return; + } + chk = TAILQ_FIRST(&asoc->pending_reply_queue); + } + } + /* + * Now we need to see if we need to queue a sack or just start + * the timer (if allowed). + */ + if (ok_to_sack) { + if (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) { + /* + * Ok special case, in SHUTDOWN-SENT case. + * here we maker sure SACK timer is off and + * instead send a SHUTDOWN and a SACK + */ + if (callout_pending(&stcb->asoc.dack_timer.timer)) { + sctp_timer_stop(SCTP_TIMER_TYPE_RECV, + stcb->sctp_ep, stcb, NULL); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("%s:%d sends a shutdown\n", + __FILE__, + __LINE__ + ); + } +#endif + sctp_send_shutdown(stcb, stcb->asoc.primary_destination); + sctp_send_sack(stcb); + } else { + int is_a_gap; + /* is there a gap now ? */ + is_a_gap = compare_with_wrap(stcb->asoc.highest_tsn_inside_map, + stcb->asoc.cumulative_tsn, MAX_TSN); + if ((stcb->asoc.first_ack_sent == 0) || /* First time we send a sack */ + ((was_a_gap) && (is_a_gap == 0)) || /* was a gap, but no longer is one */ + (stcb->asoc.numduptsns) || /* we have dup's */ + (is_a_gap) || /* is still a gap */ + (callout_pending(&stcb->asoc.dack_timer.timer)) /* timer was up . second packet */ + ) { + /* + * Ok we must build a SACK since the timer + * is pending, we got our first packet OR + * there are gaps or duplicates. + */ + stcb->asoc.first_ack_sent = 1; + sctp_send_sack(stcb); + /* The sending will stop the timer */ + } else { + sctp_timer_start(SCTP_TIMER_TYPE_RECV, + stcb->sctp_ep, stcb, NULL); + } + } + } +} + +void +sctp_service_queues(struct sctp_tcb *stcb, struct sctp_association *asoc, int hold_locks) +{ + struct sctp_tmit_chunk *chk; + int tsize, cntDel; + u_int16_t nxt_todel; + + cntDel = 0; + if (asoc->fragmented_delivery_inprogress) { + sctp_service_reassembly(stcb, asoc, hold_locks); + } + /* Can we proceed further, i.e. the PD-API is complete */ + if (asoc->fragmented_delivery_inprogress) { + /* no */ + return; + } + + /* + * Yes, reassembly delivery no longer in progress see if we + * have some on the sb hold queue. + */ + do { + if (stcb->sctp_socket->so_rcv.sb_cc >= stcb->sctp_socket->so_rcv.sb_hiwat) { + if (cntDel == 0) + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); + break; + } + /* If deliver_data says no we must stop */ + if (sctp_deliver_data(stcb, asoc, (struct sctp_tmit_chunk *)NULL, hold_locks) == 0) + break; + cntDel++; + chk = TAILQ_FIRST(&asoc->delivery_queue); + } while (chk); + if (cntDel) { + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); + } + /* + * Now is there some other chunk I can deliver + * from the reassembly queue. + */ + chk = TAILQ_FIRST(&asoc->reasmqueue); + if (chk == NULL) { + asoc->size_on_reasm_queue = 0; + asoc->cnt_on_reasm_queue = 0; + return; + } + nxt_todel = asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered + 1; + if ((chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG) && + ((nxt_todel == chk->rec.data.stream_seq) || + (chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED))) { + /* + * Yep the first one is here. We setup to + * start reception, by backing down the TSN + * just in case we can't deliver. + */ + + /* + * Before we start though either all of the + * message should be here or 1/4 the socket buffer + * max or nothing on the delivery queue and something + * can be delivered. + */ + if (TAILQ_EMPTY(&asoc->delivery_queue) && + (sctp_is_all_msg_on_reasm(asoc, &tsize) || + (asoc->size_on_reasm_queue >= + (stcb->sctp_socket->so_rcv.sb_hiwat >> 2) && tsize))) { + asoc->fragmented_delivery_inprogress = 1; + asoc->tsn_last_delivered = chk->rec.data.TSN_seq-1; + asoc->str_of_pdapi = chk->rec.data.stream_number; + asoc->ssn_of_pdapi = chk->rec.data.stream_seq; + asoc->fragment_flags = chk->rec.data.rcv_flags; + sctp_service_reassembly(stcb, asoc, hold_locks); + } + } +} + +int +sctp_process_data(struct mbuf **mm, int iphlen, int *offset, int length, + struct sctphdr *sh, struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct sctp_nets *net, u_int32_t *high_tsn) +{ + struct sctp_data_chunk *ch, chunk_buf; + struct sctp_association *asoc; + int num_chunks = 0; /* number of control chunks processed */ + int chk_length, break_flag, last_chunk; + int abort_flag = 0, was_a_gap = 0; + struct mbuf *m; + + /* set the rwnd */ + sctp_set_rwnd(stcb, &stcb->asoc); + + m = *mm; + asoc = &stcb->asoc; + if (compare_with_wrap(stcb->asoc.highest_tsn_inside_map, + stcb->asoc.cumulative_tsn, MAX_TSN)) { + /* there was a gap before this data was processed */ + was_a_gap = 1; + } + /* + * setup where we got the last DATA packet from for + * any SACK that may need to go out. Don't bump + * the net. This is done ONLY when a chunk + * is assigned. + */ + asoc->last_data_chunk_from = net; + + /* + * Now before we proceed we must figure out if this + * is a wasted cluster... i.e. it is a small packet + * sent in and yet the driver underneath allocated a + * full cluster for it. If so we must copy it to a + * smaller mbuf and free up the cluster mbuf. This + * will help with cluster starvation. + */ + if (m->m_len < (long)MHLEN && m->m_next == NULL) { + /* we only handle mbufs that are singletons.. not chains */ + MGET(m, M_DONTWAIT, MT_DATA); + if (m) { + /* ok lets see if we can copy the data up */ + caddr_t *from, *to; + + if ((*mm)->m_flags & M_PKTHDR) { + /* got to copy the header first */ +#ifdef __APPLE__ + M_COPY_PKTHDR(m, (*mm)); +#else + M_MOVE_PKTHDR(m, (*mm)); +#endif + } + /* get the pointers and copy */ + to = mtod(m, caddr_t *); + from = mtod((*mm), caddr_t *); + memcpy(to, from, (*mm)->m_len); + /* copy the length and free up the old */ + m->m_len = (*mm)->m_len; + sctp_m_freem(*mm); + /* sucess, back copy */ + *mm = m; + } else { + /* We are in trouble in the mbuf world .. yikes */ + m = *mm; + } + } + /* get pointer to the first chunk header */ + ch = (struct sctp_data_chunk *)sctp_m_getptr(m, *offset, + sizeof(chunk_buf), (u_int8_t *)&chunk_buf); + if (ch == NULL) { + printf(" ... its short\n"); + return (1); + } + /* + * process all DATA chunks... + */ + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("In process data off:%d length:%d iphlen:%d ch->type:%d\n", + *offset, length, iphlen, (int)ch->ch.chunk_type); + } +#endif + + *high_tsn = asoc->cumulative_tsn; + break_flag = 0; + while (ch->ch.chunk_type == SCTP_DATA) { + /* validate chunk length */ + chk_length = ntohs(ch->ch.chunk_length); + if ((size_t)chk_length < sizeof(struct sctp_data_chunk) + 1 || + length - *offset < chk_length) { + /* + * Need to send an abort since we had a invalid + * data chunk. + */ + struct mbuf *op_err; + MGET(op_err, M_DONTWAIT, MT_DATA); + if (op_err) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + op_err->m_len = sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(op_err, struct sctp_paramhdr *); + ph->param_type = + htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = htons(op_err->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x30000001); + } + sctp_abort_association(inp, stcb, m, iphlen, sh, + op_err); + return (2); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("A chunk of len:%d to process (tot:%d)\n", + chk_length, length - *offset); + } +#endif + +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xB1, 0); +#endif + if (SCTP_SIZE32(chk_length) == *offset - length) { + last_chunk = 1; + } else { + last_chunk = 0; + } + if (sctp_process_a_data_chunk(stcb, asoc, mm, *offset, ch, + chk_length, net, high_tsn, &abort_flag, &break_flag, + last_chunk)) { + num_chunks++; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Now incr num_chunks to %d\n", + num_chunks); + } +#endif + } + if (abort_flag) + return (2); + + if (break_flag) { + /* + * Set because of out of rwnd space and no drop rep + * space left. + */ + break; + } + + *offset += SCTP_SIZE32(chk_length); + if (*offset >= length) { + /* no more data left in the mbuf chain */ + break; + } + ch = (struct sctp_data_chunk *)sctp_m_getptr(m, *offset, + sizeof(chunk_buf), (u_int8_t *)&chunk_buf); + if (ch == NULL) { + *offset = length; + break; + } + } /* while */ + if (break_flag) { + /* + * we need to report rwnd overrun drops. + */ + sctp_send_packet_dropped(stcb, net, *mm, iphlen, 0); + } + if (num_chunks) { + /* + * Did we get data, if so update the time for + * auto-close and give peer credit for being + * alive. + */ + sctp_pegs[SCTP_DATA_DG_RECV]++; + stcb->asoc.overall_error_count = 0; + SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_last_rcvd); + } + /* now service all of the reassm queue and delivery queue */ + sctp_service_queues(stcb, asoc, 0); + if (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) { + /* + * Assure that we ack right away by making + * sure that a d-ack timer is running. So the + * sack_check will send a sack. + */ + sctp_timer_start(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, + net); + } + /* Start a sack timer or QUEUE a SACK for sending */ + sctp_sack_check(stcb, 1, was_a_gap, &abort_flag); + if (abort_flag) + return (2); + + return (0); +} + +static void +sctp_handle_segments(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_sack_chunk *ch, u_long last_tsn, u_long *biggest_tsn_acked, + u_long *biggest_newly_acked_tsn, int num_seg, int *ecn_seg_sums) +{ + /************************************************/ + /* process fragments and update sendqueue */ + /************************************************/ + struct sctp_sack *sack; + struct sctp_gap_ack_block *frag; + struct sctp_tmit_chunk *tp1; + int i; + unsigned int j; +#ifdef SCTP_FR_LOGGING + int num_frs=0; +#endif + uint16_t frag_strt, frag_end, primary_flag_set; + u_long last_frag_high; + + if (asoc->primary_destination->dest_state & SCTP_ADDR_SWITCH_PRIMARY) { + primary_flag_set = 1; + } else { + primary_flag_set = 0; + } + + sack = &ch->sack; + frag = (struct sctp_gap_ack_block *)((caddr_t)sack + + sizeof(struct sctp_sack)); + tp1 = NULL; + last_frag_high = 0; + for (i = 0; i < num_seg; i++) { + frag_strt = ntohs(frag->start); + frag_end = ntohs(frag->end); + /* some sanity checks on the fargment offsets */ + if (frag_strt > frag_end) { + /* this one is malformed, skip */ + frag++; + continue; + } + if (compare_with_wrap((frag_end+last_tsn), *biggest_tsn_acked, + MAX_TSN)) + *biggest_tsn_acked = frag_end+last_tsn; + + /* mark acked dgs and find out the highestTSN being acked */ + if (tp1 == NULL) { + tp1 = TAILQ_FIRST(&asoc->sent_queue); + + /* save the locations of the last frags */ + last_frag_high = frag_end + last_tsn; + } else { + /* + * now lets see if we need to reset the queue + * due to a out-of-order SACK fragment + */ + if (compare_with_wrap(frag_strt+last_tsn, + last_frag_high, MAX_TSN)) { + /* + * if the new frag starts after the last TSN + * frag covered, we are ok + * and this one is beyond the last one + */ + ; + } else { + /* + * ok, they have reset us, so we need to reset + * the queue this will cause extra hunting but + * hey, they chose the performance + * hit when they failed to order there gaps.. + */ + tp1 = TAILQ_FIRST(&asoc->sent_queue); + } + last_frag_high = frag_end + last_tsn; + } + for (j = frag_strt + last_tsn; j <= frag_end + last_tsn; j++) { + while (tp1) { +#ifdef SCTP_FR_LOGGING + if (tp1->rec.data.doing_fast_retransmit) + num_frs++; +#endif + + if (tp1->rec.data.TSN_seq == j) { + if (tp1->sent != SCTP_DATAGRAM_UNSENT) { + /* must be held until cum-ack passes */ + /* ECN Nonce: Add the nonce value to the sender's nonce sum */ + if (tp1->sent < SCTP_DATAGRAM_ACKED) { + /* + * If it is less than + * ACKED, it is now + * no-longer in flight. + * Higher values may + * already be set via + * previous Gap Ack + * Blocks... + * i.e. ACKED or MARKED. + */ + if (compare_with_wrap(tp1->rec.data.TSN_seq, + *biggest_newly_acked_tsn, + MAX_TSN)) { + *biggest_newly_acked_tsn = + tp1->rec.data.TSN_seq; + } + tp1->whoTo->flight_size -= tp1->book_size; + if (tp1->whoTo->flight_size < 0) { + tp1->whoTo->flight_size = 0; + } + asoc->total_flight -= + tp1->book_size; + + if (asoc->total_flight < 0) { + asoc->total_flight = 0; + } + + asoc->total_flight_count--; + if (asoc->total_flight_count < 0) { + asoc->total_flight_count = 0; + } + + if (tp1->snd_count < 2) { + /* True non-retransmited chunk */ + tp1->whoTo->net_ack2 += + tp1->send_size; + + /* update RTO too? */ + if (tp1->do_rtt) { + tp1->whoTo->RTO = + sctp_calculate_rto(stcb, + asoc, + tp1->whoTo, + &tp1->sent_rcv_time); + tp1->whoTo->rto_pending = 0; + tp1->do_rtt = 0; + } + } + } + if (tp1->sent <= SCTP_DATAGRAM_RESEND && + tp1->sent != SCTP_DATAGRAM_UNSENT && + compare_with_wrap(tp1->rec.data.TSN_seq, + asoc->this_sack_highest_gap, + MAX_TSN)) { + asoc->this_sack_highest_gap = + tp1->rec.data.TSN_seq; + if (primary_flag_set) { + tp1->whoTo->cacc_saw_newack = 1; + } + } + if (tp1->sent == SCTP_DATAGRAM_RESEND) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & + SCTP_DEBUG_INDATA3) { + printf("Hmm. one that is in RESEND that is now ACKED\n"); + } +#endif + asoc->sent_queue_retran_cnt--; +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xB2, + (asoc->sent_queue_retran_cnt & 0x000000ff)); +#endif + + if (asoc->sent_queue_retran_cnt < 0) { + printf("huh3 retran went negative?\n"); +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(30, + inp, tcb, + NULL); +#else + asoc->sent_queue_retran_cnt = 0; +#endif + } + } + (*ecn_seg_sums) += tp1->rec.data.ect_nonce; + (*ecn_seg_sums) &= SCTP_SACK_NONCE_SUM; + tp1->sent = SCTP_DATAGRAM_MARKED; + } + break; + } /* if (tp1->TSN_seq == j) */ + if (compare_with_wrap(tp1->rec.data.TSN_seq, j, + MAX_TSN)) + break; + tp1 = TAILQ_NEXT(tp1, sctp_next); + }/* end while (tp1) */ + } /* end for (j = fragStart */ + frag++; /* next one */ + } +#ifdef SCTP_FR_LOGGING + if (num_frs) + sctp_log_fr(*biggest_tsn_acked, *biggest_newly_acked_tsn, + last_tsn, SCTP_FR_LOG_BIGGEST_TSNS); +#endif +} + +static void +sctp_check_for_revoked(struct sctp_association *asoc, u_long cum_ack, + u_long biggest_tsn_acked) +{ + struct sctp_tmit_chunk *tp1; + int tot_revoked=0; + + tp1 = TAILQ_FIRST(&asoc->sent_queue); + while (tp1) { + if (compare_with_wrap(tp1->rec.data.TSN_seq, cum_ack, + MAX_TSN)) { + /* + * ok this guy is either ACK or MARKED. If it is ACKED + * it has been previously acked but not this time i.e. + * revoked. If it is MARKED it was ACK'ed again. + */ + if (tp1->sent == SCTP_DATAGRAM_ACKED) { + /* it has been revoked */ + /* + * We do NOT add back to flight size here since + * it is really NOT in flight. Resend (when/if + * it occurs will add to flight size + */ + tp1->sent = SCTP_DATAGRAM_SENT; + tot_revoked++; + } else if (tp1->sent == SCTP_DATAGRAM_MARKED) { + /* it has been re-acked in this SACK */ + tp1->sent = SCTP_DATAGRAM_ACKED; + } + } + if (compare_with_wrap(tp1->rec.data.TSN_seq, biggest_tsn_acked, + MAX_TSN)) { + /* above the sack */ + break; + } + if (tp1->sent == SCTP_DATAGRAM_UNSENT) + break; + tp1 = TAILQ_NEXT(tp1, sctp_next); + } + if (tot_revoked > 0) { + /* Setup the ecn nonce re-sync point. We + * do this since once data is revoked + * we begin to retransmit things, which + * do NOT have the ECN bits set. This means + * we are now out of sync and must wait until + * we get back in sync with the peer to + * check ECN bits. + */ + tp1 = TAILQ_FIRST(&asoc->send_queue); + if (tp1 == NULL) { + asoc->nonce_resync_tsn = asoc->sending_seq; + } else { + asoc->nonce_resync_tsn = tp1->rec.data.TSN_seq; + } + asoc->nonce_wait_for_ecne = 0; + asoc->nonce_sum_check = 0; + } + +} + +extern int sctp_peer_chunk_oh; + +static void +sctp_strike_gap_ack_chunks(struct sctp_tcb *stcb, struct sctp_association *asoc, + u_long biggest_tsn_acked, int strike_enabled, + u_long biggest_tsn_newly_acked, int accum_moved) +{ + struct sctp_tmit_chunk *tp1; + int strike_flag=0; + struct timeval now; + int tot_retrans=0; + u_int32_t sending_seq; + int primary_switch_active = 0; + int double_switch_active = 0; + + /* select the sending_seq, this is + * either the next thing ready to + * be sent but not transmitted, OR, + * the next seq we assign. + */ + tp1 = TAILQ_FIRST(&stcb->asoc.send_queue); + if (tp1 == NULL) { + sending_seq = asoc->sending_seq; + } else { + sending_seq = tp1->rec.data.TSN_seq; + } + + if (asoc->primary_destination->dest_state & SCTP_ADDR_SWITCH_PRIMARY) { + primary_switch_active = 1; + } + if (asoc->primary_destination->dest_state & SCTP_ADDR_DOUBLE_SWITCH) { + double_switch_active = 1; + } + if (stcb->asoc.peer_supports_prsctp ) { + SCTP_GETTIME_TIMEVAL(&now); + } + tp1 = TAILQ_FIRST(&asoc->sent_queue); + while (tp1) { + strike_flag=0; + if (compare_with_wrap(tp1->rec.data.TSN_seq, biggest_tsn_acked, + MAX_TSN) || + tp1->sent == SCTP_DATAGRAM_UNSENT) { + /* done */ + break; + } + if ((tp1->flags & (SCTP_PR_SCTP_ENABLED|SCTP_PR_SCTP_BUFFER)) == + SCTP_PR_SCTP_ENABLED && + tp1->sent < SCTP_DATAGRAM_ACKED) { + /* Is it expired? */ +#ifndef __FreeBSD__ + if (timercmp(&now, &tp1->rec.data.timetodrop, >)) +#else + if (timevalcmp(&now, &tp1->rec.data.timetodrop, >)) +#endif + { + /* Yes so drop it */ + if (tp1->data != NULL) { + sctp_release_pr_sctp_chunk(stcb, tp1, + (SCTP_RESPONSE_TO_USER_REQ|SCTP_NOTIFY_DATAGRAM_SENT), + &asoc->sent_queue); + } + tp1 = TAILQ_NEXT(tp1, sctp_next); + continue; + } + } + + if (compare_with_wrap(tp1->rec.data.TSN_seq, + asoc->this_sack_highest_gap, MAX_TSN)) { + /* we are beyond the tsn in the sack */ + break; + } + if (tp1->sent >= SCTP_DATAGRAM_RESEND) { + /* either a RESEND, ACKED, or MARKED */ + /* skip */ + tp1 = TAILQ_NEXT(tp1, sctp_next); + continue; + } + if (primary_switch_active && (strike_enabled == 0)) { + if (tp1->whoTo != asoc->primary_destination) { + /* + * We can only strike things on the primary if + * the strike_enabled flag is clear + */ + tp1 = TAILQ_NEXT(tp1, sctp_next); + continue; + } + } else if (primary_switch_active) { + if (tp1->whoTo->cacc_saw_newack == 0) { + /* + * Only one was received but it was NOT + * this one. + */ + tp1 = TAILQ_NEXT(tp1, sctp_next); + continue; + } + } + if (double_switch_active && + (compare_with_wrap(asoc->primary_destination->next_tsn_at_change, + tp1->rec.data.TSN_seq, MAX_TSN))) { + /* + * With a double switch we do NOT mark unless we + * are beyond the switch point. + */ + tp1 = TAILQ_NEXT(tp1, sctp_next); + continue; + } + /* + * Here we check to see if we were have already done a FR + * and if so we see if the biggest TSN we saw in the sack is + * smaller than the recovery point. If so we don't strike the + * tsn... otherwise we CAN strike the TSN. + */ + if (accum_moved && asoc->fast_retran_loss_recovery) { + /* + * Strike the TSN if in fast-recovery and + * cum-ack moved. + */ + tp1->sent++; + } else if (tp1->rec.data.doing_fast_retransmit) { + /* + * For those that have done a FR we must + * take special consideration if we strike. I.e + * the biggest_newly_acked must be higher + * than the sending_seq at the time we did + * the FR. + */ +#ifdef SCTP_FR_TO_ALTERNATE + /* + * If FR's go to new networks, then we + * must only do this for singly homed asoc's. However + * if the FR's go to the same network (Armando's work) + * then its ok to FR multiple times. + */ + if (asoc->numnets < 2) +#else + if (1) +#endif + { + if ((compare_with_wrap(biggest_tsn_newly_acked, + tp1->rec.data.fast_retran_tsn, MAX_TSN)) || + (biggest_tsn_newly_acked == + tp1->rec.data.fast_retran_tsn)) { + /* + * Strike the TSN, since this ack is + * beyond where things were when we did + * a FR. + */ +#ifdef SCTP_FR_LOGGING + sctp_log_fr(biggest_tsn_newly_acked, + tp1->rec.data.TSN_seq, + tp1->rec.data.fast_retran_tsn, + SCTP_FR_LOG_STRIKE_CHUNK); +#endif + tp1->sent++; + strike_flag=1; + } + } + } else if (compare_with_wrap(tp1->rec.data.TSN_seq, + biggest_tsn_newly_acked, MAX_TSN)) { + /* + * We don't strike these: + * This is the HTNA algorithm i.e. we don't strike + * If our TSN is larger than the Highest TSN Newly + * Acked. + */ + ; + } else { + /* Strike the TSN */ + tp1->sent++; + } + if (tp1->sent == SCTP_DATAGRAM_RESEND) { + /* Increment the count to resend */ + struct sctp_nets *alt; + +#ifdef SCTP_FR_LOGGING + sctp_log_fr(tp1->rec.data.TSN_seq, tp1->snd_count, + 0, SCTP_FR_MARKED); +#endif + if (strike_flag) { + /* This is a subsequent FR */ + sctp_pegs[SCTP_DUP_FR]++; + } + asoc->sent_queue_retran_cnt++; +#ifdef SCTP_FR_TO_ALTERNATE + /* Can we find an alternate? */ + alt = sctp_find_alternate_net(stcb, tp1->whoTo); +#else + /* + * default behavior is to NOT retransmit FR's + * to an alternate. Armando Caro's paper details + * why. + */ + alt = tp1->whoTo; +#endif + tp1->rec.data.doing_fast_retransmit = 1; + tot_retrans++; + /* mark the sending seq for possible subsequent FR's */ + if (TAILQ_EMPTY(&asoc->send_queue)) { + /* + * If the queue of send is empty then its the + * next sequence number that will be assigned so + * we subtract one from this to get the one we + * last sent. + */ + tp1->rec.data.fast_retran_tsn = sending_seq - 1; + } else { + /* + * If there are chunks on the send queue + * (unsent data that has made it from the + * stream queues but not out the door, we take + * the first one (which will have the lowest + * TSN) and subtract one to get the one we last + * sent. + */ + struct sctp_tmit_chunk *ttt; + ttt = TAILQ_FIRST(&asoc->send_queue); + tp1->rec.data.fast_retran_tsn = + ttt->rec.data.TSN_seq - 1; + } + if (tp1->do_rtt) { + /* + * this guy had a RTO calculation pending on it, + * cancel it + */ + tp1->whoTo->rto_pending = 0; + tp1->do_rtt = 0; + } + /* fix counts and things */ + + tp1->whoTo->net_ack++; + tp1->whoTo->flight_size -= tp1->book_size; + if (tp1->whoTo->flight_size < 0) { + tp1->whoTo->flight_size = 0; + } +#ifdef SCTP_LOG_RWND + sctp_log_rwnd(SCTP_INCREASE_PEER_RWND, + asoc->peers_rwnd , tp1->send_size, sctp_peer_chunk_oh); +#endif + /* add back to the rwnd */ + asoc->peers_rwnd += (tp1->send_size + sctp_peer_chunk_oh); + + /* remove from the total flight */ + asoc->total_flight -= tp1->book_size; + if (asoc->total_flight < 0) { + asoc->total_flight = 0; + } + asoc->total_flight_count--; + if (asoc->total_flight_count < 0) { + asoc->total_flight_count = 0; + } + if (alt != tp1->whoTo) { + /* yes, there is an alternate. */ + sctp_free_remote_addr(tp1->whoTo); + tp1->whoTo = alt; + alt->ref_count++; + } + } + tp1 = TAILQ_NEXT(tp1, sctp_next); + } /* while (tp1) */ + + if (tot_retrans > 0) { + /* Setup the ecn nonce re-sync point. We + * do this since once we go to FR something + * we introduce a Karn's rule scenario and + * won't know the totals for the ECN bits. + */ + asoc->nonce_resync_tsn = sending_seq; + asoc->nonce_wait_for_ecne = 0; + asoc->nonce_sum_check = 0; + } + +} + +struct sctp_tmit_chunk * +sctp_try_advance_peer_ack_point(struct sctp_tcb *stcb, + struct sctp_association *asoc) +{ + struct sctp_tmit_chunk *tp1, *tp2, *a_adv=NULL; + struct timeval now; + int now_filled=0; + + if (asoc->peer_supports_prsctp == 0) { + return (NULL); + } + tp1 = TAILQ_FIRST(&asoc->sent_queue); + while (tp1) { + if (tp1->sent != SCTP_FORWARD_TSN_SKIP && + tp1->sent != SCTP_DATAGRAM_RESEND) { + /* no chance to advance, out of here */ + break; + } + if ((tp1->flags & SCTP_PR_SCTP_ENABLED) == 0) { + /* + * We can't fwd-tsn past any that are reliable + * aka retransmitted until the asoc fails. + */ + break; + } + if (!now_filled) { + SCTP_GETTIME_TIMEVAL(&now); + now_filled = 1; + } + tp2 = TAILQ_NEXT(tp1, sctp_next); + /* + * now we got a chunk which is marked for another + * retransmission to a PR-stream but has run + * out its chances already maybe OR has been + * marked to skip now. Can we skip it if its a + * resend? + */ + if (tp1->sent == SCTP_DATAGRAM_RESEND && + (tp1->flags & SCTP_PR_SCTP_BUFFER) == 0) { + /* + * Now is this one marked for resend and its time + * is now up? + */ +#ifndef __FreeBSD__ + if (timercmp(&now, &tp1->rec.data.timetodrop, >)) +#else + if (timevalcmp(&now, &tp1->rec.data.timetodrop, >)) +#endif + { + /* Yes so drop it */ + if (tp1->data) { + sctp_release_pr_sctp_chunk(stcb, tp1, + (SCTP_RESPONSE_TO_USER_REQ|SCTP_NOTIFY_DATAGRAM_SENT), + &asoc->sent_queue); + } + } else { + /* + * No, we are done when hit one for resend whos + * time as not expired. + */ + break; + } + } + /* + * Ok now if this chunk is marked to drop it + * we can clean up the chunk, advance our peer ack point + * and we can check the next chunk. + */ + if (tp1->sent == SCTP_FORWARD_TSN_SKIP) { + /* advance PeerAckPoint goes forward */ + asoc->advanced_peer_ack_point = tp1->rec.data.TSN_seq; + a_adv = tp1; + /* + * we don't want to de-queue it here. Just wait for the + * next peer SACK to come with a new cumTSN and then + * the chunk will be droped in the normal fashion. + */ + if (tp1->data) { + sctp_free_bufspace(stcb, asoc, tp1); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { + printf("--total out:%lu total_mbuf_out:%lu\n", + (u_long)asoc->total_output_queue_size, + (u_long)asoc->total_output_mbuf_queue_size); + } +#endif + /* + * Maybe there should be another notification + * type + */ + sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, stcb, + (SCTP_RESPONSE_TO_USER_REQ|SCTP_NOTIFY_DATAGRAM_SENT), + tp1); + sctp_m_freem(tp1->data); + tp1->data = NULL; + sctp_sowwakeup(stcb->sctp_ep, + stcb->sctp_socket); + } + } else { + /* If it is still in RESEND we can advance no further */ + break; + } + /* + * If we hit here we just dumped tp1, move to next + * tsn on sent queue. + */ + tp1 = tp2; + } + return (a_adv); +} + +#ifdef SCTP_HIGH_SPEED +struct sctp_hs_raise_drop { + int32_t cwnd; + int32_t increase; + int32_t drop_percent; +}; + +#define SCTP_HS_TABLE_SIZE 73 + +struct sctp_hs_raise_drop sctp_cwnd_adjust[SCTP_HS_TABLE_SIZE] = { + {38,1,50}, /* 0 */ + {118,2,44}, /* 1 */ + {221,3,41}, /* 2 */ + {347,4,38}, /* 3 */ + {495,5,37}, /* 4 */ + {663,6,35}, /* 5 */ + {851,7,34}, /* 6 */ + {1058,8,33}, /* 7 */ + {1284,9,32}, /* 8 */ + {1529,10,31}, /* 9 */ + {1793,11,30}, /* 10 */ + {2076,12,29}, /* 11 */ + {2378,13,28}, /* 12 */ + {2699,14,28}, /* 13 */ + {3039,15,27}, /* 14 */ + {3399,16,27}, /* 15 */ + {3778,17,26}, /* 16 */ + {4177,18,26}, /* 17 */ + {4596,19,25}, /* 18 */ + {5036,20,25}, /* 19 */ + {5497,21,24}, /* 20 */ + {5979,22,24}, /* 21 */ + {6483,23,23}, /* 22 */ + {7009,24,23}, /* 23 */ + {7558,25,22}, /* 24 */ + {8130,26,22}, /* 25 */ + {8726,27,22}, /* 26 */ + {9346,28,21}, /* 27 */ + {9991,29,21}, /* 28 */ + {10661,30,21}, /* 29 */ + {11358,31,20}, /* 30 */ + {12082,32,20}, /* 31 */ + {12834,33,20}, /* 32 */ + {13614,34,19}, /* 33 */ + {14424,35,19}, /* 34 */ + {15265,36,19}, /* 35 */ + {16137,37,19}, /* 36 */ + {17042,38,18}, /* 37 */ + {17981,39,18}, /* 38 */ + {18955,40,18}, /* 39 */ + {19965,41,17}, /* 40 */ + {21013,42,17}, /* 41 */ + {22101,43,17}, /* 42 */ + {23230,44,17}, /* 43 */ + {24402,45,16}, /* 44 */ + {25618,46,16}, /* 45 */ + {26881,47,16}, /* 46 */ + {28193,48,16}, /* 47 */ + {29557,49,15}, /* 48 */ + {30975,50,15}, /* 49 */ + {32450,51,15}, /* 50 */ + {33986,52,15}, /* 51 */ + {35586,53,14}, /* 52 */ + {37253,54,14}, /* 53 */ + {38992,55,14}, /* 54 */ + {40808,56,14}, /* 55 */ + {42707,57,13}, /* 56 */ + {44694,58,13}, /* 57 */ + {46776,59,13}, /* 58 */ + {48961,60,13}, /* 59 */ + {51258,61,13}, /* 60 */ + {53677,62,12}, /* 61 */ + {56230,63,12}, /* 62 */ + {58932,64,12}, /* 63 */ + {61799,65,12}, /* 64 */ + {64851,66,11}, /* 65 */ + {68113,67,11}, /* 66 */ + {71617,68,11}, /* 67 */ + {75401,69,10}, /* 68 */ + {79517,70,10}, /* 69 */ + {84035,71,10}, /* 70 */ + {89053,72,10}, /* 71 */ + {94717,73,9} /* 72 */ +}; + +static void +sctp_hs_cwnd_increase(struct sctp_nets *net) +{ + int cur_val, i, indx, incr; + + cur_val = net->cwnd >> 10; + indx = SCTP_HS_TABLE_SIZE - 1; + + if (cur_val < sctp_cwnd_adjust[0].cwnd) { + /* normal mode */ + if (net->net_ack > net->mtu) { + net->cwnd += net->mtu; +#ifdef SCTP_CWND_LOGGING + sctp_log_cwnd(net, net->mtu, SCTP_CWND_LOG_FROM_SS); +#endif + } else { + net->cwnd += net->net_ack; +#ifdef SCTP_CWND_LOGGING + sctp_log_cwnd(net, net->net_ack, SCTP_CWND_LOG_FROM_SS); +#endif + } + } else { + for (i=net->last_hs_used; ilast_hs_used = indx; + incr = ((sctp_cwnd_adjust[indx].increase) << 10); + net->cwnd += incr; +#ifdef SCTP_CWND_LOGGING + sctp_log_cwnd(net, incr, SCTP_CWND_LOG_FROM_SS); +#endif + } +} + +static void +sctp_hs_cwnd_decrease(struct sctp_nets *net) +{ + int cur_val, i, indx; +#ifdef SCTP_CWND_LOGGING + int old_cwnd = net->cwnd; +#endif + + cur_val = net->cwnd >> 10; + indx = net->last_hs_used; + if (cur_val < sctp_cwnd_adjust[0].cwnd) { + /* normal mode */ + net->ssthresh = net->cwnd / 2; + if (net->ssthresh < (net->mtu*2)) { + net->ssthresh = 2 * net->mtu; + } + net->cwnd = net->ssthresh; +#ifdef SCTP_CWND_LOGGING + sctp_log_cwnd(net, (net->cwnd-old_cwnd), SCTP_CWND_LOG_FROM_FR); +#endif + } else { + /* drop by the proper amount */ + net->ssthresh = net->cwnd - (int)((net->cwnd / 100) * + sctp_cwnd_adjust[net->last_hs_used].drop_percent); + net->cwnd = net->ssthresh; + /* now where are we */ + indx = net->last_hs_used; + cur_val = net->cwnd >> 10; + /* reset where we are in the table */ + if (cur_val < sctp_cwnd_adjust[0].cwnd) { + /* feel out of hs */ + net->last_hs_used = 0; + } else { + for (i = indx; i >= 1; i--) { + if (cur_val > sctp_cwnd_adjust[i - 1].cwnd) { + break; + } + } + net->last_hs_used = indx; + } + } +} +#endif + +void +sctp_handle_sack(struct sctp_sack_chunk *ch, struct sctp_tcb *stcb, + struct sctp_nets *net_from, int *abort_now) +{ + struct sctp_association *asoc; + struct sctp_sack *sack; + struct sctp_tmit_chunk *tp1, *tp2; + u_long cum_ack, last_tsn, biggest_tsn_acked, biggest_tsn_newly_acked; + uint16_t num_seg; + unsigned int sack_length; + uint32_t send_s; + int some_on_streamwheel; + long j; + int strike_enabled = 0, cnt_of_cacc = 0; + int accum_moved = 0; + int marking_allowed = 1; + int will_exit_fast_recovery=0; + u_int32_t a_rwnd; + struct sctp_nets *net = NULL; + int nonce_sum_flag, ecn_seg_sums=0; + asoc = &stcb->asoc; + + /* + * Handle the incoming sack on data I have been sending. + */ + + /* + * we take any chance we can to service our queues since we + * cannot get awoken when the socket is read from :< + */ + asoc->overall_error_count = 0; + + if (asoc->sent_queue_retran_cnt) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Handling SACK for asoc:%p retran:%d\n", + asoc, asoc->sent_queue_retran_cnt); + } +#endif + } + + sctp_service_queues(stcb, asoc, 0); + + /* + * Now perform the actual SACK handling: + * 1) Verify that it is not an old sack, if so discard. + * 2) If there is nothing left in the send queue (cum-ack is equal + * to last acked) then you have a duplicate too, update any rwnd + * change and verify no timers are running. then return. + * 3) Process any new consequtive data i.e. cum-ack moved + * process these first and note that it moved. + * 4) Process any sack blocks. + * 5) Drop any acked from the queue. + * 6) Check for any revoked blocks and mark. + * 7) Update the cwnd. + * 8) Nothing left, sync up flightsizes and things, stop all timers + * and also check for shutdown_pending state. If so then go ahead + * and send off the shutdown. If in shutdown recv, send off the + * shutdown-ack and start that timer, Ret. + * 9) Strike any non-acked things and do FR procedure if needed being + * sure to set the FR flag. + * 10) Do pr-sctp procedures. + * 11) Apply any FR penalties. + * 12) Assure we will SACK if in shutdown_recv state. + */ + + j = 0; + sack_length = ntohs(ch->ch.chunk_length); + if (sack_length < sizeof(struct sctp_sack_chunk)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Bad size on sack chunk .. to small\n"); + } +#endif + return; + } + /* ECN Nonce */ + nonce_sum_flag = ch->ch.chunk_flags & SCTP_SACK_NONCE_SUM; + sack = &ch->sack; + cum_ack = last_tsn = ntohl(sack->cum_tsn_ack); + num_seg = ntohs(sack->num_gap_ack_blks); + + /* reality check */ + if (TAILQ_EMPTY(&asoc->send_queue)) { + send_s = asoc->sending_seq; + } else { + tp1 = TAILQ_FIRST(&asoc->send_queue); + send_s = tp1->rec.data.TSN_seq; + } + + if (sctp_strict_sacks) { + if (cum_ack == send_s || + compare_with_wrap(cum_ack, send_s, MAX_TSN)) { + struct mbuf *oper; + /* + * no way, we have not even sent this TSN out yet. + * Peer is hopelessly messed up with us. + */ + hopeless_peer: + *abort_now = 1; + /* XXX */ + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, struct sctp_paramhdr *); + ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x30000002); + } + sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_PEER_FAULTY, oper); + return; + } + } + /* update the Rwnd of the peer */ + a_rwnd = (u_int32_t)ntohl(sack->a_rwnd); + if (asoc->sent_queue_retran_cnt) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("cum_ack:%lx num_seg:%u last_acked_seq:%x\n", + cum_ack, (u_int)num_seg, asoc->last_acked_seq); + } +#endif + } + if (compare_with_wrap(asoc->t3timeout_highest_marked, cum_ack, MAX_TSN)) { + /* we are not allowed to mark for FR */ + marking_allowed = 0; + } + /**********************/ + /* 1) check the range */ + /**********************/ + if (compare_with_wrap(asoc->last_acked_seq, last_tsn, MAX_TSN)) { + /* acking something behind */ + if (asoc->sent_queue_retran_cnt) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("The cum-ack is behind us\n"); + } +#endif + } + return; + } + + if (TAILQ_EMPTY(&asoc->sent_queue)) { + /* nothing left on sendqueue.. consider done */ +#ifdef SCTP_LOG_RWND + sctp_log_rwnd_set(SCTP_SET_PEER_RWND_VIA_SACK, + asoc->peers_rwnd, 0, 0, a_rwnd); +#endif + asoc->peers_rwnd = a_rwnd; + if (asoc->sent_queue_retran_cnt) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Huh? retran set but none on queue\n"); + } +#endif + asoc->sent_queue_retran_cnt = 0; + } + if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { + /* SWS sender side engages */ + asoc->peers_rwnd = 0; + } + /* stop any timers */ + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, + stcb, net); + net->partial_bytes_acked = 0; + net->flight_size = 0; + } + asoc->total_flight = 0; + asoc->total_flight_count = 0; + return; + } + /* + * We init netAckSz and netAckSz2 to 0. These are used to track 2 + * things. The total byte count acked is tracked in netAckSz AND + * netAck2 is used to track the total bytes acked that are un- + * amibguious and were never retransmitted. We track these on a + * per destination address basis. + */ + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + net->prev_cwnd = net->cwnd; + net->net_ack = 0; + net->net_ack2 = 0; + } + /* process the new consecutive TSN first */ + tp1 = TAILQ_FIRST(&asoc->sent_queue); + while (tp1) { + if (compare_with_wrap(last_tsn, tp1->rec.data.TSN_seq, + MAX_TSN) || + last_tsn == tp1->rec.data.TSN_seq) { + if (tp1->sent != SCTP_DATAGRAM_UNSENT) { + /* ECN Nonce: Add the nonce to the sender's nonce sum */ + asoc->nonce_sum_expect_base += tp1->rec.data.ect_nonce; + accum_moved = 1; + if (tp1->sent < SCTP_DATAGRAM_ACKED) { + /* + * If it is less than ACKED, it is now + * no-longer in flight. Higher values + * may occur during marking + */ + if ((tp1->whoTo->dest_state & + SCTP_ADDR_UNCONFIRMED) && + (tp1->snd_count < 2) ) { + /* + * If there was no retran and + * the address is un-confirmed + * and we sent there and are + * now sacked.. its confirmed, + * mark it so. + */ + tp1->whoTo->dest_state &= + ~SCTP_ADDR_UNCONFIRMED; + } + tp1->whoTo->flight_size -= + tp1->book_size; + if (tp1->whoTo->flight_size < 0) { + tp1->whoTo->flight_size = 0; + } + asoc->total_flight -= tp1->book_size; + if (asoc->total_flight < 0) { + asoc->total_flight = 0; + } + asoc->total_flight_count--; + if (asoc->total_flight_count < 0) { + asoc->total_flight_count = 0; + } + tp1->whoTo->net_ack += tp1->send_size; + if (tp1->snd_count < 2) { + /* True non-retransmited chunk */ + tp1->whoTo->net_ack2 += + tp1->send_size; + /* update RTO too? */ + if (tp1->do_rtt) { + tp1->whoTo->RTO = + sctp_calculate_rto(stcb, + asoc, tp1->whoTo, + &tp1->sent_rcv_time); + tp1->whoTo->rto_pending = 0; + tp1->do_rtt = 0; + } + } + } + if (tp1->sent == SCTP_DATAGRAM_RESEND) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA3) { + printf("Hmm. one that is in RESEND that is now ACKED\n"); + } +#endif + asoc->sent_queue_retran_cnt--; +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xB3, + (asoc->sent_queue_retran_cnt & 0x000000ff)); +#endif + if (asoc->sent_queue_retran_cnt < 0) { + printf("huh4 retran went negative?\n"); +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(31, inp, tcb, + NULL); +#else + asoc->sent_queue_retran_cnt = 0; +#endif + } + + } + tp1->sent = SCTP_DATAGRAM_ACKED; + } + } else { + break; + } + tp1 = TAILQ_NEXT(tp1, sctp_next); + } + /*******************************************/ + /* cancel ALL T3-send timer if accum moved */ + /*******************************************/ + if (accum_moved) { + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, + stcb, net); + } + } + biggest_tsn_newly_acked = biggest_tsn_acked = last_tsn; + /* always set this up to cum-ack */ + asoc->this_sack_highest_gap = last_tsn; + + if (((num_seg * sizeof (sizeof(struct sctp_gap_ack_block))) + sizeof(struct sctp_sack_chunk)) > sack_length) { + /* skip corrupt segments */ + strike_enabled = 0; + goto skip_segments; + } + + if (num_seg > 0) { + if (asoc->primary_destination->dest_state & + SCTP_ADDR_SWITCH_PRIMARY) { + /* clear the nets CACC flags */ + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + net->cacc_saw_newack = 0; + } + } + /* + * thisSackHighestGap will increase while handling NEW segments + */ + + sctp_handle_segments(stcb, asoc, ch, last_tsn, + &biggest_tsn_acked, &biggest_tsn_newly_acked, + num_seg, &ecn_seg_sums); + + if (sctp_strict_sacks) { + /* validate the biggest_tsn_acked in the gap acks + * if strict adherence is wanted. + */ + if ((biggest_tsn_acked == send_s) || + (compare_with_wrap(biggest_tsn_acked, send_s, MAX_TSN))) { + /* + * peer is either confused or we are under + * attack. We must abort. + */ + goto hopeless_peer; + } + } + + if (asoc->primary_destination->dest_state & + SCTP_ADDR_SWITCH_PRIMARY) { + /* clear the nets CACC flags */ + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + if (net->cacc_saw_newack) { + cnt_of_cacc++; + } + } + } + + } + + if (cnt_of_cacc < 2) { + strike_enabled = 1; + } else { + strike_enabled = 0; + } + skip_segments: + /********************************************/ + /* drop the acked chunks from the sendqueue */ + /********************************************/ + asoc->last_acked_seq = cum_ack; + if (asoc->primary_destination->dest_state & SCTP_ADDR_SWITCH_PRIMARY) { + if ((cum_ack == asoc->primary_destination->next_tsn_at_change) || + (compare_with_wrap(cum_ack, + asoc->primary_destination->next_tsn_at_change, MAX_TSN))) { + struct sctp_nets *lnet; + /* Turn off the switch flag for ALL addresses */ + TAILQ_FOREACH(lnet, &asoc->nets, sctp_next) { + asoc->primary_destination->dest_state &= + ~(SCTP_ADDR_SWITCH_PRIMARY|SCTP_ADDR_DOUBLE_SWITCH); + } + } + } + /* Drag along the t3 timeout point so we don't have a problem at wrap */ + if (marking_allowed) { + asoc->t3timeout_highest_marked = cum_ack; + } + tp1 = TAILQ_FIRST(&asoc->sent_queue); + do { + if (compare_with_wrap(tp1->rec.data.TSN_seq, cum_ack, + MAX_TSN)) { + break; + } + if (tp1->sent == SCTP_DATAGRAM_UNSENT) { + /* no more sent on list */ + break; + } + tp2 = TAILQ_NEXT(tp1, sctp_next); + TAILQ_REMOVE(&asoc->sent_queue, tp1, sctp_next); + if (tp1->data) { + sctp_free_bufspace(stcb, asoc, tp1); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { + printf("--total out:%lu total_mbuf_out:%lu\n", + (u_long)asoc->total_output_queue_size, + (u_long)asoc->total_output_mbuf_queue_size); + } +#endif + + sctp_m_freem(tp1->data); + if (tp1->flags & SCTP_PR_SCTP_BUFFER) { + asoc->sent_queue_cnt_removeable--; + } + + } + tp1->data = NULL; + asoc->sent_queue_cnt--; + sctp_free_remote_addr(tp1->whoTo); + sctppcbinfo.ipi_count_chunk--; + asoc->chunks_on_out_queue--; + + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is going negative"); + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, tp1); + sctppcbinfo.ipi_gencnt_chunk++; + sctp_sowwakeup(stcb->sctp_ep, stcb->sctp_socket); + tp1 = tp2; + } while (tp1 != NULL); + + + if (asoc->fast_retran_loss_recovery && accum_moved) { + if (compare_with_wrap(asoc->last_acked_seq, + asoc->fast_recovery_tsn, MAX_TSN) || + asoc->last_acked_seq == asoc->fast_recovery_tsn) { + /* Setup so we will exit RFC2582 fast recovery */ + will_exit_fast_recovery = 1; + } + } + + /* Check for revoked fragments if we hand + * fragments in a previous segment. If we + * had no previous fragments we cannot have + * a revoke issue. + */ + if (asoc->saw_sack_with_frags) + sctp_check_for_revoked(asoc, cum_ack, biggest_tsn_acked); + + if (num_seg) + asoc->saw_sack_with_frags = 1; + else + asoc->saw_sack_with_frags = 0; + + /******************************/ + /* update cwnd */ + /******************************/ + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + /* if nothing was acked on this destination skip it */ + if (net->net_ack == 0) + continue; + + if (net->net_ack2 > 0) { + /* + * Karn's rule applies to clearing error count, + * this is optional. + */ + net->error_count = 0; + if ((net->dest_state&SCTP_ADDR_NOT_REACHABLE) == + SCTP_ADDR_NOT_REACHABLE) { + /* addr came good */ + net->dest_state &= ~SCTP_ADDR_NOT_REACHABLE; + net->dest_state |= SCTP_ADDR_REACHABLE; + sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb, + SCTP_RECEIVED_SACK, (void *)net); + /* now was it the primary? if so restore */ + if (net->dest_state & SCTP_ADDR_WAS_PRIMARY) { + sctp_set_primary_addr(stcb, (struct sockaddr *)NULL, net); + } + } + } + + if (asoc->fast_retran_loss_recovery && + will_exit_fast_recovery == 0) { + /* If we are in loss recovery we skip any cwnd update */ + sctp_pegs[SCTP_CWND_SKIP]++; + goto skip_cwnd_update; + } + if (accum_moved) { + /* If the cumulative ack moved we can proceed */ + if (net->cwnd <= net->ssthresh) { + /* We are in slow start */ + if (net->flight_size + net->net_ack >= + net->cwnd ) { +#ifdef SCTP_HIGH_SPEED + sctp_hs_cwnd_increase(net); +#else + if (net->net_ack > net->mtu) { + net->cwnd += net->mtu; +#ifdef SCTP_CWND_LOGGING + sctp_log_cwnd(net, net->mtu, + SCTP_CWND_LOG_FROM_SS); +#endif + + } else { + net->cwnd += net->net_ack; +#ifdef SCTP_CWND_LOGGING + sctp_log_cwnd(net, net->net_ack, + SCTP_CWND_LOG_FROM_SS); +#endif + + } +#endif + sctp_pegs[SCTP_CWND_SS]++; + } else { + unsigned int dif; + sctp_pegs[SCTP_CWND_NOUSE_SS]++; + dif = net->cwnd - (net->flight_size + + net->net_ack); +#ifdef SCTP_CWND_LOGGING +/* sctp_log_cwnd(net, net->net_ack, + SCTP_CWND_LOG_NOADV_SS);*/ +#endif + if (dif > sctp_pegs[SCTP_CWND_DIFF_SA]) { + sctp_pegs[SCTP_CWND_DIFF_SA] = + dif; + sctp_pegs[SCTP_OQS_AT_SS] = + asoc->total_output_queue_size; + sctp_pegs[SCTP_SQQ_AT_SS] = + asoc->sent_queue_cnt; + sctp_pegs[SCTP_SQC_AT_SS] = + asoc->send_queue_cnt; + } + } + } else { + /* We are in congestion avoidance */ + if (net->flight_size + net->net_ack >= + net->cwnd) { + /* + * add to pba only if we had a cwnd's + * worth (or so) in flight OR the + * burst limit was applied. + */ + net->partial_bytes_acked += + net->net_ack; + + /* + * Do we need to increase + * (if pba is > cwnd)? + */ + if (net->partial_bytes_acked >= + net->cwnd) { + if (net->cwnd < + net->partial_bytes_acked) { + net->partial_bytes_acked -= + net->cwnd; + } else { + net->partial_bytes_acked = + 0; + } + net->cwnd += net->mtu; +#ifdef SCTP_CWND_LOGGING + sctp_log_cwnd(net, net->mtu, + SCTP_CWND_LOG_FROM_CA); +#endif + sctp_pegs[SCTP_CWND_CA]++; + } + } else { + unsigned int dif; + sctp_pegs[SCTP_CWND_NOUSE_CA]++; +#ifdef SCTP_CWND_LOGGING +/* sctp_log_cwnd(net, net->net_ack, + SCTP_CWND_LOG_NOADV_CA); +*/ +#endif + dif = net->cwnd - (net->flight_size + + net->net_ack); + if (dif > sctp_pegs[SCTP_CWND_DIFF_CA]) { + sctp_pegs[SCTP_CWND_DIFF_CA] = + dif; + sctp_pegs[SCTP_OQS_AT_CA] = + asoc->total_output_queue_size; + sctp_pegs[SCTP_SQQ_AT_CA] = + asoc->sent_queue_cnt; + sctp_pegs[SCTP_SQC_AT_CA] = + asoc->send_queue_cnt; + + } + + } + } + } else { + sctp_pegs[SCTP_CWND_NOCUM]++; + } + skip_cwnd_update: + /* + * NOW, according to Karn's rule do we need to restore the + * RTO timer back? Check our net_ack2. If not set then we + * have a ambiguity.. i.e. all data ack'd was sent to more + * than one place. + */ + + if (net->net_ack2) { + /* restore any doubled timers */ + net->RTO = ((net->lastsa >> 2) + net->lastsv) >> 1; + if (net->RTO < stcb->asoc.minrto) { + net->RTO = stcb->asoc.minrto; + } + if (net->RTO > stcb->asoc.maxrto) { + net->RTO = stcb->asoc.maxrto; + } + } + if (net->cwnd > sctp_pegs[SCTP_MAX_CWND]) { + sctp_pegs[SCTP_MAX_CWND] = net->cwnd; + } + } + /**********************************/ + /* Now what about shutdown issues */ + /**********************************/ + some_on_streamwheel = 0; + if (!TAILQ_EMPTY(&asoc->out_wheel)) { + /* Check to see if some data queued */ + struct sctp_stream_out *outs; + TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) { + if (!TAILQ_EMPTY(&outs->outqueue)) { + some_on_streamwheel = 1; + break; + } + } + } + if (TAILQ_EMPTY(&asoc->send_queue) && TAILQ_EMPTY(&asoc->sent_queue) && + some_on_streamwheel == 0) { + /* nothing left on sendqueue.. consider done */ + /* stop all timers */ +#ifdef SCTP_LOG_RWND + sctp_log_rwnd_set(SCTP_SET_PEER_RWND_VIA_SACK, + asoc->peers_rwnd, 0, 0, a_rwnd); +#endif + asoc->peers_rwnd = a_rwnd; + if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { + /* SWS sender side engages */ + asoc->peers_rwnd = 0; + } + /* stop any timers */ + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, + stcb, net); + net->flight_size = 0; + net->partial_bytes_acked = 0; + } + asoc->total_flight = 0; + asoc->total_flight_count = 0; + /* clean up */ + if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { + asoc->state = SCTP_STATE_SHUTDOWN_SENT; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("%s:%d sends a shutdown\n", + __FILE__, + __LINE__ + ); + } +#endif + sctp_send_shutdown(stcb, + stcb->asoc.primary_destination); + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, + stcb->sctp_ep, stcb, asoc->primary_destination); + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, + stcb->sctp_ep, stcb, asoc->primary_destination); + } else if (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) { + asoc->state = SCTP_STATE_SHUTDOWN_ACK_SENT; + + sctp_send_shutdown_ack(stcb, + stcb->asoc.primary_destination); + + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK, + stcb->sctp_ep, stcb, asoc->primary_destination); + } + return; + } + /* + * Now here we are going to recycle net_ack for a different + * use... HEADS UP. + */ + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + net->net_ack = 0; + } + if ((num_seg > 0) && marking_allowed) { + sctp_strike_gap_ack_chunks(stcb, asoc, biggest_tsn_acked, + strike_enabled, biggest_tsn_newly_acked, accum_moved); + } + + /*********************************************/ + /* Here we perform PR-SCTP procedures */ + /* (section 4.2) */ + /*********************************************/ + /* C1. update advancedPeerAckPoint */ + if (compare_with_wrap(cum_ack, asoc->advanced_peer_ack_point, MAX_TSN)) { + asoc->advanced_peer_ack_point = cum_ack; + } + /* C2. try to further move advancedPeerAckPoint ahead */ + if (asoc->peer_supports_prsctp) { + struct sctp_tmit_chunk *lchk; + lchk = sctp_try_advance_peer_ack_point(stcb, asoc); + /* C3. See if we need to send a Fwd-TSN */ + if (compare_with_wrap(asoc->advanced_peer_ack_point, cum_ack, + MAX_TSN)) { + /* + * ISSUE with ECN, see FWD-TSN processing for notes + * on issues that will occur when the ECN NONCE stuff + * is put into SCTP for cross checking. + */ + send_forward_tsn(stcb, asoc); + + /* ECN Nonce: Disable Nonce Sum check when FWD TSN is sent and store resync tsn*/ + asoc->nonce_sum_check = 0; + asoc->nonce_resync_tsn = asoc->advanced_peer_ack_point; + if (lchk) { + /* Assure a timer is up */ + sctp_timer_start(SCTP_TIMER_TYPE_SEND, + stcb->sctp_ep, stcb, lchk->whoTo); + } + } + } + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + if (asoc->fast_retran_loss_recovery == 0) { + /* out of a RFC2582 Fast recovery window? */ + if (net->net_ack > 0) { + /* + * per section 7.2.3, are there + * any destinations that had a fast + * retransmit to them. If so what we + * need to do is adjust ssthresh and + * cwnd. + */ + struct sctp_tmit_chunk *lchk; +#ifdef SCTP_HIGH_SPEED + sctp_hs_cwnd_decrease(net); +#else +#ifdef SCTP_CWND_LOGGING + int old_cwnd = net->cwnd; +#endif + net->ssthresh = net->cwnd / 2; + if (net->ssthresh < (net->mtu*2)) { + net->ssthresh = 2 * net->mtu; + } + net->cwnd = net->ssthresh; +#ifdef SCTP_CWND_LOGGING + sctp_log_cwnd(net, (net->cwnd-old_cwnd), + SCTP_CWND_LOG_FROM_FR); +#endif +#endif + + lchk = TAILQ_FIRST(&asoc->send_queue); + + net->partial_bytes_acked = 0; + /* Turn on fast recovery window */ + asoc->fast_retran_loss_recovery = 1; + if (lchk == NULL) { + /* Mark end of the window */ + asoc->fast_recovery_tsn = asoc->sending_seq - 1; + } else { + asoc->fast_recovery_tsn = lchk->rec.data.TSN_seq - 1; + } + + + /* Disable Nonce Sum Checking and store the resync tsn*/ + asoc->nonce_sum_check = 0; + asoc->nonce_resync_tsn = asoc->fast_recovery_tsn + 1; + + sctp_timer_stop(SCTP_TIMER_TYPE_SEND, + stcb->sctp_ep, stcb, net); + sctp_timer_start(SCTP_TIMER_TYPE_SEND, + stcb->sctp_ep, stcb, net); + } + } else if (net->net_ack > 0) { + /* + * Mark a peg that we WOULD have done a cwnd reduction + * but RFC2582 prevented this action. + */ + sctp_pegs[SCTP_FR_INAWINDOW]++; + } + } + + + /****************************************************************** + * Here we do the stuff with ECN Nonce checking. + * We basically check to see if the nonce sum flag was incorrect + * or if resynchronization needs to be done. Also if we catch a + * misbehaving receiver we give him the kick. + ******************************************************************/ + + if (asoc->ecn_nonce_allowed) { + if (asoc->nonce_sum_check) { + if (nonce_sum_flag != ((asoc->nonce_sum_expect_base + ecn_seg_sums) & SCTP_SACK_NONCE_SUM)) { + if (asoc->nonce_wait_for_ecne == 0) { + struct sctp_tmit_chunk *lchk; + lchk = TAILQ_FIRST(&asoc->send_queue); + asoc->nonce_wait_for_ecne = 1; + if (lchk) { + asoc->nonce_wait_tsn = lchk->rec.data.TSN_seq; + } else { + asoc->nonce_wait_tsn = asoc->sending_seq; + } + } else { + if (compare_with_wrap(asoc->last_acked_seq, asoc->nonce_wait_tsn, MAX_TSN) || + (asoc->last_acked_seq == asoc->nonce_wait_tsn)) { + /* Misbehaving peer. We need to react to this guy */ + printf("Mis-behaving peer detected\n"); + asoc->ecn_allowed = 0; + asoc->ecn_nonce_allowed = 0; + } + } + } + } else { + /* See if Resynchronization Possible */ + if (compare_with_wrap(asoc->last_acked_seq, asoc->nonce_resync_tsn, MAX_TSN)) { + asoc->nonce_sum_check = 1; + /* now we must calculate what the base + * is. We do this based on two things, we know + * the total's for all the segments gap-acked + * in the SACK, its stored in ecn_seg_sums. + * We also know the SACK's nonce sum, its + * in nonce_sum_flag. So we can build a truth + * table to back-calculate the new value of asoc->nonce_sum_expect_base: + * + * SACK-flag-Value Seg-Sums Base + * 0 0 0 + * 1 0 1 + * 0 1 1 + * 1 1 0 + */ + asoc->nonce_sum_expect_base = (ecn_seg_sums ^ nonce_sum_flag) & SCTP_SACK_NONCE_SUM; + } + } + } + /* Now are we exiting loss recovery ? */ + if (will_exit_fast_recovery) { + /* Ok, we must exit fast recovery */ + asoc->fast_retran_loss_recovery = 0; + } + if ((asoc->sat_t3_loss_recovery) && + ((compare_with_wrap(asoc->last_acked_seq, asoc->sat_t3_recovery_tsn, + MAX_TSN) || + (asoc->last_acked_seq == asoc->sat_t3_recovery_tsn)))) { + /* end satellite t3 loss recovery */ + asoc->sat_t3_loss_recovery = 0; + } + /* Adjust and set the new rwnd value */ +#ifdef SCTP_LOG_RWND + sctp_log_rwnd_set(SCTP_SET_PEER_RWND_VIA_SACK, + asoc->peers_rwnd, asoc->total_flight, (asoc->sent_queue_cnt * sctp_peer_chunk_oh), a_rwnd); +#endif + + asoc->peers_rwnd = sctp_sbspace_sub(a_rwnd, + (u_int32_t)(asoc->total_flight + (asoc->sent_queue_cnt * sctp_peer_chunk_oh))); + if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { + /* SWS sender side engages */ + asoc->peers_rwnd = 0; + } + /* + * Now we must setup so we have a timer up for anyone with + * outstanding data. + */ + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + struct sctp_tmit_chunk *chk; + TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { + if (chk->whoTo == net && + (chk->sent < SCTP_DATAGRAM_ACKED || + chk->sent == SCTP_FORWARD_TSN_SKIP)) { + /* + * Not ack'ed and still outstanding to this + * destination or marked and must be + * sacked after fwd-tsn sent. + */ + sctp_timer_start(SCTP_TIMER_TYPE_SEND, + stcb->sctp_ep, stcb, net); + break; + } + } + } +} + +void +sctp_update_acked(struct sctp_tcb *stcb, struct sctp_shutdown_chunk *cp, + struct sctp_nets *netp, int *abort_flag) +{ + /* Mutate a shutdown into a SACK */ + struct sctp_sack_chunk sack; + + /* Copy cum-ack */ + sack.sack.cum_tsn_ack = cp->cumulative_tsn_ack; + /* Arrange so a_rwnd does NOT change */ + sack.ch.chunk_type = SCTP_SELECTIVE_ACK; + sack.ch.chunk_flags = 0; + sack.ch.chunk_length = ntohs(sizeof(struct sctp_sack_chunk)); + sack.sack.a_rwnd = + htonl(stcb->asoc.peers_rwnd + stcb->asoc.total_flight); + /* + * no gaps in this one. This may cause a temporal view to reneging, + * but hopefully the second chunk is a true SACK in the packet and + * will correct this view. One will come soon after no matter what + * to fix this. + */ + sack.sack.num_gap_ack_blks = 0; + sack.sack.num_dup_tsns = 0; + /* Now call the SACK processor */ + sctp_handle_sack(&sack, stcb, netp, abort_flag); +} + +static void +sctp_kick_prsctp_reorder_queue(struct sctp_tcb *stcb, + struct sctp_stream_in *strmin) +{ + struct sctp_tmit_chunk *chk, *nchk; + struct sctp_association *asoc; + int tt; + + asoc = &stcb->asoc; + tt = strmin->last_sequence_delivered; + /* + * First deliver anything prior to and including the stream no that + * came in + */ + chk = TAILQ_FIRST(&strmin->inqueue); + while (chk) { + nchk = TAILQ_NEXT(chk, sctp_next); + if (compare_with_wrap(tt, chk->rec.data.stream_seq, MAX_SEQ) || + (tt == chk->rec.data.stream_seq)) { + /* this is deliverable now */ + TAILQ_REMOVE(&strmin->inqueue, chk, sctp_next); + /* subtract pending on streams */ + asoc->size_on_all_streams -= chk->send_size; + asoc->cnt_on_all_streams--; + /* deliver it to at least the delivery-q */ + sctp_deliver_data(stcb, &stcb->asoc, chk, 0); + } else { + /* no more delivery now. */ + break; + } + chk = nchk; + } + /* + * now we must deliver things in queue the normal way if any + * are now ready. + */ + tt = strmin->last_sequence_delivered + 1; + chk = TAILQ_FIRST(&strmin->inqueue); + while (chk) { + nchk = TAILQ_NEXT(chk, sctp_next); + if (tt == chk->rec.data.stream_seq) { + /* this is deliverable now */ + TAILQ_REMOVE(&strmin->inqueue, chk, sctp_next); + /* subtract pending on streams */ + asoc->size_on_all_streams -= chk->send_size; + asoc->cnt_on_all_streams--; + /* deliver it to at least the delivery-q */ + strmin->last_sequence_delivered = + chk->rec.data.stream_seq; + sctp_deliver_data(stcb, &stcb->asoc, chk, 0); + tt = strmin->last_sequence_delivered + 1; + } else { + break; + } + chk = nchk; + } + +} + +void +sctp_handle_forward_tsn(struct sctp_tcb *stcb, + struct sctp_forward_tsn_chunk *fwd, int *abort_flag) +{ + /* + * ISSUES that MUST be fixed for ECN! When we are the + * sender of the forward TSN, when the SACK comes back + * that acknowledges the FWD-TSN we must reset the + * NONCE sum to match correctly. This will get quite + * tricky since we may have sent more data interveneing and + * must carefully account for what the SACK says on the + * nonce and any gaps that are reported. This work + * will NOT be done here, but I note it here since + * it is really related to PR-SCTP and FWD-TSN's + */ + + /* The pr-sctp fwd tsn */ + /* + * here we will perform all the data receiver side steps for + * processing FwdTSN, as required in by pr-sctp draft: + * + * Assume we get FwdTSN(x): + * + * 1) update local cumTSN to x + * 2) try to further advance cumTSN to x + others we have + * 3) examine and update re-ordering queue on pr-in-streams + * 4) clean up re-assembly queue + * 5) Send a sack to report where we are. + */ + struct sctp_strseq *stseq; + struct sctp_association *asoc; + u_int32_t new_cum_tsn, gap, back_out_htsn; + unsigned int i, cnt_gone, fwd_sz, cumack_set_flag, m_size; + struct sctp_stream_in *strm; + struct sctp_tmit_chunk *chk, *at; + + cumack_set_flag = 0; + asoc = &stcb->asoc; + cnt_gone = 0; + if ((fwd_sz = ntohs(fwd->ch.chunk_length)) < sizeof(struct sctp_forward_tsn_chunk)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Bad size too small/big fwd-tsn\n"); + } +#endif + return; + } + m_size = (stcb->asoc.mapping_array_size << 3); + /*************************************************************/ + /* 1. Here we update local cumTSN and shift the bitmap array */ + /*************************************************************/ + new_cum_tsn = ntohl(fwd->new_cumulative_tsn); + + if (compare_with_wrap(asoc->cumulative_tsn, new_cum_tsn, MAX_TSN) || + asoc->cumulative_tsn == new_cum_tsn) { + /* Already got there ... */ + return; + } + + back_out_htsn = asoc->highest_tsn_inside_map; + if (compare_with_wrap(new_cum_tsn, asoc->highest_tsn_inside_map, + MAX_TSN)) { + asoc->highest_tsn_inside_map = new_cum_tsn; +#ifdef SCTP_MAP_LOGGING + sctp_log_map(0, 0, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); +#endif + } + /* + * now we know the new TSN is more advanced, let's find the + * actual gap + */ + if ((compare_with_wrap(new_cum_tsn, asoc->mapping_array_base_tsn, + MAX_TSN)) || + (new_cum_tsn == asoc->mapping_array_base_tsn)) { + gap = new_cum_tsn - asoc->mapping_array_base_tsn; + } else { + /* try to prevent underflow here */ + gap = new_cum_tsn + (MAX_TSN - asoc->mapping_array_base_tsn) + 1; + } + + if (gap > m_size || gap < 0) { + asoc->highest_tsn_inside_map = back_out_htsn; + if ((long)gap > sctp_sbspace(&stcb->sctp_socket->so_rcv)) { + /* + * out of range (of single byte chunks in the rwnd I + * give out) + * too questionable. better to drop it silently + */ + return; + } + if (asoc->highest_tsn_inside_map > + asoc->mapping_array_base_tsn) { + gap = asoc->highest_tsn_inside_map - + asoc->mapping_array_base_tsn; + } else { + gap = asoc->highest_tsn_inside_map + + (MAX_TSN - asoc->mapping_array_base_tsn) + 1; + } + cumack_set_flag = 1; + } + for (i = 0; i <= gap; i++) { + SCTP_SET_TSN_PRESENT(asoc->mapping_array, i); + } + /* + * Now after marking all, slide thing forward but no + * sack please. + */ + sctp_sack_check(stcb, 0, 0, abort_flag); + if (*abort_flag) + return; + + if (cumack_set_flag) { + /* + * fwd-tsn went outside my gap array - not a + * common occurance. Do the same thing we + * do when a cookie-echo arrives. + */ + asoc->highest_tsn_inside_map = new_cum_tsn - 1; + asoc->mapping_array_base_tsn = new_cum_tsn; + asoc->cumulative_tsn = asoc->highest_tsn_inside_map; +#ifdef SCTP_MAP_LOGGING + sctp_log_map(0, 3, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); +#endif + asoc->last_echo_tsn = asoc->highest_tsn_inside_map; + } + /*************************************************************/ + /* 2. Clear up re-assembly queue */ + /*************************************************************/ + + /* + * First service it if pd-api is up, just in case we can + * progress it forward + */ + if (asoc->fragmented_delivery_inprogress) { + sctp_service_reassembly(stcb, asoc, 0); + } + if (!TAILQ_EMPTY(&asoc->reasmqueue)) { + /* For each one on here see if we need to toss it */ + /* + * For now large messages held on the reasmqueue that are + * complete will be tossed too. We could in theory do more + * work to spin through and stop after dumping one msg + * aka seeing the start of a new msg at the head, and call + * the delivery function... to see if it can be delivered... + * But for now we just dump everything on the queue. + */ + chk = TAILQ_FIRST(&asoc->reasmqueue); + while (chk) { + at = TAILQ_NEXT(chk, sctp_next); + if (compare_with_wrap(asoc->cumulative_tsn, + chk->rec.data.TSN_seq, MAX_TSN) || + asoc->cumulative_tsn == chk->rec.data.TSN_seq) { + /* It needs to be tossed */ + TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next); + if (compare_with_wrap(chk->rec.data.TSN_seq, + asoc->tsn_last_delivered, MAX_TSN)) { + asoc->tsn_last_delivered = + chk->rec.data.TSN_seq; + asoc->str_of_pdapi = + chk->rec.data.stream_number; + asoc->ssn_of_pdapi = + chk->rec.data.stream_seq; + asoc->fragment_flags = + chk->rec.data.rcv_flags; + } + asoc->size_on_reasm_queue -= chk->send_size; + asoc->cnt_on_reasm_queue--; + cnt_gone++; + + /* Clear up any stream problem */ + if ((chk->rec.data.rcv_flags & SCTP_DATA_UNORDERED) != + SCTP_DATA_UNORDERED && + (compare_with_wrap(chk->rec.data.stream_seq, + asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered, + MAX_SEQ))) { + /* + * We must dump forward this streams + * sequence number if the chunk is not + * unordered that is being skipped. + * There is a chance that if the peer + * does not include the last fragment + * in its FWD-TSN we WILL have a problem + * here since you would have a partial + * chunk in queue that may not be + * deliverable. + * Also if a Partial delivery API as + * started the user may get a partial + * chunk. The next read returning a new + * chunk... really ugly but I see no way + * around it! Maybe a notify?? + */ + asoc->strmin[chk->rec.data.stream_number].last_sequence_delivered = + chk->rec.data.stream_seq; + } + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + } else { + /* + * Ok we have gone beyond the end of the + * fwd-tsn's mark. Some checks... + */ + if ((asoc->fragmented_delivery_inprogress) && + (chk->rec.data.rcv_flags & SCTP_DATA_FIRST_FRAG)) { + /* Special case PD-API is up and what we fwd-tsn' + * over includes one that had the LAST_FRAG. We + * no longer need to do the PD-API. + */ + asoc->fragmented_delivery_inprogress = 0; + sctp_ulp_notify(SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION, + stcb, SCTP_PARTIAL_DELIVERY_ABORTED, (void *)NULL); + + } + break; + } + chk = at; + } + } + if (asoc->fragmented_delivery_inprogress) { + /* + * Ok we removed cnt_gone chunks in the PD-API queue that + * were being delivered. So now we must turn off the + * flag. + */ + sctp_ulp_notify(SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION, + stcb, SCTP_PARTIAL_DELIVERY_ABORTED, (void *)NULL); + asoc->fragmented_delivery_inprogress = 0; + } + /*************************************************************/ + /* 3. Update the PR-stream re-ordering queues */ + /*************************************************************/ + stseq = (struct sctp_strseq *)((caddr_t)fwd + sizeof(*fwd)); + fwd_sz -= sizeof(*fwd); + { + /* New method. */ + int num_str, i; + num_str = fwd_sz/sizeof(struct sctp_strseq); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Using NEW method, %d strseq's reported in FWD-TSN\n", + num_str); + } +#endif + for (i = 0; i < num_str; i++) { + u_int16_t st; + unsigned char *xx; + /* Convert */ + xx = (unsigned char *)&stseq[i]; + st = ntohs(stseq[i].stream); + stseq[i].stream = st; + st = ntohs(stseq[i].sequence); + stseq[i].sequence = st; + /* now process */ + if (stseq[i].stream > asoc->streamincnt) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INDATA1) { + printf("Bogus stream number %d " + "streamincnt is %d\n", + stseq[i].stream, asoc->streamincnt); + } +#endif + /* + * It is arguable if we should continue. Since + * the peer sent bogus stream info we may be in + * deep trouble.. + * a return may be a better choice? + */ + continue; + } + strm = &asoc->strmin[stseq[i].stream]; + if (compare_with_wrap(stseq[i].sequence, + strm->last_sequence_delivered, MAX_SEQ)) { + /* Update the sequence number */ + strm->last_sequence_delivered = + stseq[i].sequence; + } + /* now kick the stream the new way */ + sctp_kick_prsctp_reorder_queue(stcb, strm); + } + } +} diff --git a/sys/netinet/sctp_indata.h b/sys/netinet/sctp_indata.h new file mode 100644 index 0000000000..265314c80d --- /dev/null +++ b/sys/netinet/sctp_indata.h @@ -0,0 +1,63 @@ +/* $KAME: sctp_indata.h,v 1.8 2004/08/17 04:06:17 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_indata.h,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +#ifndef __sctp_indata_h__ +#define __sctp_indata_h__ + +/* + * Copyright (C) 2002, 2003, 2004 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) +int sctp_deliver_data(struct sctp_tcb *, struct sctp_association *, + struct sctp_tmit_chunk *, int); + +void sctp_set_rwnd(struct sctp_tcb *, struct sctp_association *); + +void sctp_handle_sack(struct sctp_sack_chunk *, struct sctp_tcb *, + struct sctp_nets *, int *); + +/* draft-ietf-tsvwg-usctp */ +void sctp_handle_forward_tsn(struct sctp_tcb *, + struct sctp_forward_tsn_chunk *, int *); + +struct sctp_tmit_chunk * +sctp_try_advance_peer_ack_point(struct sctp_tcb *, struct sctp_association *); + +void sctp_service_queues(struct sctp_tcb *, struct sctp_association *, int have_lock); + +void sctp_update_acked(struct sctp_tcb *, struct sctp_shutdown_chunk *, + struct sctp_nets *, int *); + +int sctp_process_data(struct mbuf **, int, int *, int, struct sctphdr *, + struct sctp_inpcb *, struct sctp_tcb *, struct sctp_nets *, u_int32_t *); + +void sctp_sack_check(struct sctp_tcb *, int, int, int *); +#endif +#endif diff --git a/sys/netinet/sctp_input.c b/sys/netinet/sctp_input.c new file mode 100644 index 0000000000..dbdc2f1a90 --- /dev/null +++ b/sys/netinet/sctp_input.c @@ -0,0 +1,4420 @@ +/* $KAME: sctp_input.c,v 1.27 2005/03/06 16:04:17 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_input.c,v 1.1 2005/07/15 14:46:16 eirikn Exp $ */ + +/* + * Copyright (C) 2002, 2003, 2004 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if !(defined(__OpenBSD__) || defined(__APPLE__)) +#include "opt_ipsec.h" +#endif +#if defined(__FreeBSD__) +#include "opt_compat.h" +#include "opt_inet6.h" +#include "opt_inet.h" +#endif +#if defined(__NetBSD__) +#include "opt_inet.h" +#endif +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if (defined(__FreeBSD__) && __FreeBSD_version >= 500000) +#include +#else +#include +#endif +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef INET6 +#include +#include +#endif /* INET6 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __APPLE__ +#include +#elif !defined(__FreeBSD__) +#include +#endif + +#ifdef IPSEC +#ifndef __OpenBSD__ +#include +#include +#else +#undef IPSEC +#endif +#endif /*IPSEC*/ + +#include + +#ifdef SCTP_DEBUG +extern u_int32_t sctp_debug_on; +#endif + +/* INIT handler */ +static void +sctp_handle_init(struct mbuf *m, int iphlen, int offset, + struct sctphdr *sh, struct sctp_init_chunk *cp, struct sctp_inpcb *inp, + struct sctp_tcb *stcb, struct sctp_nets *net) +{ + struct sctp_init *init; + struct mbuf *op_err; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_init: handling INIT tcb:%p\n", stcb); + } +#endif + op_err = NULL; + init = &cp->init; + /* First are we accepting? */ + if (((inp->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING) == 0) || + (inp->sctp_socket->so_qlimit == 0)) { + sctp_abort_association(inp, stcb, m, iphlen, sh, op_err); + return; + } + if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_init_chunk)) { + /* Invalid length */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); + sctp_abort_association(inp, stcb, m, iphlen, sh, op_err); + return; + } + /* validate parameters */ + if (init->initiate_tag == 0) { + /* protocol error... send abort */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); + sctp_abort_association(inp, stcb, m, iphlen, sh, op_err); + return; + } + if (ntohl(init->a_rwnd) < SCTP_MIN_RWND) { + /* invalid parameter... send abort */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); + sctp_abort_association(inp, stcb, m, iphlen, sh, op_err); + return; + } + if (init->num_inbound_streams == 0) { + /* protocol error... send abort */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); + sctp_abort_association(inp, stcb, m, iphlen, sh, op_err); + return; + } + if (init->num_outbound_streams == 0) { + /* protocol error... send abort */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); + sctp_abort_association(inp, stcb, m, iphlen, sh, op_err); + return; + } + + /* send an INIT-ACK w/cookie */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("sctp_handle_init: sending INIT-ACK\n"); + } +#endif + + sctp_send_initiate_ack(inp, stcb, m, iphlen, offset, sh, cp); +} + +/* + * process peer "INIT/INIT-ACK" chunk + * returns value < 0 on error + */ + +static int +sctp_process_init(struct sctp_init_chunk *cp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_init *init; + struct sctp_association *asoc; + struct sctp_nets *lnet; + unsigned int i; + + init = &cp->init; + asoc = &stcb->asoc; + /* save off parameters */ + asoc->peer_vtag = ntohl(init->initiate_tag); + asoc->peers_rwnd = ntohl(init->a_rwnd); + + if (TAILQ_FIRST(&asoc->nets)) { + /* update any ssthresh's that may have a default */ + TAILQ_FOREACH(lnet, &asoc->nets, sctp_next) { + lnet->ssthresh = asoc->peers_rwnd; + } + } + if (asoc->pre_open_streams > ntohs(init->num_inbound_streams)) { + unsigned int newcnt; + struct sctp_stream_out *outs; + struct sctp_tmit_chunk *chk; + + /* cut back on number of streams */ + newcnt = ntohs(init->num_inbound_streams); + /* This if is probably not needed but I am cautious */ + if (asoc->strmout) { + /* First make sure no data chunks are trapped */ + for (i=newcnt; i < asoc->pre_open_streams; i++) { + outs = &asoc->strmout[i]; + chk = TAILQ_FIRST(&outs->outqueue); + while (chk) { + TAILQ_REMOVE(&outs->outqueue, chk, + sctp_next); + asoc->stream_queue_cnt--; + sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, + stcb, SCTP_NOTIFY_DATAGRAM_UNSENT, + chk); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = NULL; + chk->asoc = NULL; + /* Free the chunk */ + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&outs->outqueue); + } + } + } + /* cut back the count and abandon the upper streams */ + asoc->pre_open_streams = newcnt; + } + asoc->streamincnt = ntohs(init->num_outbound_streams); + if (asoc->streamincnt > MAX_SCTP_STREAMS) { + asoc->streamincnt = MAX_SCTP_STREAMS; + } + + asoc->streamoutcnt = asoc->pre_open_streams; + /* init tsn's */ + asoc->highest_tsn_inside_map = asoc->asconf_seq_in = ntohl(init->initial_tsn) - 1; +#ifdef SCTP_MAP_LOGGING + sctp_log_map(0, 5, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); +#endif + /* This is the next one we expect */ + asoc->str_reset_seq_in = asoc->asconf_seq_in + 1; + + asoc->mapping_array_base_tsn = ntohl(init->initial_tsn); + asoc->cumulative_tsn = asoc->asconf_seq_in; + asoc->last_echo_tsn = asoc->asconf_seq_in; + asoc->advanced_peer_ack_point = asoc->last_acked_seq; + /* open the requested streams */ + if (asoc->strmin != NULL) { + /* Free the old ones */ + FREE(asoc->strmin, M_PCB); + } + MALLOC(asoc->strmin, struct sctp_stream_in *, asoc->streamincnt * + sizeof(struct sctp_stream_in), M_PCB, M_NOWAIT); + if (asoc->strmin == NULL) { + /* we didn't get memory for the streams! */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("process_init: couldn't get memory for the streams!\n"); + } +#endif + return (-1); + } + for (i = 0; i < asoc->streamincnt; i++) { + asoc->strmin[i].stream_no = i; + asoc->strmin[i].last_sequence_delivered = 0xffff; + /* + * U-stream ranges will be set when the cookie + * is unpacked. Or for the INIT sender they + * are un set (if pr-sctp not supported) when the + * INIT-ACK arrives. + */ + TAILQ_INIT(&asoc->strmin[i].inqueue); + /* + * we are not on any wheel, pr-sctp streams + * will go on the wheel when they have data waiting + * for reorder. + */ + asoc->strmin[i].next_spoke.tqe_next = 0; + asoc->strmin[i].next_spoke.tqe_prev = 0; + } + + /* + * load_address_from_init will put the addresses into the + * association when the COOKIE is processed or the INIT-ACK + * is processed. Both types of COOKIE's existing and new + * call this routine. It will remove addresses that + * are no longer in the association (for the restarting + * case where addresses are removed). Up front when the + * INIT arrives we will discard it if it is a restart + * and new addresses have been added. + */ + return (0); +} + +/* + * INIT-ACK message processing/consumption + * returns value < 0 on error + */ +static int +sctp_process_init_ack(struct mbuf *m, int iphlen, int offset, + struct sctphdr *sh, struct sctp_init_ack_chunk *cp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_association *asoc; + struct mbuf *op_err; + int retval, abort_flag; + uint32_t initack_limit; + /* First verify that we have no illegal param's */ + abort_flag = 0; + op_err = NULL; + + op_err = sctp_arethere_unrecognized_parameters(m, + (offset+sizeof(struct sctp_init_chunk)) , + &abort_flag, (struct sctp_chunkhdr *)cp); + if (abort_flag) { + /* Send an abort and notify peer */ + if (op_err != NULL) { + sctp_send_operr_to(m, iphlen, op_err, cp->init.initiate_tag); + } else { + /* + * Just notify (abort_assoc does this if + * we send an abort). + */ + sctp_abort_notification(stcb, 0); + /* + * No sense in further INIT's since + * we will get the same param back + */ + sctp_free_assoc(stcb->sctp_ep, stcb); + } + return (-1); + } + asoc = &stcb->asoc; + /* process the peer's parameters in the INIT-ACK */ + retval = sctp_process_init((struct sctp_init_chunk *)cp, stcb, net); + if (retval < 0) { + return (retval); + } + + initack_limit = offset + ntohs(cp->ch.chunk_length); + /* load all addresses */ + if (sctp_load_addresses_from_init(stcb, m, iphlen, + (offset + sizeof(struct sctp_init_chunk)), initack_limit, sh, + NULL)) { + /* Huh, we should abort */ + sctp_abort_notification(stcb, 0); + sctp_free_assoc(stcb->sctp_ep, stcb); + return (-1); + } + if (op_err) { + sctp_queue_op_err(stcb, op_err); + /* queuing will steal away the mbuf chain to the out queue */ + op_err = NULL; + } + /* extract the cookie and queue it to "echo" it back... */ + stcb->asoc.overall_error_count = 0; + net->error_count = 0; + retval = sctp_send_cookie_echo(m, offset, stcb, net); + if (retval < 0) { + /* + * No cookie, we probably should send a op error. + * But in any case if there is no cookie in the INIT-ACK, + * we can abandon the peer, its broke. + */ + if (retval == -3) { + /* We abort with an error of missing mandatory param */ + struct mbuf *op_err; + op_err = + sctp_generate_invmanparam(SCTP_CAUSE_MISS_PARAM); + if (op_err) { + /* + * Expand beyond to include the mandatory + * param cookie + */ + struct sctp_inv_mandatory_param *mp; + op_err->m_len = + sizeof(struct sctp_inv_mandatory_param); + mp = mtod(op_err, + struct sctp_inv_mandatory_param *); + /* Subtract the reserved param */ + mp->length = + htons(sizeof(struct sctp_inv_mandatory_param) - 2); + mp->num_param = htonl(1); + mp->param = htons(SCTP_STATE_COOKIE); + mp->resv = 0; + } + sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, + sh, op_err); + } + return (retval); + } + + /* + * Cancel the INIT timer, We do this first before queueing + * the cookie. We always cancel at the primary to assue that + * we are canceling the timer started by the INIT which always + * goes to the primary. + */ + sctp_timer_stop(SCTP_TIMER_TYPE_INIT, stcb->sctp_ep, stcb, + asoc->primary_destination); + + /* calculate the RTO */ + net->RTO = sctp_calculate_rto(stcb, asoc, net, &asoc->time_entered); + + return (0); +} + +static void +sctp_handle_heartbeat_ack(struct sctp_heartbeat_chunk *cp, + struct sctp_tcb *stcb, struct sctp_nets *net) +{ + struct sockaddr_storage store; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct sctp_nets *r_net; + struct timeval tv; + + if (ntohs(cp->ch.chunk_length) != sizeof(struct sctp_heartbeat_chunk)) { + /* Invalid length */ + return; + } + + sin = (struct sockaddr_in *)&store; + sin6 = (struct sockaddr_in6 *)&store; + + memset(&store, 0, sizeof(store)); + if (cp->heartbeat.hb_info.addr_family == AF_INET && + cp->heartbeat.hb_info.addr_len == sizeof(struct sockaddr_in)) { + sin->sin_family = cp->heartbeat.hb_info.addr_family; + sin->sin_len = cp->heartbeat.hb_info.addr_len; + sin->sin_port = stcb->rport; + memcpy(&sin->sin_addr, cp->heartbeat.hb_info.address, + sizeof(sin->sin_addr)); + } else if (cp->heartbeat.hb_info.addr_family == AF_INET6 && + cp->heartbeat.hb_info.addr_len == sizeof(struct sockaddr_in6)) { + sin6->sin6_family = cp->heartbeat.hb_info.addr_family; + sin6->sin6_len = cp->heartbeat.hb_info.addr_len; + sin6->sin6_port = stcb->rport; + memcpy(&sin6->sin6_addr, cp->heartbeat.hb_info.address, + sizeof(sin6->sin6_addr)); + } else { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("unsupported address family"); + } +#endif + return; + } + r_net = sctp_findnet(stcb, (struct sockaddr *)sin); + if (r_net == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Huh? I can't find the address I sent it to, discard\n"); + } +#endif + return; + } + if ((r_net && (r_net->dest_state & SCTP_ADDR_UNCONFIRMED)) && + (r_net->heartbeat_random1 == cp->heartbeat.hb_info.random_value1) && + (r_net->heartbeat_random2 == cp->heartbeat.hb_info.random_value2)) { + /* + * If the its a HB and it's random value is correct when + * can confirm the destination. + */ + r_net->dest_state &= ~SCTP_ADDR_UNCONFIRMED; + sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED, + stcb, 0, (void *)r_net); + } + r_net->error_count = 0; + r_net->hb_responded = 1; + tv.tv_sec = cp->heartbeat.hb_info.time_value_1; + tv.tv_usec = cp->heartbeat.hb_info.time_value_2; + if (r_net->dest_state & SCTP_ADDR_NOT_REACHABLE) { + r_net->dest_state = SCTP_ADDR_REACHABLE; + sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_UP, stcb, + SCTP_HEARTBEAT_SUCCESS, (void *)r_net); + + /* now was it the primary? if so restore */ + if (r_net->dest_state & SCTP_ADDR_WAS_PRIMARY) { + sctp_set_primary_addr(stcb, (struct sockaddr *)NULL, r_net); + } + } + /* Now lets do a RTO with this */ + r_net->RTO = sctp_calculate_rto(stcb, &stcb->asoc, r_net, &tv); +} + +static void +sctp_handle_abort(struct sctp_abort_chunk *cp, + struct sctp_tcb *stcb, struct sctp_nets *net) +{ + struct sctp_inpcb *inp; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_abort: handling ABORT\n"); + } +#endif + if (stcb == NULL) + return; + /* verify that the destination addr is in the association */ + /* ignore abort for addresses being deleted */ + + /* stop any receive timers */ + sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, net); + /* notify user of the abort and clean up... */ + sctp_abort_notification(stcb, 0); + /* free the tcb */ + inp = stcb->sctp_ep; + sctp_free_assoc(stcb->sctp_ep, stcb); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_abort: finished\n"); + } +#endif +} + +static void +sctp_handle_shutdown(struct sctp_shutdown_chunk *cp, + struct sctp_tcb *stcb, struct sctp_nets *net, int *abort_flag) +{ + struct sctp_association *asoc; + int some_on_streamwheel; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_shutdown: handling SHUTDOWN\n"); + } +#endif + if (stcb == NULL) + return; + + if ((SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_WAIT) || + (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_ECHOED)) { + return; + } + + if (ntohs(cp->ch.chunk_length) != sizeof(struct sctp_shutdown_chunk)) { + /* update current data status */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Warning Shutdown NOT the expected size.. skipping (%d:%d)\n", + ntohs(cp->ch.chunk_length), + (int)sizeof(struct sctp_shutdown_chunk)); + } +#endif + return; + } else { + sctp_update_acked(stcb, cp, net, abort_flag); + } + asoc = &stcb->asoc; + /* goto SHUTDOWN_RECEIVED state to block new requests */ + if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_RECEIVED) && + (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT)) { + asoc->state = SCTP_STATE_SHUTDOWN_RECEIVED; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Moving to SHUTDOWN-RECEIVED state\n"); + } +#endif + /* notify upper layer that peer has initiated a shutdown */ + sctp_ulp_notify(SCTP_NOTIFY_PEER_SHUTDOWN, stcb, 0, NULL); + + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + + /* Set the flag so we cannot send more, we + * would call the function but we don't want to + * wake up the ulp necessarily. + */ +#if defined(__FreeBSD__) && __FreeBSD_version >= 502115 + stcb->sctp_ep->sctp_socket->so_rcv.sb_state |= SBS_CANTSENDMORE; +#else + stcb->sctp_ep->sctp_socket->so_state |= SS_CANTSENDMORE; +#endif + } + /* reset time */ + SCTP_GETTIME_TIMEVAL(&asoc->time_entered); + } + if (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) { + /* + * stop the shutdown timer, since we WILL move + * to SHUTDOWN-ACK-SENT. + */ + sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, net); + } + /* Now are we there yet? */ + some_on_streamwheel = 0; + if (!TAILQ_EMPTY(&asoc->out_wheel)) { + /* Check to see if some data queued */ + struct sctp_stream_out *outs; + TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) { + if (!TAILQ_EMPTY(&outs->outqueue)) { + some_on_streamwheel = 1; + break; + } + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("some_on_streamwheel:%d send_q_empty:%d sent_q_empty:%d\n", + some_on_streamwheel, + !TAILQ_EMPTY(&asoc->send_queue), + !TAILQ_EMPTY(&asoc->sent_queue)); + } +#endif + if (!TAILQ_EMPTY(&asoc->send_queue) || + !TAILQ_EMPTY(&asoc->sent_queue) || + some_on_streamwheel) { + /* By returning we will push more data out */ + return; + } else { + /* no outstanding data to send, so move on... */ + /* send SHUTDOWN-ACK */ + sctp_send_shutdown_ack(stcb, stcb->asoc.primary_destination); + /* move to SHUTDOWN-ACK-SENT state */ + asoc->state = SCTP_STATE_SHUTDOWN_ACK_SENT; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("moving to SHUTDOWN_ACK state\n"); + } +#endif + /* start SHUTDOWN timer */ + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK, stcb->sctp_ep, + stcb, net); + } +} + +static void +sctp_handle_shutdown_ack(struct sctp_shutdown_ack_chunk *cp, + struct sctp_tcb *stcb, struct sctp_nets *net) +{ + struct sctp_association *asoc; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_shutdown_ack: handling SHUTDOWN ACK\n"); + } +#endif + if (stcb == NULL) + return; + + asoc = &stcb->asoc; + /* process according to association state */ + if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) && + (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { + /* unexpected SHUTDOWN-ACK... so ignore... */ + return; + } + /* are the queues empty? */ + if (!TAILQ_EMPTY(&asoc->send_queue) || + !TAILQ_EMPTY(&asoc->sent_queue) || + !TAILQ_EMPTY(&asoc->out_wheel)) { + sctp_report_all_outbound(stcb); + } + /* stop the timer */ + sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, net); + /* send SHUTDOWN-COMPLETE */ + sctp_send_shutdown_complete(stcb, net); + /* notify upper layer protocol */ + sctp_ulp_notify(SCTP_NOTIFY_ASSOC_DOWN, stcb, 0, NULL); + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + stcb->sctp_ep->sctp_flags &= ~SCTP_PCB_FLAGS_CONNECTED; + /* Set the connected flag to disconnected */ + stcb->sctp_ep->sctp_socket->so_snd.sb_cc = 0; + stcb->sctp_ep->sctp_socket->so_snd.sb_mbcnt = 0; + soisdisconnected(stcb->sctp_ep->sctp_socket); + } + /* free the TCB but first save off the ep */ + sctp_free_assoc(stcb->sctp_ep, stcb); +} + +/* + * Skip past the param header and then we will find the chunk that + * caused the problem. There are two possiblities ASCONF or FWD-TSN + * other than that and our peer must be broken. + */ +static void +sctp_process_unrecog_chunk(struct sctp_tcb *stcb, struct sctp_paramhdr *phdr, + struct sctp_nets *net) +{ + struct sctp_chunkhdr *chk; + + chk = (struct sctp_chunkhdr *)((caddr_t)phdr + sizeof(*phdr)); + switch (chk->chunk_type) { + case SCTP_ASCONF_ACK: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("Strange peer, snds ASCONF but does not recongnize asconf-ack?\n"); + } +#endif + case SCTP_ASCONF: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("Peer does not support ASCONF/ASCONF-ACK chunks\n"); + } +#endif /* SCTP_DEBUG */ + sctp_asconf_cleanup(stcb, net); + break; + case SCTP_FORWARD_CUM_TSN: + stcb->asoc.peer_supports_prsctp = 0; + break; + default: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("Peer does not support chunk type %d(%x)??\n", + chk->chunk_type, (u_int)chk->chunk_type); + } +#endif + break; + } +} + +/* + * Skip past the param header and then we will find the param that + * caused the problem. There are a number of param's in a ASCONF + * OR the prsctp param these will turn of specific features. + */ +static void +sctp_process_unrecog_param(struct sctp_tcb *stcb, struct sctp_paramhdr *phdr) +{ + struct sctp_paramhdr *pbad; + + pbad = phdr + 1; + switch (ntohs(pbad->param_type)) { + /* pr-sctp draft */ + case SCTP_PRSCTP_SUPPORTED: + stcb->asoc.peer_supports_prsctp = 0; + break; + case SCTP_SUPPORTED_CHUNK_EXT: + break; + /* draft-ietf-tsvwg-addip-sctp */ + case SCTP_ECN_NONCE_SUPPORTED: + stcb->asoc.peer_supports_ecn_nonce = 0; + stcb->asoc.ecn_nonce_allowed = 0; + stcb->asoc.ecn_allowed = 0; + break; + case SCTP_ADD_IP_ADDRESS: + case SCTP_DEL_IP_ADDRESS: + stcb->asoc.peer_supports_asconf = 0; + break; + case SCTP_SET_PRIM_ADDR: + stcb->asoc.peer_supports_asconf_setprim = 0; + break; + case SCTP_SUCCESS_REPORT: + case SCTP_ERROR_CAUSE_IND: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("Huh, the peer does not support success? or error cause?\n"); + printf("Turning off ASCONF to this strange peer\n"); + } +#endif + stcb->asoc.peer_supports_asconf = 0; + stcb->asoc.peer_supports_asconf_setprim = 0; + break; + default: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("Peer does not support base param type %d(%x)??\n", + pbad->param_type, (u_int)pbad->param_type); + } +#endif + break; + } +} + +static int +sctp_handle_error(struct sctp_chunkhdr *ch, + struct sctp_tcb *stcb, struct sctp_nets *net) +{ + int chklen; + struct sctp_paramhdr *phdr; + uint16_t error_type; + uint16_t error_len; + struct sctp_association *asoc; + + int adjust; + /* parse through all of the errors and process */ + asoc = &stcb->asoc; + phdr = (struct sctp_paramhdr *)((caddr_t)ch + + sizeof(struct sctp_chunkhdr)); + chklen = ntohs(ch->chunk_length) - sizeof(struct sctp_chunkhdr); + while ((size_t)chklen >= sizeof(struct sctp_paramhdr)) { + /* Process an Error Cause */ + error_type = ntohs(phdr->param_type); + error_len = ntohs(phdr->param_length); + if ((error_len > chklen) || (error_len == 0)) { + /* invalid param length for this param */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Bogus length in error param- chunk left:%d errorlen:%d\n", + chklen, error_len); + } +#endif /* SCTP_DEBUG */ + return (0); + } + switch (error_type) { + case SCTP_CAUSE_INV_STRM: + case SCTP_CAUSE_MISS_PARAM: + case SCTP_CAUSE_INVALID_PARAM: + case SCTP_CAUSE_NOUSER_DATA: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Software error we got a %d back? We have a bug :/ (or do they?)\n", + error_type); + } +#endif + break; + case SCTP_CAUSE_STALE_COOKIE: + /* We only act if we have echoed a cookie and are waiting. */ + if (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED) { + int *p; + p = (int *)((caddr_t)phdr + sizeof(*phdr)); + /* Save the time doubled */ + asoc->cookie_preserve_req = ntohl(*p) << 1; + asoc->stale_cookie_count++; + if (asoc->stale_cookie_count > + asoc->max_init_times) { + sctp_abort_notification(stcb, 0); + /* now free the asoc */ + sctp_free_assoc(stcb->sctp_ep, stcb); + return (-1); + } + /* blast back to INIT state */ + asoc->state &= ~SCTP_STATE_COOKIE_ECHOED; + asoc->state |= SCTP_STATE_COOKIE_WAIT; + sctp_timer_stop(SCTP_TIMER_TYPE_COOKIE, + stcb->sctp_ep, stcb, net); + sctp_send_initiate(stcb->sctp_ep, stcb); + } + break; + case SCTP_CAUSE_UNRESOLV_ADDR: + /* + * Nothing we can do here, we don't do hostname + * addresses so if the peer does not like my IPv6 (or + * IPv4 for that matter) it does not matter. If they + * don't support that type of address, they can NOT + * possibly get that packet type... i.e. with no IPv6 + * you can't recieve a IPv6 packet. so we can safely + * ignore this one. If we ever added support for + * HOSTNAME Addresses, then we would need to do + * something here. + */ + break; + case SCTP_CAUSE_UNRECOG_CHUNK: + sctp_process_unrecog_chunk(stcb, phdr, net); + break; + case SCTP_CAUSE_UNRECOG_PARAM: + sctp_process_unrecog_param(stcb, phdr); + break; + case SCTP_CAUSE_COOKIE_IN_SHUTDOWN: + /* + * We ignore this since the timer will drive out a new + * cookie anyway and there timer will drive us to send + * a SHUTDOWN_COMPLETE. We can't send one here since + * we don't have their tag. + */ + break; + case SCTP_CAUSE_DELETEING_LAST_ADDR: + case SCTP_CAUSE_OPERATION_REFUSED: + case SCTP_CAUSE_DELETING_SRC_ADDR: + /* We should NOT get these here, but in a ASCONF-ACK. */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("Peer sends ASCONF errors in a Operational Error?<%d>?\n", + error_type); + } +#endif + break; + case SCTP_CAUSE_OUT_OF_RESC: + /* + * And what, pray tell do we do with the fact + * that the peer is out of resources? Not + * really sure we could do anything but abort. + * I suspect this should have came WITH an + * abort instead of in a OP-ERROR. + */ + break; + default: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + /* don't know what this error cause is... */ + printf("sctp_handle_error: unknown error type = 0x%xh\n", + error_type); + } +#endif /* SCTP_DEBUG */ + break; + } + adjust = SCTP_SIZE32(error_len); + chklen -= adjust; + phdr = (struct sctp_paramhdr *)((caddr_t)phdr + adjust); + } + return (0); +} + +static int +sctp_handle_init_ack(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, + struct sctp_init_ack_chunk *cp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_init_ack *init_ack; + int *state; + struct mbuf *op_err; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_init_ack: handling INIT-ACK\n"); + } +#endif + if (stcb == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_init_ack: TCB is null\n"); + } +#endif + return (-1); + } + if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_init_ack_chunk)) { + /* Invalid length */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); + sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, + op_err); + return (-1); + } + init_ack = &cp->init; + /* validate parameters */ + if (init_ack->initiate_tag == 0) { + /* protocol error... send an abort */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); + sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, + op_err); + return (-1); + } + if (ntohl(init_ack->a_rwnd) < SCTP_MIN_RWND) { + /* protocol error... send an abort */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); + sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, + op_err); + return (-1); + } + if (init_ack->num_inbound_streams == 0) { + /* protocol error... send an abort */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); + sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, + op_err); + return (-1); + } + if (init_ack->num_outbound_streams == 0) { + /* protocol error... send an abort */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_INVALID_PARAM); + sctp_abort_association(stcb->sctp_ep, stcb, m, iphlen, sh, + op_err); + return (-1); + } + + /* process according to association state... */ + state = &stcb->asoc.state; + switch (*state & SCTP_STATE_MASK) { + case SCTP_STATE_COOKIE_WAIT: + /* this is the expected state for this chunk */ + /* process the INIT-ACK parameters */ + if (stcb->asoc.primary_destination->dest_state & + SCTP_ADDR_UNCONFIRMED) { + /* + * The primary is where we sent the INIT, we can + * always consider it confirmed when the INIT-ACK + * is returned. Do this before we load addresses + * though. + */ + stcb->asoc.primary_destination->dest_state &= + ~SCTP_ADDR_UNCONFIRMED; + sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED, + stcb, 0, (void *)stcb->asoc.primary_destination); + } + if (sctp_process_init_ack(m, iphlen, offset, sh, cp, stcb, net + ) < 0) { + /* error in parsing parameters */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_process_init_ack: error in msg, discarding\n"); + } +#endif + return (-1); + } + /* update our state */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("moving to COOKIE-ECHOED state\n"); + } +#endif + if (*state & SCTP_STATE_SHUTDOWN_PENDING) { + *state = SCTP_STATE_COOKIE_ECHOED | + SCTP_STATE_SHUTDOWN_PENDING; + } else { + *state = SCTP_STATE_COOKIE_ECHOED; + } + + /* reset the RTO calc */ + stcb->asoc.overall_error_count = 0; + SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); + /* + * collapse the init timer back in case of a exponential backoff + */ + sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, stcb->sctp_ep, + stcb, net); + /* + * the send at the end of the inbound data processing will + * cause the cookie to be sent + */ + break; + case SCTP_STATE_SHUTDOWN_SENT: + /* incorrect state... discard */ + break; + case SCTP_STATE_COOKIE_ECHOED: + /* incorrect state... discard */ + break; + case SCTP_STATE_OPEN: + /* incorrect state... discard */ + break; + case SCTP_STATE_EMPTY: + case SCTP_STATE_INUSE: + default: + /* incorrect state... discard */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Leaving handle-init-ack default\n"); + } +#endif + return (-1); + break; + } /* end switch asoc state */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Leaving handle-init-ack end\n"); + } +#endif + return (0); +} + + +/* + * handle a state cookie for an existing association + * m: input packet mbuf chain-- assumes a pullup on IP/SCTP/COOKIE-ECHO chunk + * note: this is a "split" mbuf and the cookie signature does not exist + * offset: offset into mbuf to the cookie-echo chunk + */ +static struct sctp_tcb * +sctp_process_cookie_existing(struct mbuf *m, int iphlen, int offset, + struct sctphdr *sh, struct sctp_state_cookie *cookie, int cookie_len, + struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, + struct sockaddr *init_src, int *notification) +{ + struct sctp_association *asoc; + struct sctp_init_chunk *init_cp, init_buf; + struct sctp_init_ack_chunk *initack_cp, initack_buf; + int chk_length; + int init_offset, initack_offset; + int retval; + + /* I know that the TCB is non-NULL from the caller */ + asoc = &stcb->asoc; + + if (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) { + /* SHUTDOWN came in after sending INIT-ACK */ + struct mbuf *op_err; + struct sctp_paramhdr *ph; + + sctp_send_shutdown_ack(stcb, stcb->asoc.primary_destination); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_cookie: got a cookie, while shutting down!\n"); + } +#endif + MGETHDR(op_err, M_DONTWAIT, MT_HEADER); + if (op_err == NULL) { + /* FOOBAR */ + return (NULL); + } + /* pre-reserve some space */ + op_err->m_data += sizeof(struct ip6_hdr); + op_err->m_data += sizeof(struct sctphdr); + op_err->m_data += sizeof(struct sctp_chunkhdr); + /* Set the len */ + op_err->m_len = op_err->m_pkthdr.len = sizeof(struct sctp_paramhdr); + ph = mtod(op_err, struct sctp_paramhdr *); + ph->param_type = htons(SCTP_CAUSE_COOKIE_IN_SHUTDOWN); + ph->param_length = htons(sizeof(struct sctp_paramhdr)); + sctp_send_operr_to(m, iphlen, op_err, cookie->peers_vtag); + return (NULL); + } + /* + * find and validate the INIT chunk in the cookie (peer's info) + * the INIT should start after the cookie-echo header struct + * (chunk header, state cookie header struct) + */ + init_offset = offset += sizeof(struct sctp_cookie_echo_chunk); + + init_cp = (struct sctp_init_chunk *) + sctp_m_getptr(m, init_offset, sizeof(struct sctp_init_chunk), + (u_int8_t *)&init_buf); + if (init_cp == NULL) { + /* could not pull a INIT chunk in cookie */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("process_cookie_existing: could not pull INIT chunk hdr\n"); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + chk_length = ntohs(init_cp->ch.chunk_length); + if (init_cp->ch.chunk_type != SCTP_INITIATION) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("process_cookie_existing: could not find INIT chunk!\n"); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + + /* + * find and validate the INIT-ACK chunk in the cookie (my info) + * the INIT-ACK follows the INIT chunk + */ + initack_offset = init_offset + SCTP_SIZE32(chk_length); + initack_cp = (struct sctp_init_ack_chunk *) + sctp_m_getptr(m, initack_offset, sizeof(struct sctp_init_ack_chunk), + (u_int8_t *)&initack_buf); + if (initack_cp == NULL) { + /* could not pull INIT-ACK chunk in cookie */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("process_cookie_existing: could not pull INIT-ACK chunk hdr\n"); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + chk_length = ntohs(initack_cp->ch.chunk_length); + if (initack_cp->ch.chunk_type != SCTP_INITIATION_ACK) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("process_cookie_existing: could not find INIT-ACK chunk!\n"); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + if ((ntohl(initack_cp->init.initiate_tag) == asoc->my_vtag) && + (ntohl(init_cp->init.initiate_tag) == asoc->peer_vtag)) { + /* + * case D in Section 5.2.4 Table 2: MMAA + * process accordingly to get into the OPEN state + */ + switch SCTP_GET_STATE(asoc) { + case SCTP_STATE_COOKIE_WAIT: + /* + * INIT was sent, but got got a COOKIE_ECHO with + * the correct tags... just accept it... + */ + /* First we must process the INIT !! */ + retval = sctp_process_init(init_cp, stcb, net); + if (retval < 0) { +#ifdef SCTP_DEBUG + printf("process_cookie_existing: INIT processing failed\n"); +#endif + return (NULL); + } + /* intentional fall through to below... */ + + case SCTP_STATE_COOKIE_ECHOED: + /* Duplicate INIT case */ + /* we have already processed the INIT so no problem */ + sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, + net); + sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb, net); + sctp_timer_stop(SCTP_TIMER_TYPE_COOKIE, inp, stcb, + net); + /* update current state */ + if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { + asoc->state = SCTP_STATE_OPEN | + SCTP_STATE_SHUTDOWN_PENDING; + } else if ((asoc->state & SCTP_STATE_SHUTDOWN_SENT) == 0) { + /* if ok, move to OPEN state */ + asoc->state = SCTP_STATE_OPEN; + } + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) && + (!(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING))) { + /* + * Here is where collision would go if we did a + * connect() and instead got a + * init/init-ack/cookie done before the + * init-ack came back.. + */ + stcb->sctp_ep->sctp_flags |= + SCTP_PCB_FLAGS_CONNECTED; + soisconnected(stcb->sctp_ep->sctp_socket); + } + /* notify upper layer */ + *notification = SCTP_NOTIFY_ASSOC_UP; + sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, + net); + /* + * since we did not send a HB make sure we don't double + * things + */ + net->hb_responded = 1; + + if (stcb->asoc.sctp_autoclose_ticks && + (inp->sctp_flags & SCTP_PCB_FLAGS_AUTOCLOSE)) { + sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, + inp, stcb, NULL); + } + break; + default: + /* + * we're in the OPEN state (or beyond), so peer + * must have simply lost the COOKIE-ACK + */ + break; + } /* end switch */ + + /* + * We ignore the return code here.. not sure if we should + * somehow abort.. but we do have an existing asoc. This + * really should not fail. + */ + if (sctp_load_addresses_from_init(stcb, m, iphlen, + init_offset + sizeof(struct sctp_init_chunk), + initack_offset, sh, init_src)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Weird cookie load_address failure on cookie existing - 1\n"); + } +#endif + return (NULL); + } + + /* respond with a COOKIE-ACK */ + sctp_send_cookie_ack(stcb); + return (stcb); + } /* end if */ + if (ntohl(initack_cp->init.initiate_tag) != asoc->my_vtag && + ntohl(init_cp->init.initiate_tag) == asoc->peer_vtag && + cookie->tie_tag_my_vtag == 0 && + cookie->tie_tag_peer_vtag == 0) { + /* + * case C in Section 5.2.4 Table 2: XMOO + * silently discard + */ + return (NULL); + } + if (ntohl(initack_cp->init.initiate_tag) == asoc->my_vtag && + (ntohl(init_cp->init.initiate_tag) != asoc->peer_vtag || + init_cp->init.initiate_tag == 0)) { + /* + * case B in Section 5.2.4 Table 2: MXAA or MOAA + * my info should be ok, re-accept peer info + */ + sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net); + sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb, net); + sctp_timer_stop(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net); + sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net); + /* + * since we did not send a HB make sure we don't double things + */ + net->hb_responded = 1; + if (stcb->asoc.sctp_autoclose_ticks && + (inp->sctp_flags & SCTP_PCB_FLAGS_AUTOCLOSE)) { + sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, + NULL); + } + asoc->my_rwnd = ntohl(initack_cp->init.a_rwnd); + asoc->pre_open_streams = + ntohs(initack_cp->init.num_outbound_streams); + asoc->init_seq_number = ntohl(initack_cp->init.initial_tsn); + asoc->sending_seq = asoc->asconf_seq_out = asoc->str_reset_seq_out = + asoc->init_seq_number; + asoc->t3timeout_highest_marked = asoc->asconf_seq_out; + asoc->last_cwr_tsn = asoc->init_seq_number - 1; + asoc->asconf_seq_in = asoc->last_acked_seq = asoc->init_seq_number - 1; + asoc->str_reset_seq_in = asoc->init_seq_number; + asoc->advanced_peer_ack_point = asoc->last_acked_seq; + + /* process the INIT info (peer's info) */ + retval = sctp_process_init(init_cp, stcb, net); + if (retval < 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("process_cookie_existing: INIT processing failed\n"); + } +#endif + return (NULL); + } + if (sctp_load_addresses_from_init(stcb, m, iphlen, + init_offset + sizeof(struct sctp_init_chunk), + initack_offset, sh, init_src)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Weird cookie load_address failure on cookie existing - 2\n"); + } +#endif + return (NULL); + } + + if ((asoc->state & SCTP_STATE_COOKIE_WAIT) || + (asoc->state & SCTP_STATE_COOKIE_ECHOED)) { + *notification = SCTP_NOTIFY_ASSOC_UP; + + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) && + !(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING)) { + stcb->sctp_ep->sctp_flags |= + SCTP_PCB_FLAGS_CONNECTED; + soisconnected(stcb->sctp_ep->sctp_socket); + } + } + if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { + asoc->state = SCTP_STATE_OPEN | + SCTP_STATE_SHUTDOWN_PENDING; + } else { + asoc->state = SCTP_STATE_OPEN; + } + sctp_send_cookie_ack(stcb); + return (stcb); + } + + if ((ntohl(initack_cp->init.initiate_tag) != asoc->my_vtag && + ntohl(init_cp->init.initiate_tag) != asoc->peer_vtag) && + cookie->tie_tag_my_vtag == asoc->my_vtag_nonce && + cookie->tie_tag_peer_vtag == asoc->peer_vtag_nonce && + cookie->tie_tag_peer_vtag != 0) { + /* + * case A in Section 5.2.4 Table 2: XXMM (peer restarted) + */ + sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb, net); + sctp_timer_stop(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net); + sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net); + + /* notify upper layer */ + *notification = SCTP_NOTIFY_ASSOC_RESTART; + + /* send up all the data */ + sctp_report_all_outbound(stcb); + + /* process the INIT-ACK info (my info) */ + asoc->my_vtag = ntohl(initack_cp->init.initiate_tag); + asoc->my_rwnd = ntohl(initack_cp->init.a_rwnd); + asoc->pre_open_streams = + ntohs(initack_cp->init.num_outbound_streams); + asoc->init_seq_number = ntohl(initack_cp->init.initial_tsn); + asoc->sending_seq = asoc->asconf_seq_out = asoc->str_reset_seq_out = + asoc->init_seq_number; + asoc->t3timeout_highest_marked = asoc->asconf_seq_out; + asoc->last_cwr_tsn = asoc->init_seq_number - 1; + asoc->asconf_seq_in = asoc->last_acked_seq = asoc->init_seq_number - 1; + asoc->str_reset_seq_in = asoc->init_seq_number; + + asoc->advanced_peer_ack_point = asoc->last_acked_seq; + if (asoc->mapping_array) + memset(asoc->mapping_array, 0, + asoc->mapping_array_size); + /* process the INIT info (peer's info) */ + retval = sctp_process_init(init_cp, stcb, net); + if (retval < 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("process_cookie_existing: INIT processing failed\n"); + } +#endif + return (NULL); + } + + sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net); + /* + * since we did not send a HB make sure we don't double things + */ + net->hb_responded = 1; + + if (sctp_load_addresses_from_init(stcb, m, iphlen, + init_offset + sizeof(struct sctp_init_chunk), + initack_offset, sh, init_src)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Weird cookie load_address failure on cookie existing - 3\n"); + } +#endif + return (NULL); + } + + if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { + asoc->state = SCTP_STATE_OPEN | + SCTP_STATE_SHUTDOWN_PENDING; + } else if (!(asoc->state & SCTP_STATE_SHUTDOWN_SENT)) { + /* move to OPEN state, if not in SHUTDOWN_SENT */ + asoc->state = SCTP_STATE_OPEN; + } + /* respond with a COOKIE-ACK */ + sctp_send_cookie_ack(stcb); + + return (stcb); + } + /* all other cases... */ + return (NULL); +} + +/* + * handle a state cookie for a new association + * m: input packet mbuf chain-- assumes a pullup on IP/SCTP/COOKIE-ECHO chunk + * note: this is a "split" mbuf and the cookie signature does not exist + * offset: offset into mbuf to the cookie-echo chunk + * length: length of the cookie chunk + * to: where the init was from + * returns a new TCB + */ +static struct sctp_tcb * +sctp_process_cookie_new(struct mbuf *m, int iphlen, int offset, + struct sctphdr *sh, struct sctp_state_cookie *cookie, int cookie_len, + struct sctp_inpcb *inp, struct sctp_nets **netp, + struct sockaddr *init_src, int *notification) +{ + struct sctp_tcb *stcb; + struct sctp_init_chunk *init_cp, init_buf; + struct sctp_init_ack_chunk *initack_cp, initack_buf; + struct sockaddr_storage sa_store; + struct sockaddr *initack_src = (struct sockaddr *)&sa_store; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct sctp_association *asoc; + int chk_length; + int init_offset, initack_offset, initack_limit; + int retval; + int error = 0; + u_int32_t old_tag; + /* + * find and validate the INIT chunk in the cookie (peer's info) + * the INIT should start after the cookie-echo header struct + * (chunk header, state cookie header struct) + */ + init_offset = offset + sizeof(struct sctp_cookie_echo_chunk); + init_cp = (struct sctp_init_chunk *) + sctp_m_getptr(m, init_offset, sizeof(struct sctp_init_chunk), + (u_int8_t *)&init_buf); + if (init_cp == NULL) { + /* could not pull a INIT chunk in cookie */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("process_cookie_new: could not pull INIT chunk hdr\n"); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + chk_length = ntohs(init_cp->ch.chunk_length); + if (init_cp->ch.chunk_type != SCTP_INITIATION) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("HUH? process_cookie_new: could not find INIT chunk!\n"); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + + initack_offset = init_offset + SCTP_SIZE32(chk_length); + /* + * find and validate the INIT-ACK chunk in the cookie (my info) + * the INIT-ACK follows the INIT chunk + */ + initack_cp = (struct sctp_init_ack_chunk *) + sctp_m_getptr(m, initack_offset, sizeof(struct sctp_init_ack_chunk), + (u_int8_t *)&initack_buf); + if (initack_cp == NULL) { + /* could not pull INIT-ACK chunk in cookie */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("process_cookie_new: could not pull INIT-ACK chunk hdr\n"); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + chk_length = ntohs(initack_cp->ch.chunk_length); + if (initack_cp->ch.chunk_type != SCTP_INITIATION_ACK) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + u_int8_t *pp; + pp = (u_int8_t *)initack_cp; + printf("process_cookie_new: could not find INIT-ACK chunk!\n"); + printf("Found bytes %x %x %x %x at postion %d\n", + (u_int)pp[0], (u_int)pp[1], (u_int)pp[2], + (u_int)pp[3], initack_offset); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + initack_limit = initack_offset + SCTP_SIZE32(chk_length); + + /* + * now that we know the INIT/INIT-ACK are in place, + * create a new TCB and popluate + */ + stcb = sctp_aloc_assoc(inp, init_src, 0, &error, ntohl(initack_cp->init.initiate_tag)); + if (stcb == NULL) { + struct mbuf *op_err; + /* memory problem? */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("process_cookie_new: no room for another TCB!\n"); + } +#endif /* SCTP_DEBUG */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_OUT_OF_RESC); + sctp_abort_association(inp, (struct sctp_tcb *)NULL, m, iphlen, + sh, op_err); + return (NULL); + } + + /* get the correct sctp_nets */ + *netp = sctp_findnet(stcb, init_src); + asoc = &stcb->asoc; + /* get scope variables out of cookie */ + asoc->ipv4_local_scope = cookie->ipv4_scope; + asoc->site_scope = cookie->site_scope; + asoc->local_scope = cookie->local_scope; + asoc->loopback_scope = cookie->loopback_scope; + + if ((asoc->ipv4_addr_legal != cookie->ipv4_addr_legal) || + (asoc->ipv6_addr_legal != cookie->ipv6_addr_legal)) { + struct mbuf *op_err; + /* + * Houston we have a problem. The EP changed while the cookie + * was in flight. Only recourse is to abort the association. + */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_OUT_OF_RESC); + sctp_abort_association(inp, (struct sctp_tcb *)NULL, m, iphlen, + sh, op_err); + return (NULL); + } + + /* process the INIT-ACK info (my info) */ + old_tag = asoc->my_vtag; + asoc->my_vtag = ntohl(initack_cp->init.initiate_tag); + asoc->my_rwnd = ntohl(initack_cp->init.a_rwnd); + asoc->pre_open_streams = ntohs(initack_cp->init.num_outbound_streams); + asoc->init_seq_number = ntohl(initack_cp->init.initial_tsn); + asoc->sending_seq = asoc->asconf_seq_out = asoc->str_reset_seq_out = asoc->init_seq_number; + asoc->t3timeout_highest_marked = asoc->asconf_seq_out; + asoc->last_cwr_tsn = asoc->init_seq_number - 1; + asoc->asconf_seq_in = asoc->last_acked_seq = asoc->init_seq_number - 1; + asoc->str_reset_seq_in = asoc->init_seq_number; + + asoc->advanced_peer_ack_point = asoc->last_acked_seq; + + /* process the INIT info (peer's info) */ + retval = sctp_process_init(init_cp, stcb, *netp); + if (retval < 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("process_cookie_new: INIT processing failed\n"); + } +#endif + sctp_free_assoc(inp, stcb); + return (NULL); + } + /* load all addresses */ + if (sctp_load_addresses_from_init(stcb, m, iphlen, + init_offset + sizeof(struct sctp_init_chunk), initack_offset, sh, + init_src)) { + sctp_free_assoc(inp, stcb); + return (NULL); + } + + /* update current state */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("moving to OPEN state\n"); + } +#endif + if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { + asoc->state = SCTP_STATE_OPEN | SCTP_STATE_SHUTDOWN_PENDING; + } else { + asoc->state = SCTP_STATE_OPEN; + } + /* calculate the RTT */ + (*netp)->RTO = sctp_calculate_rto(stcb, asoc, *netp, + &cookie->time_entered); + + /* + * if we're doing ASCONFs, check to see if we have any new + * local addresses that need to get added to the peer (eg. + * addresses changed while cookie echo in flight). This needs + * to be done after we go to the OPEN state to do the correct + * asconf processing. + * else, make sure we have the correct addresses in our lists + */ + + /* warning, we re-use sin, sin6, sa_store here! */ + /* pull in local_address (our "from" address) */ + if (cookie->laddr_type == SCTP_IPV4_ADDRESS) { + /* source addr is IPv4 */ + sin = (struct sockaddr_in *)initack_src; + memset(sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(struct sockaddr_in); + sin->sin_addr.s_addr = cookie->laddress[0]; + } else if (cookie->laddr_type == SCTP_IPV6_ADDRESS) { + /* source addr is IPv6 */ + sin6 = (struct sockaddr_in6 *)initack_src; + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_scope_id = cookie->scope_id; + memcpy(&sin6->sin6_addr, cookie->laddress, + sizeof(sin6->sin6_addr)); + } else { + sctp_free_assoc(inp, stcb); + return (NULL); + } + + sctp_check_address_list(stcb, m, initack_offset + + sizeof(struct sctp_init_ack_chunk), initack_limit, + initack_src, cookie->local_scope, cookie->site_scope, + cookie->ipv4_scope, cookie->loopback_scope); + + + /* set up to notify upper layer */ + *notification = SCTP_NOTIFY_ASSOC_UP; + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) && + !(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING)) { + /* + * This is an endpoint that called connect() + * how it got a cookie that is NEW is a bit of + * a mystery. It must be that the INIT was sent, but + * before it got there.. a complete INIT/INIT-ACK/COOKIE + * arrived. But of course then it should have went to + * the other code.. not here.. oh well.. a bit of protection + * is worth having.. + */ + stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; + soisconnected(stcb->sctp_ep->sctp_socket); + sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, *netp); + } else if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING)) { + /* + * We don't want to do anything with this + * one. Since it is the listening guy. The timer will + * get started for accepted connections in the caller. + */ + ; + } else { + sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, *netp); + } + /* since we did not send a HB make sure we don't double things */ + (*netp)->hb_responded = 1; + + if (stcb->asoc.sctp_autoclose_ticks && + (inp->sctp_flags & SCTP_PCB_FLAGS_AUTOCLOSE)) { + sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, NULL); + } + + /* respond with a COOKIE-ACK */ + sctp_send_cookie_ack(stcb); + + return (stcb); +} + + +/* + * handles a COOKIE-ECHO message + * stcb: modified to either a new or left as existing (non-NULL) TCB + */ +static struct mbuf * +sctp_handle_cookie_echo(struct mbuf *m, int iphlen, int offset, + struct sctphdr *sh, struct sctp_cookie_echo_chunk *cp, + struct sctp_inpcb **inp_p, struct sctp_tcb **stcb, struct sctp_nets **netp) +{ + struct sctp_state_cookie *cookie; + struct sockaddr_in6 sin6; + struct sockaddr_in sin; + struct sctp_tcb *l_stcb=*stcb; + struct sctp_inpcb *l_inp; + struct sockaddr *to; + struct sctp_pcb *ep; + struct mbuf *m_sig; + uint8_t calc_sig[SCTP_SIGNATURE_SIZE], tmp_sig[SCTP_SIGNATURE_SIZE]; + uint8_t *sig; + uint8_t cookie_ok = 0; + unsigned int size_of_pkt, sig_offset, cookie_offset; + unsigned int cookie_len; + struct timeval now; + struct timeval time_expires; + struct sockaddr_storage dest_store; + struct sockaddr *localep_sa = (struct sockaddr *)&dest_store; + struct ip *iph; + int notification = 0; + struct sctp_nets *netl; + int had_a_existing_tcb = 0; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_cookie: handling COOKIE-ECHO\n"); + } +#endif + + if (inp_p == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("sctp_handle_cookie: null inp_p!\n"); + } +#endif + return (NULL); + } + /* First get the destination address setup too. */ + iph = mtod(m, struct ip *); + if (iph->ip_v == IPVERSION) { + /* its IPv4 */ + struct sockaddr_in *sin; + sin = (struct sockaddr_in *)(localep_sa); + memset(sin, 0, sizeof(*sin)); + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_port = sh->dest_port; + sin->sin_addr.s_addr = iph->ip_dst.s_addr ; + } else if (iph->ip_v == (IPV6_VERSION >> 4)) { + /* its IPv6 */ + struct ip6_hdr *ip6; + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)(localep_sa); + memset(sin6, 0, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + ip6 = mtod(m, struct ip6_hdr *); + sin6->sin6_port = sh->dest_port; + sin6->sin6_addr = ip6->ip6_dst; + } else { + return (NULL); + } + + cookie = &cp->cookie; + cookie_offset = offset + sizeof(struct sctp_chunkhdr); + cookie_len = ntohs(cp->ch.chunk_length); + + if ((cookie->peerport != sh->src_port) && + (cookie->myport != sh->dest_port) && + (cookie->my_vtag != sh->v_tag)) { + /* + * invalid ports or bad tag. Note that we always leave + * the v_tag in the header in network order and when we + * stored it in the my_vtag slot we also left it in network + * order. This maintians the match even though it may be in + * the opposite byte order of the machine :-> + */ + return (NULL); + } + + /* compute size of packet */ + if (m->m_flags & M_PKTHDR) { + size_of_pkt = m->m_pkthdr.len; + } else { + /* Should have a pkt hdr really */ + struct mbuf *mat; + mat = m; + size_of_pkt = 0; + while (mat != NULL) { + size_of_pkt += mat->m_len; + mat = mat->m_next; + } + } + if (cookie_len > size_of_pkt || + cookie_len < sizeof(struct sctp_cookie_echo_chunk) + + sizeof(struct sctp_init_chunk) + + sizeof(struct sctp_init_ack_chunk) + SCTP_SIGNATURE_SIZE) { + /* cookie too long! or too small */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_cookie: cookie_len=%u, pkt size=%u\n", cookie_len, size_of_pkt); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + /* + * split off the signature into its own mbuf (since it + * should not be calculated in the sctp_hash_digest_m() call). + */ + sig_offset = offset + cookie_len - SCTP_SIGNATURE_SIZE; + if (sig_offset > size_of_pkt) { + /* packet not correct size! */ + /* XXX this may already be accounted for earlier... */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_cookie: sig offset=%u, pkt size=%u\n", sig_offset, size_of_pkt); + } +#endif + return (NULL); + } + + m_sig = m_split(m, sig_offset, M_DONTWAIT); + if (m_sig == NULL) { + /* out of memory or ?? */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("sctp_handle_cookie: couldn't m_split the signature\n"); + } +#endif + return (NULL); + } + /* + * compute the signature/digest for the cookie + */ + ep = &(*inp_p)->sctp_ep; + l_inp = *inp_p; + if (l_stcb) { + SCTP_TCB_UNLOCK(l_stcb); + } + SCTP_INP_RLOCK(l_inp); + if (l_stcb) { + SCTP_TCB_LOCK(l_stcb); + } + /* which cookie is it? */ + if ((cookie->time_entered.tv_sec < (long)ep->time_of_secret_change) && + (ep->current_secret_number != ep->last_secret_number)) { + /* it's the old cookie */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_cookie: old cookie sig\n"); + } +#endif + sctp_hash_digest_m((char *)ep->secret_key[(int)ep->last_secret_number], + SCTP_SECRET_SIZE, m, cookie_offset, calc_sig); + } else { + /* it's the current cookie */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_cookie: current cookie sig\n"); + } +#endif + sctp_hash_digest_m((char *)ep->secret_key[(int)ep->current_secret_number], + SCTP_SECRET_SIZE, m, cookie_offset, calc_sig); + } + /* get the signature */ + SCTP_INP_RUNLOCK(l_inp); + sig = (u_int8_t *)sctp_m_getptr(m_sig, 0, SCTP_SIGNATURE_SIZE, (u_int8_t *)&tmp_sig); + if (sig == NULL) { + /* couldn't find signature */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("sctp_handle_cookie: couldn't pull the signature\n"); + } +#endif + return (NULL); + } + /* compare the received digest with the computed digest */ + if (memcmp(calc_sig, sig, SCTP_SIGNATURE_SIZE) != 0) { + /* try the old cookie? */ + if ((cookie->time_entered.tv_sec == (long)ep->time_of_secret_change) && + (ep->current_secret_number != ep->last_secret_number)) { + /* compute digest with old */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_cookie: old cookie sig\n"); + } +#endif + sctp_hash_digest_m((char *)ep->secret_key[(int)ep->last_secret_number], + SCTP_SECRET_SIZE, m, cookie_offset, calc_sig); + /* compare */ + if (memcmp(calc_sig, sig, SCTP_SIGNATURE_SIZE) == 0) + cookie_ok = 1; + } + } else { + cookie_ok = 1; + } + + /* + * Now before we continue we must reconstruct our mbuf so + * that normal processing of any other chunks will work. + */ + { + struct mbuf *m_at; + m_at = m; + while (m_at->m_next != NULL) { + m_at = m_at->m_next; + } + m_at->m_next = m_sig; + if (m->m_flags & M_PKTHDR) { + /* + * We should only do this if and only if the front + * mbuf has a m_pkthdr... it should in theory. + */ + if (m_sig->m_flags & M_PKTHDR) { + /* Add back to the pkt hdr of main m chain */ + m->m_pkthdr.len += m_sig->m_len; + } else { + /* + * Got a problem, no pkthdr in split chain. + * TSNH but we will handle it just in case + */ + int mmlen = 0; + struct mbuf *lat; + printf("Warning: Hitting m_split join TSNH code - fixed\n"); + lat = m_sig; + while (lat) { + mmlen += lat->m_len; + lat = lat->m_next; + } + m->m_pkthdr.len += mmlen; + } + } + } + + if (cookie_ok == 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("handle_cookie_echo: cookie signature validation failed!\n"); + printf("offset = %u, cookie_offset = %u, sig_offset = %u\n", + (u_int32_t)offset, cookie_offset, sig_offset); + } +#endif + return (NULL); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("handle_cookie_echo: cookie signature validation passed\n"); + } +#endif + + /* + * check the cookie timestamps to be sure it's not stale + */ + SCTP_GETTIME_TIMEVAL(&now); + /* Expire time is in Ticks, so we convert to seconds */ + time_expires.tv_sec = cookie->time_entered.tv_sec + cookie->cookie_life; + time_expires.tv_usec = cookie->time_entered.tv_usec; +#ifndef __FreeBSD__ + if (timercmp(&now, &time_expires, >)) +#else + if (timevalcmp(&now, &time_expires, >)) +#endif + { + /* cookie is stale! */ + struct mbuf *op_err; + struct sctp_stale_cookie_msg *scm; + u_int32_t tim; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("sctp_handle_cookie: got a STALE cookie!\n"); + } +#endif + MGETHDR(op_err, M_DONTWAIT, MT_HEADER); + if (op_err == NULL) { + /* FOOBAR */ + return (NULL); + } + /* pre-reserve some space */ + op_err->m_data += sizeof(struct ip6_hdr); + op_err->m_data += sizeof(struct sctphdr); + op_err->m_data += sizeof(struct sctp_chunkhdr); + + /* Set the len */ + op_err->m_len = op_err->m_pkthdr.len = sizeof(struct sctp_stale_cookie_msg); + scm = mtod(op_err, struct sctp_stale_cookie_msg *); + scm->ph.param_type = htons(SCTP_CAUSE_STALE_COOKIE); + scm->ph.param_length = htons((sizeof(struct sctp_paramhdr) + + (sizeof(u_int32_t)))); + /* seconds to usec */ + tim = (now.tv_sec - time_expires.tv_sec) * 1000000; + /* add in usec */ + if (tim == 0) + tim = now.tv_usec - cookie->time_entered.tv_usec; + scm->time_usec = htonl(tim); + sctp_send_operr_to(m, iphlen, op_err, cookie->peers_vtag); + return (NULL); + } + /* + * Now we must see with the lookup address if we have an existing + * asoc. This will only happen if we were in the COOKIE-WAIT state + * and a INIT collided with us and somewhere the peer sent the + * cookie on another address besides the single address our assoc + * had for him. In this case we will have one of the tie-tags set + * at least AND the address field in the cookie can be used to + * look it up. + */ + to = NULL; + if (cookie->addr_type == SCTP_IPV6_ADDRESS) { + memset(&sin6, 0, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(sin6); + sin6.sin6_port = sh->src_port; + sin6.sin6_scope_id = cookie->scope_id; + memcpy(&sin6.sin6_addr.s6_addr, cookie->address, + sizeof(sin6.sin6_addr.s6_addr)); + to = (struct sockaddr *)&sin6; + } else if (cookie->addr_type == SCTP_IPV4_ADDRESS) { + memset(&sin, 0, sizeof(sin)); + sin.sin_family = AF_INET; + sin.sin_len = sizeof(sin); + sin.sin_port = sh->src_port; + sin.sin_addr.s_addr = cookie->address[0]; + to = (struct sockaddr *)&sin; + } + + if ((*stcb == NULL) && to) { + /* Yep, lets check */ + *stcb = sctp_findassociation_ep_addr(inp_p, to, netp, localep_sa, NULL); + if (*stcb == NULL) { + /* We should have only got back the same inp. If we + * got back a different ep we have a problem. The original + * findep got back l_inp and now + */ + if (l_inp != *inp_p) { + printf("Bad problem find_ep got a diff inp then special_locate?\n"); + } + } + } + + cookie_len -= SCTP_SIGNATURE_SIZE; + if (*stcb == NULL) { + /* this is the "normal" case... get a new TCB */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_cookie: processing NEW cookie\n"); + } +#endif + *stcb = sctp_process_cookie_new(m, iphlen, offset, sh, cookie, + cookie_len, *inp_p, netp, to, ¬ification); + /* now always decrement, since this is the normal + * case.. we had no tcb when we entered. + */ + } else { + /* this is abnormal... cookie-echo on existing TCB */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_cookie: processing EXISTING cookie\n"); + } +#endif + had_a_existing_tcb = 1; + *stcb = sctp_process_cookie_existing(m, iphlen, offset, sh, + cookie, cookie_len, *inp_p, *stcb, *netp, to, ¬ification); + } + + if (*stcb == NULL) { + /* still no TCB... must be bad cookie-echo */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("handle_cookie_echo: ACK! don't have a TCB!\n"); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + + /* + * Ok, we built an association so confirm the address + * we sent the INIT-ACK to. + */ + netl = sctp_findnet(*stcb, to); + /* This code should in theory NOT run but + */ + if (netl == NULL) { + int ret; +#ifdef SCTP_DEBUG + printf("TSNH! Huh, why do I need to add this address here?\n"); +#endif + ret = sctp_add_remote_addr(*stcb, to, 0, 100); + netl = sctp_findnet(*stcb, to); + } + if (netl) { + if (netl->dest_state & SCTP_ADDR_UNCONFIRMED) { + netl->dest_state &= ~SCTP_ADDR_UNCONFIRMED; + sctp_set_primary_addr((*stcb), (struct sockaddr *)NULL, + netl); + sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_CONFIRMED, + (*stcb), 0, (void *)netl); + } + } +#ifdef SCTP_DEBUG + else { + printf("Could not add source address for some reason\n"); + } +#endif + + if ((*inp_p)->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { + if (!had_a_existing_tcb || + (((*inp_p)->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) { + /* + * If we have a NEW cookie or the connect never reached + * the connected state during collision we must do the + * TCP accept thing. + */ + struct socket *so, *oso; + struct sctp_inpcb *inp; + if (notification == SCTP_NOTIFY_ASSOC_RESTART) { + /* + * For a restart we will keep the same socket, + * no need to do anything. I THINK!! + */ + sctp_ulp_notify(notification, *stcb, 0, NULL); + return (m); + } + oso = (*inp_p)->sctp_socket; + SCTP_TCB_UNLOCK((*stcb)); + so = sonewconn(oso, SS_ISCONNECTED); + SCTP_INP_WLOCK((*stcb)->sctp_ep); + SCTP_TCB_LOCK((*stcb)); + SCTP_INP_WUNLOCK((*stcb)->sctp_ep); + if (so == NULL) { + struct mbuf *op_err; + /* Too many sockets */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("process_cookie_new: no room for another socket!\n"); + } +#endif /* SCTP_DEBUG */ + op_err = sctp_generate_invmanparam(SCTP_CAUSE_OUT_OF_RESC); + sctp_abort_association(*inp_p, NULL, m, iphlen, + sh, op_err); + sctp_free_assoc(*inp_p, *stcb); + return (NULL); + } + inp = (struct sctp_inpcb *)so->so_pcb; + inp->sctp_flags = (SCTP_PCB_FLAGS_TCPTYPE | + SCTP_PCB_FLAGS_CONNECTED | + SCTP_PCB_FLAGS_IN_TCPPOOL | + (SCTP_PCB_COPY_FLAGS & (*inp_p)->sctp_flags) | + SCTP_PCB_FLAGS_DONT_WAKE); + inp->sctp_socket = so; + + /* + * Now we must move it from one hash table to another + * and get the tcb in the right place. + */ + sctp_move_pcb_and_assoc(*inp_p, inp, *stcb); + + /* Switch over to the new guy */ + *inp_p = inp; + + sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, + *stcb, *netp); + + sctp_ulp_notify(notification, *stcb, 0, NULL); + return (m); + } + } + if ((notification) && ((*inp_p)->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE)) { + sctp_ulp_notify(notification, *stcb, 0, NULL); + } + return (m); +} + +static void +sctp_handle_cookie_ack(struct sctp_cookie_ack_chunk *cp, + struct sctp_tcb *stcb, struct sctp_nets *net) +{ + /* cp must not be used, others call this without a c-ack :-) */ + struct sctp_association *asoc; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_cookie_ack: handling COOKIE-ACK\n"); + } +#endif + if (stcb == NULL) + return; + + asoc = &stcb->asoc; + + sctp_timer_stop(SCTP_TIMER_TYPE_COOKIE, stcb->sctp_ep, stcb, net); + + /* process according to association state */ + if (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED) { + /* state change only needed when I am in right state */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("moving to OPEN state\n"); + } +#endif + if (asoc->state & SCTP_STATE_SHUTDOWN_PENDING) { + asoc->state = SCTP_STATE_OPEN | SCTP_STATE_SHUTDOWN_PENDING; + } else { + asoc->state = SCTP_STATE_OPEN; + } + + /* update RTO */ + if (asoc->overall_error_count == 0) { + net->RTO = sctp_calculate_rto(stcb, asoc, net, + &asoc->time_entered); + } + SCTP_GETTIME_TIMEVAL(&asoc->time_entered); + sctp_ulp_notify(SCTP_NOTIFY_ASSOC_UP, stcb, 0, NULL); + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; + soisconnected(stcb->sctp_ep->sctp_socket); + } + sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, + stcb, net); + /* since we did not send a HB make sure we don't double things */ + net->hb_responded = 1; + + if (stcb->asoc.sctp_autoclose_ticks && + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_AUTOCLOSE)) { + sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, + stcb->sctp_ep, stcb, NULL); + } + + /* + * set ASCONF timer if ASCONFs are pending and allowed + * (eg. addresses changed when init/cookie echo in flight) + */ + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) && + (stcb->asoc.peer_supports_asconf) && + (!TAILQ_EMPTY(&stcb->asoc.asconf_queue))) { + sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, + stcb->sctp_ep, stcb, + stcb->asoc.primary_destination); + } + + } + /* Toss the cookie if I can */ + sctp_toss_old_cookies(asoc); + if (!TAILQ_EMPTY(&asoc->sent_queue)) { + /* Restart the timer if we have pending data */ + struct sctp_tmit_chunk *chk; + chk = TAILQ_FIRST(&asoc->sent_queue); + if (chk) { + sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, + stcb, chk->whoTo); + } + } + +} + +static void +sctp_handle_ecn_echo(struct sctp_ecne_chunk *cp, + struct sctp_tcb *stcb) +{ + struct sctp_nets *net; + struct sctp_tmit_chunk *lchk; + u_int32_t tsn; + if (ntohs(cp->ch.chunk_length) != sizeof(struct sctp_ecne_chunk)) { + return; + } + sctp_pegs[SCTP_ECNE_RCVD]++; + tsn = ntohl(cp->tsn); + /* ECN Nonce stuff: need a resync and disable the nonce sum check */ + /* Also we make sure we disable the nonce_wait */ + lchk = TAILQ_FIRST(&stcb->asoc.send_queue); + if (lchk == NULL) { + stcb->asoc.nonce_resync_tsn = stcb->asoc.sending_seq; + } else { + stcb->asoc.nonce_resync_tsn = lchk->rec.data.TSN_seq; + } + stcb->asoc.nonce_wait_for_ecne = 0; + stcb->asoc.nonce_sum_check = 0; + + /* Find where it was sent, if possible */ + net = NULL; + lchk = TAILQ_FIRST(&stcb->asoc.sent_queue); + while (lchk) { + if (lchk->rec.data.TSN_seq == tsn) { + net = lchk->whoTo; + break; + } + if (compare_with_wrap(lchk->rec.data.TSN_seq, tsn, MAX_SEQ)) + break; + lchk = TAILQ_NEXT(lchk, sctp_next); + } + if (net == NULL) + /* default is we use the primary */ + net = stcb->asoc.primary_destination; + + if (compare_with_wrap(tsn, stcb->asoc.last_cwr_tsn, MAX_TSN)) { +#ifdef SCTP_CWND_LOGGING + int old_cwnd; +#endif +#ifdef SCTP_CWND_LOGGING + old_cwnd = net->cwnd; +#endif + sctp_pegs[SCTP_CWR_PERFO]++; + net->ssthresh = net->cwnd / 2; + if (net->ssthresh < net->mtu) { + net->ssthresh = net->mtu; + /* here back off the timer as well, to slow us down */ + net->RTO <<= 2; + } + net->cwnd = net->ssthresh; +#ifdef SCTP_CWND_LOGGING + sctp_log_cwnd(net, (net->cwnd-old_cwnd), SCTP_CWND_LOG_FROM_SAT); +#endif + /* we reduce once every RTT. So we will only lower + * cwnd at the next sending seq i.e. the resync_tsn. + */ + stcb->asoc.last_cwr_tsn = stcb->asoc.nonce_resync_tsn; + } + /* + * We always send a CWR this way if our previous one was lost + * our peer will get an update, or if it is not time again + * to reduce we still get the cwr to the peer. + */ + sctp_send_cwr(stcb, net, tsn); +} + +static void +sctp_handle_ecn_cwr(struct sctp_cwr_chunk *cp, struct sctp_tcb *stcb) +{ + /* Here we get a CWR from the peer. We must look in + * the outqueue and make sure that we have a covered + * ECNE in teh control chunk part. If so remove it. + */ + struct sctp_tmit_chunk *chk; + struct sctp_ecne_chunk *ecne; + + TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) { + if (chk->rec.chunk_id != SCTP_ECN_ECHO) { + continue; + } + /* Look for and remove if it is the right TSN. Since + * there is only ONE ECNE on the control queue at + * any one time we don't need to worry about more than + * one! + */ + ecne = mtod(chk->data, struct sctp_ecne_chunk *); + if (compare_with_wrap(ntohl(cp->tsn), ntohl(ecne->tsn), + MAX_TSN) || (cp->tsn == ecne->tsn)) { + /* this covers this ECNE, we can remove it */ + TAILQ_REMOVE(&stcb->asoc.control_send_queue, chk, + sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + stcb->asoc.ctrl_queue_cnt--; + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + break; + } + } +} + +static void +sctp_handle_shutdown_complete(struct sctp_shutdown_complete_chunk *cp, + struct sctp_tcb *stcb, struct sctp_nets *net) +{ + struct sctp_association *asoc; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_handle_shutdown_complete: handling SHUTDOWN-COMPLETE\n"); + } +#endif + if (stcb == NULL) + return; + + asoc = &stcb->asoc; + /* process according to association state */ + if (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT) { + /* unexpected SHUTDOWN-COMPLETE... so ignore... */ + return; + } + /* notify upper layer protocol */ + sctp_ulp_notify(SCTP_NOTIFY_ASSOC_DOWN, stcb, 0, NULL); + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + stcb->sctp_ep->sctp_flags &= ~SCTP_PCB_FLAGS_CONNECTED; + stcb->sctp_ep->sctp_socket->so_snd.sb_cc = 0; + stcb->sctp_ep->sctp_socket->so_snd.sb_mbcnt = 0; + soisdisconnected(stcb->sctp_ep->sctp_socket); + } + /* are the queues empty? they should be */ + if (!TAILQ_EMPTY(&asoc->send_queue) || + !TAILQ_EMPTY(&asoc->sent_queue) || + !TAILQ_EMPTY(&asoc->out_wheel)) { + sctp_report_all_outbound(stcb); + } + /* stop the timer */ + sctp_timer_stop(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, net); + /* free the TCB */ + sctp_free_assoc(stcb->sctp_ep, stcb); + return; +} + +static int +process_chunk_drop(struct sctp_tcb *stcb, struct sctp_chunk_desc *desc, + struct sctp_nets *net, u_int8_t flg) +{ + switch (desc->chunk_type) { + case SCTP_DATA: + /* find the tsn to resend (possibly */ + { + u_int32_t tsn; + struct sctp_tmit_chunk *tp1; + tsn = ntohl(desc->tsn_ifany); + tp1 = TAILQ_FIRST(&stcb->asoc.sent_queue); + while (tp1) { + if (tp1->rec.data.TSN_seq == tsn) { + /* found it */ + break; + } + if (compare_with_wrap(tp1->rec.data.TSN_seq, tsn, + MAX_TSN)) { + /* not found */ + tp1 = NULL; + break; + } + tp1 = TAILQ_NEXT(tp1, sctp_next); + } + if (tp1 == NULL) { + /* Do it the other way */ + sctp_pegs[SCTP_PDRP_DNFND]++; + tp1 = TAILQ_FIRST(&stcb->asoc.sent_queue); + while (tp1) { + if (tp1->rec.data.TSN_seq == tsn) { + /* found it */ + break; + } + tp1 = TAILQ_NEXT(tp1, sctp_next); + } + } + if (tp1 == NULL) { + sctp_pegs[SCTP_PDRP_TSNNF]++; + } + if ((tp1) && (tp1->sent < SCTP_DATAGRAM_ACKED)) { + u_int8_t *ddp; + if (((tp1->rec.data.state_flags & SCTP_WINDOW_PROBE) == SCTP_WINDOW_PROBE) && + ((flg & SCTP_FROM_MIDDLE_BOX) == 0)) { + sctp_pegs[SCTP_PDRP_DIWNP]++; + return (0); + } + if (stcb->asoc.peers_rwnd == 0 && + (flg & SCTP_FROM_MIDDLE_BOX)) { + sctp_pegs[SCTP_PDRP_DIZRW]++; + return (0); + } + ddp = (u_int8_t *)(mtod(tp1->data, caddr_t) + + sizeof(struct sctp_data_chunk)); + { + unsigned int iii; + for (iii = 0; iii < sizeof(desc->data_bytes); + iii++) { + if (ddp[iii] != desc->data_bytes[iii]) { + sctp_pegs[SCTP_PDRP_BADD]++; + return (-1); + } + } + } + if (tp1->sent != SCTP_DATAGRAM_RESEND) { + stcb->asoc.sent_queue_retran_cnt++; + } + /* We zero out the nonce so resync not needed */ + tp1->rec.data.ect_nonce = 0; + + if (tp1->do_rtt) { + /* + * this guy had a RTO calculation pending on it, + * cancel it + */ + tp1->whoTo->rto_pending = 0; + tp1->do_rtt = 0; + } + sctp_pegs[SCTP_PDRP_MARK]++; + tp1->sent = SCTP_DATAGRAM_RESEND; + /* + * mark it as if we were doing a FR, since we + * will be getting gap ack reports behind the + * info from the router. + */ + tp1->rec.data.doing_fast_retransmit = 1; + /* + * mark the tsn with what sequences can cause a new FR. + */ + if (TAILQ_EMPTY(&stcb->asoc.send_queue) ) { + tp1->rec.data.fast_retran_tsn = stcb->asoc.sending_seq; + } else { + tp1->rec.data.fast_retran_tsn = (TAILQ_FIRST(&stcb->asoc.send_queue))->rec.data.TSN_seq; + } + + /* restart the timer */ + sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, + stcb, tp1->whoTo); + sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, + stcb, tp1->whoTo); + + /* fix counts and things */ + tp1->whoTo->flight_size -= tp1->send_size; + if (tp1->whoTo->flight_size < 0) { + tp1->whoTo->flight_size = 0; + } + stcb->asoc.total_flight -= tp1->book_size; + if (stcb->asoc.total_flight < 0) { + stcb->asoc.total_flight = 0; + } + stcb->asoc.total_flight_count--; + if (stcb->asoc.total_flight_count < 0) { + stcb->asoc.total_flight_count = 0; + } + tp1->snd_count--; + } + { + /* audit code */ + unsigned int audit; + audit = 0; + TAILQ_FOREACH(tp1, &stcb->asoc.sent_queue, sctp_next) { + if (tp1->sent == SCTP_DATAGRAM_RESEND) + audit++; + } + TAILQ_FOREACH(tp1, &stcb->asoc.control_send_queue, + sctp_next) { + if (tp1->sent == SCTP_DATAGRAM_RESEND) + audit++; + } + if (audit != stcb->asoc.sent_queue_retran_cnt) { + printf("**Local Audit finds cnt:%d asoc cnt:%d\n", + audit, stcb->asoc.sent_queue_retran_cnt); +#ifndef SCTP_AUDITING_ENABLED + stcb->asoc.sent_queue_retran_cnt = audit; +#endif + } + } + } + break; + case SCTP_ASCONF: + { + struct sctp_tmit_chunk *asconf; + TAILQ_FOREACH(asconf, &stcb->asoc.control_send_queue, + sctp_next) { + if (asconf->rec.chunk_id == SCTP_ASCONF) { + break; + } + } + if (asconf) { + if (asconf->sent != SCTP_DATAGRAM_RESEND) + stcb->asoc.sent_queue_retran_cnt++; + asconf->sent = SCTP_DATAGRAM_RESEND; + asconf->snd_count--; + } + } + break; + case SCTP_INITIATION: + /* resend the INIT */ + stcb->asoc.dropped_special_cnt++; + if (stcb->asoc.dropped_special_cnt < SCTP_RETRY_DROPPED_THRESH) { + /* + * If we can get it in, in a few attempts we do this, + * otherwise we let the timer fire. + */ + sctp_timer_stop(SCTP_TIMER_TYPE_INIT, stcb->sctp_ep, + stcb, net); + sctp_send_initiate(stcb->sctp_ep, stcb); + } + break; + case SCTP_SELECTIVE_ACK: + /* resend the sack */ + sctp_send_sack(stcb); + break; + case SCTP_HEARTBEAT_REQUEST: + /* resend a demand HB */ + sctp_send_hb(stcb, 1, net); + break; + case SCTP_SHUTDOWN: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("%s:%d sends a shutdown\n", + __FILE__, + __LINE__ + ); + } +#endif + sctp_send_shutdown(stcb, net); + break; + case SCTP_SHUTDOWN_ACK: + sctp_send_shutdown_ack(stcb, net); + break; + case SCTP_COOKIE_ECHO: + { + struct sctp_tmit_chunk *cookie; + cookie = NULL; + TAILQ_FOREACH(cookie, &stcb->asoc.control_send_queue, + sctp_next) { + if (cookie->rec.chunk_id == SCTP_COOKIE_ECHO) { + break; + } + } + if (cookie) { + if (cookie->sent != SCTP_DATAGRAM_RESEND) + stcb->asoc.sent_queue_retran_cnt++; + cookie->sent = SCTP_DATAGRAM_RESEND; + sctp_timer_stop(SCTP_TIMER_TYPE_COOKIE, stcb->sctp_ep, stcb, net); + } + } + break; + case SCTP_COOKIE_ACK: + sctp_send_cookie_ack(stcb); + break; + case SCTP_ASCONF_ACK: + /* resend last asconf ack */ + sctp_send_asconf_ack(stcb, 1); + break; + case SCTP_FORWARD_CUM_TSN: + send_forward_tsn(stcb, &stcb->asoc); + break; + /* can't do anything with these */ + case SCTP_PACKET_DROPPED: + case SCTP_INITIATION_ACK: /* this should not happen */ + case SCTP_HEARTBEAT_ACK: + case SCTP_ABORT_ASSOCIATION: + case SCTP_OPERATION_ERROR: + case SCTP_SHUTDOWN_COMPLETE: + case SCTP_ECN_ECHO: + case SCTP_ECN_CWR: + default: + break; + } + return (0); +} + +static void +sctp_reset_in_stream(struct sctp_tcb *stcb, + struct sctp_stream_reset_response *resp, int number_entries) +{ + int i; + uint16_t *list, temp; + + /* We set things to 0xffff since this is the last delivered + * sequence and we will be sending in 0 after the reset. + */ + + if (resp->reset_flags & SCTP_RESET_PERFORMED) { + if (number_entries) { + list = resp->list_of_streams; + for (i = 0; i < number_entries; i++) { + temp = ntohs(list[i]); + list[i] = temp; + if (list[i] >= stcb->asoc.streamincnt) { + printf("Invalid stream in-stream reset %d\n", list[i]); + continue; + } + stcb->asoc.strmin[(list[i])].last_sequence_delivered = 0xffff; + } + } else { + list = NULL; + for (i = 0; i < stcb->asoc.streamincnt; i++) { + stcb->asoc.strmin[i].last_sequence_delivered = 0xffff; + } + } + sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_RECV, stcb, number_entries, (void *)list); + } +} + +static void +sctp_clean_up_stream_reset(struct sctp_tcb *stcb) +{ + struct sctp_tmit_chunk *chk, *nchk; + struct sctp_association *asoc; + + asoc = &stcb->asoc; + + for (chk = TAILQ_FIRST(&asoc->control_send_queue); + chk; chk = nchk) { + nchk = TAILQ_NEXT(chk, sctp_next); + if (chk->rec.chunk_id == SCTP_STREAM_RESET) { + struct sctp_stream_reset_req *strreq; + strreq = mtod(chk->data, struct sctp_stream_reset_req *); + if (strreq->sr_req.ph.param_type == ntohs(SCTP_STR_RESET_RESPONSE)) { + /* we only clean up the request */ + continue; + } else if (strreq->sr_req.ph.param_type != ntohs(SCTP_STR_RESET_REQUEST)) { + printf("TSNH, an unknown stream reset request is in queue %x\n", + (u_int)ntohs(strreq->sr_req.ph.param_type)); + continue; + } + sctp_timer_stop(SCTP_TIMER_TYPE_STRRESET, stcb->sctp_ep, stcb, chk->whoTo); + TAILQ_REMOVE(&asoc->control_send_queue, + chk, + sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + asoc->ctrl_queue_cnt--; + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + /* we can only have one of these so we break */ + break; + } + } +} + + +void +sctp_handle_stream_reset_response(struct sctp_tcb *stcb, + struct sctp_stream_reset_response *resp) +{ + uint32_t seq, tsn; + int number_entries, param_length; + + param_length = ntohs(resp->ph.param_length); + seq = ntohl(resp->reset_req_seq_resp); + if (seq == stcb->asoc.str_reset_seq_out) { + sctp_clean_up_stream_reset(stcb); + stcb->asoc.str_reset_seq_out++; + stcb->asoc.stream_reset_outstanding = 0; + tsn = ntohl(resp->reset_at_tsn); + number_entries = (param_length - sizeof(struct sctp_stream_reset_response))/sizeof(uint16_t); + tsn--; + if ((tsn == stcb->asoc.cumulative_tsn) || + (compare_with_wrap(stcb->asoc.cumulative_tsn, tsn, MAX_TSN))) { + /* no problem we are good to go */ + sctp_reset_in_stream(stcb, resp, number_entries); + } else { + /* So, we have a stream reset but there + * is pending data. We need to copy + * out the stream_reset and then queue + * any data = or > resp->reset_at_tsn + */ + if (stcb->asoc.pending_reply != NULL) { + /* FIX ME FIX ME + * This IS WRONG. We need + * to queue each of these up + * and only release the chunks + * for each reset that the cum-ack + * goes by. This is a short cut. + */ + FREE(stcb->asoc.pending_reply, M_PCB); + } + MALLOC(stcb->asoc.pending_reply, struct sctp_stream_reset_response *, param_length, + M_PCB, M_NOWAIT); + memcpy(stcb->asoc.pending_reply, resp, param_length); + } + + } else { + /* duplicate */ +#ifdef SCTP_DEBUG + printf("Duplicate old stream reset resp next:%x this one:%x\n", + stcb->asoc.str_reset_seq_out, seq); +#endif + } +} + + +static void +sctp_handle_stream_reset(struct sctp_tcb *stcb, struct sctp_stream_reset_req *sr_req) +{ + int chk_length, param_len; + struct sctp_paramhdr *ph; + /* now it may be a reset or a reset-response */ + struct sctp_stream_reset_request *req; + struct sctp_stream_reset_response *resp; + chk_length = ntohs(sr_req->ch.chunk_length); + + ph = (struct sctp_paramhdr *)&sr_req->sr_req; + while ((size_t)chk_length >= sizeof(struct sctp_stream_reset_request)) { + param_len = ntohs(ph->param_length); + if (ntohs(ph->param_type) == SCTP_STR_RESET_REQUEST) { + /* this will send the ACK and do the reset if needed */ + req = (struct sctp_stream_reset_request *)ph; + sctp_send_str_reset_ack(stcb, req); + } else if (ntohs(ph->param_type) == SCTP_STR_RESET_RESPONSE) { + /* Now here is a tricky one. We reset our receive side + * of the streams. But what happens if the peers + * next sending TSN is NOT equal to 1 minus our cumack? + * And if his cumack is not equal to our next one out - 1 + * we have another problem if this is receprical. + */ + resp = (struct sctp_stream_reset_response *)ph; + sctp_handle_stream_reset_response(stcb, resp); + } + ph = (struct sctp_paramhdr *)((caddr_t)ph + SCTP_SIZE32(param_len)); + chk_length -= SCTP_SIZE32(param_len); + } +} + +/* + * Handle a router or endpoints report of a packet loss, there + * are two ways to handle this, either we get the whole packet + * and must disect it ourselves (possibly with truncation and + * or corruption) or it is a summary from a middle box that did + * the disectting for us. + */ +static void +sctp_handle_packet_dropped(struct sctp_pktdrop_chunk *cp, + struct sctp_tcb *stcb, struct sctp_nets *net) +{ + u_int32_t bottle_bw, on_queue; + u_int16_t trunc_len; + unsigned int chlen; + unsigned int at; + struct sctp_chunk_desc desc; + struct sctp_chunkhdr *ch; + + chlen = ntohs(cp->ch.chunk_length); + chlen -= sizeof(struct sctp_pktdrop_chunk); + /* XXX possible chlen underflow */ + if (chlen == 0) { + ch = NULL; + if (cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX) + sctp_pegs[SCTP_PDRP_BWRPT]++; + } else { + ch = (struct sctp_chunkhdr *)(cp->data + sizeof(struct sctphdr)); + chlen -= sizeof(struct sctphdr); + /* XXX possible chlen underflow */ + memset(&desc, 0, sizeof(desc)); + } + + /* first update a rwnd possibly */ + if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX) == 0) { + /* From a peer, we get a rwnd report */ + u_int32_t a_rwnd; + + sctp_pegs[SCTP_PDRP_FEHOS]++; + + bottle_bw = ntohl(cp->bottle_bw); + on_queue = ntohl(cp->current_onq); + if (bottle_bw && on_queue) { + /* a rwnd report is in here */ + if (bottle_bw > on_queue) + a_rwnd = bottle_bw - on_queue; + else + a_rwnd = 0; + + if (a_rwnd <= 0) + stcb->asoc.peers_rwnd = 0; + else { + if (a_rwnd > stcb->asoc.total_flight) { + stcb->asoc.peers_rwnd = + a_rwnd - stcb->asoc.total_flight; + } else { + stcb->asoc.peers_rwnd = 0; + } + if (stcb->asoc.peers_rwnd < + stcb->sctp_ep->sctp_ep.sctp_sws_sender) { + /* SWS sender side engages */ + stcb->asoc.peers_rwnd = 0; + } + } + } + } else { + sctp_pegs[SCTP_PDRP_FMBOX]++; + } + trunc_len = (u_int16_t)ntohs(cp->trunc_len); + /* now the chunks themselves */ + while ((ch != NULL) && (chlen >= sizeof(struct sctp_chunkhdr))) { + desc.chunk_type = ch->chunk_type; + /* get amount we need to move */ + at = ntohs(ch->chunk_length); + if (at < sizeof(struct sctp_chunkhdr)) { + /* corrupt chunk, maybe at the end? */ + sctp_pegs[SCTP_PDRP_CRUPT]++; + break; + } + if (trunc_len == 0) { + /* we are supposed to have all of it */ + if (at > chlen) { + /* corrupt skip it */ + sctp_pegs[SCTP_PDRP_CRUPT]++; + break; + } + } else { + /* is there enough of it left ? */ + if (desc.chunk_type == SCTP_DATA) { + if (chlen < (sizeof(struct sctp_data_chunk) + + sizeof(desc.data_bytes))) { + break; + } + } else { + if (chlen < sizeof(struct sctp_chunkhdr)) { + break; + } + } + } + if (desc.chunk_type == SCTP_DATA) { + /* can we get out the tsn? */ + if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX)) + sctp_pegs[SCTP_PDRP_MB_DA]++; + + if (chlen >= (sizeof(struct sctp_data_chunk) + sizeof(u_int32_t)) ) { + /* yep */ + struct sctp_data_chunk *dcp; + u_int8_t *ddp; + unsigned int iii; + dcp = (struct sctp_data_chunk *)ch; + ddp = (u_int8_t *)(dcp + 1); + for (iii = 0; iii < sizeof(desc.data_bytes); iii++) { + desc.data_bytes[iii] = ddp[iii]; + } + desc.tsn_ifany = dcp->dp.tsn; + } else { + /* nope we are done. */ + sctp_pegs[SCTP_PDRP_NEDAT]++; + break; + } + } else { + if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX)) + sctp_pegs[SCTP_PDRP_MB_CT]++; + } + + if (process_chunk_drop(stcb, &desc, net, cp->ch.chunk_flags)) { + sctp_pegs[SCTP_PDRP_PDBRK]++; + break; + } + if (SCTP_SIZE32(at) > chlen) { + break; + } + chlen -= SCTP_SIZE32(at); + if (chlen < sizeof(struct sctp_chunkhdr)) { + /* done, none left */ + break; + } + ch = (struct sctp_chunkhdr *)((caddr_t)ch + SCTP_SIZE32(at)); + } + + /* now middle boxes in sat networks get a cwnd bump */ + if ((cp->ch.chunk_flags & SCTP_FROM_MIDDLE_BOX) && + (stcb->asoc.sat_t3_loss_recovery == 0) && + (stcb->asoc.sat_network)) { + /* + * This is debateable but for sat networks it makes sense + * Note if a T3 timer has went off, we will prohibit any + * changes to cwnd until we exit the t3 loss recovery. + */ + u_int32_t bw_avail; + int rtt, incr; +#ifdef SCTP_CWND_LOGGING + int old_cwnd=net->cwnd; +#endif + /* need real RTT for this calc */ + rtt = ((net->lastsa >> 2) + net->lastsv) >> 1; + /* get bottle neck bw */ + bottle_bw = ntohl(cp->bottle_bw); + /* and whats on queue */ + on_queue = ntohl(cp->current_onq); + /* + * adjust the on-queue if our flight is more it could be + * that the router has not yet gotten data "in-flight" to it + */ + if (on_queue < net->flight_size) + on_queue = net->flight_size; + + /* calculate the available space */ + bw_avail = (bottle_bw*rtt)/1000; + if (bw_avail > bottle_bw) { + /* + * Cap the growth to no more than the bottle neck. + * This can happen as RTT slides up due to queues. + * It also means if you have more than a 1 second + * RTT with a empty queue you will be limited to + * the bottle_bw per second no matter if + * other points have 1/2 the RTT and you could + * get more out... + */ + bw_avail = bottle_bw; + } + + if (on_queue > bw_avail) { + /* + * No room for anything else don't allow anything + * else to be "added to the fire". + */ + int seg_inflight, seg_onqueue, my_portion; + net->partial_bytes_acked = 0; + + /* how much are we over queue size? */ + incr = on_queue - bw_avail; + if (stcb->asoc.seen_a_sack_this_pkt) { + /* undo any cwnd adjustment that + * the sack might have made + */ + net->cwnd = net->prev_cwnd; + } + + /* Now how much of that is mine? */ + seg_inflight = net->flight_size / net->mtu; + seg_onqueue = on_queue / net->mtu; + my_portion = (incr * seg_inflight)/seg_onqueue; + + /* Have I made an adjustment already */ + if (net->cwnd > net->flight_size) { + /* for this flight I made an adjustment + * we need to decrease the portion by a share + * our previous adjustment. + */ + int diff_adj; + diff_adj = net->cwnd - net->flight_size; + if (diff_adj > my_portion) + my_portion = 0; + else + my_portion -= diff_adj; + } + + /* back down to the previous cwnd (assume + * we have had a sack before this packet). minus + * what ever portion of the overage is my fault. + */ + net->cwnd -= my_portion; + + /* we will NOT back down more than 1 MTU */ + if (net->cwnd <= net->mtu) { + net->cwnd = net->mtu; + } + /* force into CA */ + net->ssthresh = net->cwnd - 1; + } else { + /* + * Take 1/4 of the space left or + * max burst up .. whichever is less. + */ + incr = min((bw_avail - on_queue) >> 2, + (int)stcb->asoc.max_burst * (int)net->mtu); + net->cwnd += incr; + } + if (net->cwnd > bw_avail) { + /* We can't exceed the pipe size */ + net->cwnd = bw_avail; + } + if (net->cwnd < net->mtu) { + /* We always have 1 MTU */ + net->cwnd = net->mtu; + } +#ifdef SCTP_CWND_LOGGING + if (net->cwnd - old_cwnd != 0) { + /* log only changes */ + sctp_log_cwnd(net, (net->cwnd - old_cwnd), + SCTP_CWND_LOG_FROM_SAT); + } +#endif + } +} + +extern int sctp_strict_init; + +/* + * handles all control chunks in a packet + * inputs: + * - m: mbuf chain, assumed to still contain IP/SCTP header + * - stcb: is the tcb found for this packet + * - offset: offset into the mbuf chain to first chunkhdr + * - length: is the length of the complete packet + * outputs: + * - length: modified to remaining length after control processing + * - netp: modified to new sctp_nets after cookie-echo processing + * - return NULL to discard the packet (ie. no asoc, bad packet,...) + * otherwise return the tcb for this packet + */ +static struct sctp_tcb * +sctp_process_control(struct mbuf *m, int iphlen, int *offset, int length, + struct sctphdr *sh, struct sctp_chunkhdr *ch, struct sctp_inpcb *inp, + struct sctp_tcb *stcb, struct sctp_nets **netp, int *fwd_tsn_seen) +{ + struct sctp_association *asoc; + u_int32_t vtag_in; + int num_chunks = 0; /* number of control chunks processed */ + int chk_length; + int ret; + + /* + * How big should this be, and should it be alloc'd? + * Lets try the d-mtu-ceiling for now (2k) and that should + * hopefully work ... until we get into jumbo grams and such.. + */ + u_int8_t chunk_buf[DEFAULT_CHUNK_BUFFER]; + struct sctp_tcb *locked_tcb = stcb; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("sctp_process_control: iphlen=%u, offset=%u, length=%u stcb:%p\n", + iphlen, *offset, length, stcb); + } +#endif /* SCTP_DEBUG */ + + /* validate chunk header length... */ + if (ntohs(ch->chunk_length) < sizeof(*ch)) { + return (NULL); + } + + /* + * validate the verification tag + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("sctp_process_control: validating vtags\n"); + } +#endif /* SCTP_DEBUG */ + vtag_in = ntohl(sh->v_tag); + if (ch->chunk_type == SCTP_INITIATION) { + if (vtag_in != 0) { + /* protocol error- silently discard... */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("sctp_process_control: INIT with vtag != 0\n"); + } +#endif /* SCTP_DEBUG */ + sctp_pegs[SCTP_BAD_VTAGS]++; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + } + } else if (ch->chunk_type != SCTP_COOKIE_ECHO) { + /* + * first check if it's an ASCONF with an unknown src addr + * we need to look inside to find the association + */ + if (ch->chunk_type == SCTP_ASCONF && stcb == NULL) { + stcb = sctp_findassociation_ep_asconf(m, iphlen, + *offset, sh, &inp, netp); + } + if (stcb == NULL) { + /* no association, so it's out of the blue... */ + sctp_handle_ootb(m, iphlen, *offset, sh, inp, NULL); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("sctp_process_control: handling OOTB packet, chunk type=%xh\n", + ch->chunk_type); + } +#endif /* SCTP_DEBUG */ + *offset = length; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + } + asoc = &stcb->asoc; + /* ABORT and SHUTDOWN can use either v_tag... */ + if ((ch->chunk_type == SCTP_ABORT_ASSOCIATION) || + (ch->chunk_type == SCTP_SHUTDOWN_COMPLETE) || + (ch->chunk_type == SCTP_PACKET_DROPPED)) { + if ((vtag_in == asoc->my_vtag) || + ((ch->chunk_flags & SCTP_HAD_NO_TCB) && + (vtag_in == asoc->peer_vtag))) { + /* this is valid */ + } else { + /* drop this packet... */ + sctp_pegs[SCTP_BAD_VTAGS]++; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + } + } else if (ch->chunk_type == SCTP_SHUTDOWN_ACK) { + if (vtag_in != asoc->my_vtag) { + /* + * this could be a stale SHUTDOWN-ACK or the + * peer never got the SHUTDOWN-COMPLETE and + * is still hung; we have started a new asoc + * but it won't complete until the shutdown is + * completed + */ + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + sctp_handle_ootb(m, iphlen, *offset, sh, inp, + NULL); + return (NULL); + } + } else { + /* for all other chunks, vtag must match */ + + if (vtag_in != asoc->my_vtag) { + /* invalid vtag... */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("invalid vtag: %xh, expect %xh\n", vtag_in, asoc->my_vtag); + } +#endif /* SCTP_DEBUG */ + sctp_pegs[SCTP_BAD_VTAGS]++; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + *offset = length; + return (NULL); + } + } + } /* end if !SCTP_COOKIE_ECHO */ + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("sctp_process_control: vtags ok, processing ctrl chunks\n"); + } +#endif /* SCTP_DEBUG */ + + /* + * process all control chunks... + */ + if (((ch->chunk_type == SCTP_SELECTIVE_ACK) || + (ch->chunk_type == SCTP_HEARTBEAT_REQUEST)) && + (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_ECHOED)) { + /* implied cookie-ack.. we must have lost the ack */ + stcb->asoc.overall_error_count = 0; + sctp_handle_cookie_ack((struct sctp_cookie_ack_chunk *)ch, stcb, *netp); + } + + while (IS_SCTP_CONTROL(ch)) { + /* validate chunk length */ + chk_length = ntohs(ch->chunk_length); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT2) { + printf("sctp_process_control: processing a chunk type=%u, len=%u\n", ch->chunk_type, chk_length); + } +#endif /* SCTP_DEBUG */ + if ((size_t)chk_length < sizeof(*ch) || + (*offset + chk_length) > length) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("sctp_process_control: chunk length invalid! *offset:%u, chk_length:%u > length:%u\n", + *offset, length, chk_length); + } +#endif /* SCTP_DEBUG */ + *offset = length; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + } + + /* + * INIT-ACK only gets the init ack "header" portion only + * because we don't have to process the peer's COOKIE. + * All others get a complete chunk. + */ + if (ch->chunk_type == SCTP_INITIATION_ACK) { + /* get an init-ack chunk */ + ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, + sizeof(struct sctp_init_ack), chunk_buf); + if (ch == NULL) { + *offset = length; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + } + } else { + /* get a complete chunk... */ + if ((size_t)chk_length > sizeof(chunk_buf)) { + struct mbuf *oper; + struct sctp_paramhdr *phdr; + oper = NULL; + MGETHDR(oper, M_DONTWAIT, MT_HEADER); + if (oper) { + /* pre-reserve some space */ + oper->m_data += + sizeof(struct sctp_chunkhdr); + phdr = + mtod(oper, struct sctp_paramhdr *); + phdr->param_type = + htons(SCTP_CAUSE_OUT_OF_RESC); + phdr->param_length = + htons(sizeof(struct sctp_paramhdr)); + sctp_queue_op_err(stcb, oper); + } + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + } + ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, + chk_length, chunk_buf); + if (ch == NULL) { + printf("sctp_process_control: Can't get the all data....\n"); + *offset = length; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + } + + } + num_chunks++; + /* Save off the last place we got a control from */ + if ((*netp) && stcb) { + stcb->asoc.last_control_chunk_from = *netp; + } +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xB0, ch->chunk_type); +#endif + switch (ch->chunk_type) { + case SCTP_INITIATION: + /* must be first and only chunk */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_INIT\n"); + } +#endif /* SCTP_DEBUG */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + /* We are not interested anymore */ + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) { + /* finish the job now */ + sctp_inpcb_free(inp, 1); + } + *offset = length; + return (NULL); + } + if ((num_chunks > 1) || + (sctp_strict_init && (length - *offset > SCTP_SIZE32(chk_length)))) { + *offset = length; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + } + if ((stcb != NULL) && + (SCTP_GET_STATE(&stcb->asoc) == + SCTP_STATE_SHUTDOWN_ACK_SENT)) { + sctp_send_shutdown_ack(stcb, + stcb->asoc.primary_destination); + *offset = length; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + } + sctp_handle_init(m, iphlen, *offset, sh, + (struct sctp_init_chunk *)ch, inp, stcb, *netp); + *offset = length; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + break; + case SCTP_INITIATION_ACK: + /* must be first and only chunk */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_INIT-ACK\n"); + } +#endif /* SCTP_DEBUG */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + /* We are not interested anymore */ + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + *offset = length; + if (stcb) { + sctp_free_assoc(inp, stcb); + } else { + if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) { + /* finish the job now */ + sctp_inpcb_free(inp, 1); + } + } + return (NULL); + } + if ((num_chunks > 1) || + (sctp_strict_init && (length - *offset > SCTP_SIZE32(chk_length)))) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("Length is %d rounded chk_length:%d .. dropping\n", + length - *offset, + SCTP_SIZE32(chk_length)); + } +#endif + *offset = length; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + } + ret = sctp_handle_init_ack(m, iphlen, *offset, sh, + (struct sctp_init_ack_chunk *)ch, stcb, *netp); + /* + * Special case, I must call the output routine + * to get the cookie echoed + */ + if ((stcb) && ret == 0) + sctp_chunk_output(stcb->sctp_ep, stcb, 2); + *offset = length; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("All done INIT-ACK processing\n"); + } +#endif + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + return (NULL); + break; + case SCTP_SELECTIVE_ACK: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_SACK\n"); + } +#endif /* SCTP_DEBUG */ + sctp_pegs[SCTP_PEG_SACKS_SEEN]++; + { + int abort_now = 0; + stcb->asoc.seen_a_sack_this_pkt = 1; + sctp_handle_sack((struct sctp_sack_chunk *)ch, + stcb, *netp, &abort_now); + if (abort_now) { + /* ABORT signal from sack processing */ + *offset = length; + return (NULL); + } + } + break; + case SCTP_HEARTBEAT_REQUEST: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_HEARTBEAT\n"); + } +#endif /* SCTP_DEBUG */ + sctp_pegs[SCTP_HB_RECV]++; + sctp_send_heartbeat_ack(stcb, m, *offset, chk_length, + *netp); + + /* He's alive so give him credit */ + stcb->asoc.overall_error_count = 0; + break; + case SCTP_HEARTBEAT_ACK: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_HEARTBEAT-ACK\n"); + } +#endif /* SCTP_DEBUG */ + + /* He's alive so give him credit */ + stcb->asoc.overall_error_count = 0; + + sctp_pegs[SCTP_HB_ACK_RECV]++; + sctp_handle_heartbeat_ack((struct sctp_heartbeat_chunk *)ch, + stcb, *netp); + break; + case SCTP_ABORT_ASSOCIATION: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_ABORT\n"); + } +#endif /* SCTP_DEBUG */ + sctp_handle_abort((struct sctp_abort_chunk *)ch, + stcb, *netp); + *offset = length; + return (NULL); + break; + case SCTP_SHUTDOWN: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_SHUTDOWN\n"); + } +#endif /* SCTP_DEBUG */ + { + int abort_flag = 0; + sctp_handle_shutdown((struct sctp_shutdown_chunk *)ch, + stcb, *netp, &abort_flag); + if (abort_flag) { + *offset = length; + return (NULL); + } + } + break; + case SCTP_SHUTDOWN_ACK: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_SHUTDOWN-ACK\n"); + } +#endif /* SCTP_DEBUG */ + sctp_handle_shutdown_ack((struct sctp_shutdown_ack_chunk *)ch, stcb, *netp); + *offset = length; + return (NULL); + break; + case SCTP_OPERATION_ERROR: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_OP-ERR\n"); + } +#endif /* SCTP_DEBUG */ + if (sctp_handle_error(ch, stcb, *netp) < 0) { + *offset = length; + return (NULL); + } + break; + case SCTP_COOKIE_ECHO: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_COOKIE-ECHO stcb is %p\n", stcb); + } +#endif /* SCTP_DEBUG */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + /* We are not interested anymore */ + *offset = length; + if (stcb) { + sctp_free_assoc(inp, stcb); + } else { + if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) { + /* finish the job now */ + sctp_inpcb_free(inp, 1); + } + } + return (NULL); + } + /* + * First are we accepting? + * We do this again here since it is possible + * that a previous endpoint WAS listening responded to + * a INIT-ACK and then closed. We opened and bound.. + * and are now no longer listening. + */ + if (((inp->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING) == 0) || + (inp->sctp_socket->so_qlimit == 0)) { + sctp_abort_association(inp, stcb, m, iphlen, sh, + NULL); + *offset = length; + return (NULL); + } else if (inp->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING) { + /* we are accepting so check limits like TCP */ + if (inp->sctp_socket->so_qlen > + inp->sctp_socket->so_qlimit) { + /* no space */ + struct mbuf *oper; + struct sctp_paramhdr *phdr; + oper = NULL; + MGETHDR(oper, M_DONTWAIT, MT_HEADER); + if (oper) { + oper->m_len = + oper->m_pkthdr.len = + sizeof(struct sctp_paramhdr); + phdr = mtod(oper, + struct sctp_paramhdr *); + phdr->param_type = + htons(SCTP_CAUSE_OUT_OF_RESC); + phdr->param_length = + htons(sizeof(struct sctp_paramhdr)); + } + sctp_abort_association(inp, stcb, m, + iphlen, sh, oper); + *offset = length; + return (NULL); + } + } + { + struct mbuf *ret_buf; + ret_buf = sctp_handle_cookie_echo(m, iphlen, + *offset, sh, + (struct sctp_cookie_echo_chunk *)ch, &inp, + &stcb, netp); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("ret_buf:%p length:%d off:%d\n", + ret_buf, length, *offset); + } +#endif /* SCTP_DEBUG */ + + if (ret_buf == NULL) { + if (locked_tcb) { + SCTP_TCB_UNLOCK(locked_tcb); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("GAK, null buffer\n"); + } +#endif /* SCTP_DEBUG */ + *offset = length; + return (NULL); + } + if (!TAILQ_EMPTY(&stcb->asoc.sent_queue)) { + /* + * Restart the timer if we have pending + * data + */ + struct sctp_tmit_chunk *chk; + chk = TAILQ_FIRST(&stcb->asoc.sent_queue); + if (chk) { + sctp_timer_start(SCTP_TIMER_TYPE_SEND, + stcb->sctp_ep, stcb, + chk->whoTo); + } + } + } + break; + case SCTP_COOKIE_ACK: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_COOKIE-ACK\n"); + } +#endif /* SCTP_DEBUG */ + + if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + /* We are not interested anymore */ + sctp_free_assoc(inp, stcb); + *offset = length; + return (NULL); + } + /* He's alive so give him credit */ + stcb->asoc.overall_error_count = 0; + sctp_handle_cookie_ack((struct sctp_cookie_ack_chunk *)ch, + stcb, *netp); + break; + case SCTP_ECN_ECHO: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_ECN-ECHO\n"); + } +#endif /* SCTP_DEBUG */ + /* He's alive so give him credit */ + stcb->asoc.overall_error_count = 0; + sctp_handle_ecn_echo((struct sctp_ecne_chunk *)ch, + stcb); + break; + case SCTP_ECN_CWR: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_ECN-CWR\n"); + } +#endif /* SCTP_DEBUG */ + /* He's alive so give him credit */ + stcb->asoc.overall_error_count = 0; + + sctp_handle_ecn_cwr((struct sctp_cwr_chunk *)ch, stcb); + break; + case SCTP_SHUTDOWN_COMPLETE: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_SHUTDOWN-COMPLETE\n"); + } +#endif /* SCTP_DEBUG */ + /* must be first and only chunk */ + if ((num_chunks > 1) || + (length - *offset > SCTP_SIZE32(chk_length))) { + *offset = length; + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + + return (NULL); + } + sctp_handle_shutdown_complete((struct sctp_shutdown_complete_chunk *)ch, + stcb, *netp); + *offset = length; + return (NULL); + break; + case SCTP_ASCONF: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_ASCONF\n"); + } +#endif /* SCTP_DEBUG */ + /* He's alive so give him credit */ + stcb->asoc.overall_error_count = 0; + + sctp_handle_asconf(m, *offset, + (struct sctp_asconf_chunk *)ch, stcb, *netp); + break; + case SCTP_ASCONF_ACK: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_ASCONF-ACK\n"); + } +#endif /* SCTP_DEBUG */ + /* He's alive so give him credit */ + stcb->asoc.overall_error_count = 0; + + sctp_handle_asconf_ack(m, *offset, + (struct sctp_asconf_ack_chunk *)ch, stcb, *netp); + break; + case SCTP_FORWARD_CUM_TSN: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_FWD-TSN\n"); + } +#endif /* SCTP_DEBUG */ + /* He's alive so give him credit */ + { + int abort_flag = 0; + stcb->asoc.overall_error_count = 0; + *fwd_tsn_seen = 1; + sctp_handle_forward_tsn(stcb, + (struct sctp_forward_tsn_chunk *)ch, &abort_flag); + if (abort_flag) { + *offset = length; + return (NULL); + } else { + stcb->asoc.overall_error_count = 0; + } + + } + break; + case SCTP_STREAM_RESET: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_STREAM_RESET\n"); + } +#endif /* SCTP_DEBUG */ + ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, + chk_length, chunk_buf); + if (stcb->asoc.peer_supports_strreset == 0) { + /* hmm, peer should have annonced this, but + * we will turn it on since he is sending us + * a stream reset. + */ + stcb->asoc.peer_supports_strreset = 1; + } + sctp_handle_stream_reset(stcb, (struct sctp_stream_reset_req *)ch); + break; + case SCTP_PACKET_DROPPED: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("SCTP_PACKET_DROPPED\n"); + } +#endif /* SCTP_DEBUG */ + /* re-get it all please */ + ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, + chk_length, chunk_buf); + + sctp_handle_packet_dropped((struct sctp_pktdrop_chunk *)ch, + stcb, *netp); + + + break; + default: + /* it's an unknown chunk! */ + if ((ch->chunk_type & 0x40) && (stcb != NULL)) { + struct mbuf *mm; + struct sctp_paramhdr *phd; + MGETHDR(mm, M_DONTWAIT, MT_HEADER); + if (mm) { + phd = mtod(mm, struct sctp_paramhdr *); + /* We cheat and use param type since we + * did not bother to define a error + * cause struct. + * They are the same basic format with + * different names. + */ + phd->param_type = + htons(SCTP_CAUSE_UNRECOG_CHUNK); + phd->param_length = + htons(chk_length + sizeof(*phd)); + mm->m_len = sizeof(*phd); + mm->m_next = sctp_m_copym(m, *offset, + SCTP_SIZE32(chk_length), + M_DONTWAIT); + if (mm->m_next) { + mm->m_pkthdr.len = + SCTP_SIZE32(chk_length) + + sizeof(*phd); + sctp_queue_op_err(stcb, mm); + } else { + sctp_m_freem(mm); +#ifdef SCTP_DEBUG + if (sctp_debug_on & + SCTP_DEBUG_INPUT1) { + printf("Gak can't copy the chunk into operr %d bytes\n", + chk_length); + } +#endif + } + } +#ifdef SCTP_DEBUG + else { + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("Gak can't mgethdr for op-err of unrec chunk\n"); + } + } +#endif + } + if ((ch->chunk_type & 0x80) == 0) { + /* discard this packet */ + *offset = length; + return (stcb); + } /* else skip this bad chunk and continue... */ + break; + } /* switch (ch->chunk_type) */ + /* get the next chunk */ + *offset += SCTP_SIZE32(chk_length); + if (*offset >= length) { + /* no more data left in the mbuf chain */ + break; + } + ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, *offset, + sizeof(struct sctp_chunkhdr), chunk_buf); + if (ch == NULL) { + if (locked_tcb) + SCTP_TCB_UNLOCK(locked_tcb); + *offset = length; + return (NULL); + } + } /* while */ + return (stcb); +} + + +/* + * Process the ECN bits we have something set so + * we must look to see if it is ECN(0) or ECN(1) or CE + */ +static void +sctp_process_ecn_marked_a(struct sctp_tcb *stcb, struct sctp_nets *net, + u_int8_t ecn_bits) +{ + if ((ecn_bits & SCTP_CE_BITS) == SCTP_CE_BITS) { + ; + } else if ((ecn_bits & SCTP_ECT1_BIT) == SCTP_ECT1_BIT) { + /* + * we only add to the nonce sum for ECT1, ECT0 + * does not change the NS bit (that we have + * yet to find a way to send it yet). + */ + + /* ECN Nonce stuff */ + stcb->asoc.receiver_nonce_sum++; + stcb->asoc.receiver_nonce_sum &= SCTP_SACK_NONCE_SUM; + + /* + * Drag up the last_echo point if cumack is larger since we + * don't want the point falling way behind by more than 2^^31 + * and then having it be incorrect. + */ + if (compare_with_wrap(stcb->asoc.cumulative_tsn, + stcb->asoc.last_echo_tsn, MAX_TSN)) { + stcb->asoc.last_echo_tsn = stcb->asoc.cumulative_tsn; + } + } else if ((ecn_bits & SCTP_ECT0_BIT) == SCTP_ECT0_BIT) { + /* + * Drag up the last_echo point if cumack is larger since we + * don't want the point falling way behind by more than 2^^31 + * and then having it be incorrect. + */ + if (compare_with_wrap(stcb->asoc.cumulative_tsn, + stcb->asoc.last_echo_tsn, MAX_TSN)) { + stcb->asoc.last_echo_tsn = stcb->asoc.cumulative_tsn; + } + } +} + +static void +sctp_process_ecn_marked_b(struct sctp_tcb *stcb, struct sctp_nets *net, + u_int32_t high_tsn, u_int8_t ecn_bits) +{ + if ((ecn_bits & SCTP_CE_BITS) == SCTP_CE_BITS) { + /* + * we possibly must notify the sender that a congestion + * window reduction is in order. We do this + * by adding a ECNE chunk to the output chunk + * queue. The incoming CWR will remove this chunk. + */ + if (compare_with_wrap(high_tsn, stcb->asoc.last_echo_tsn, + MAX_TSN)) { + /* Yep, we need to add a ECNE */ + sctp_send_ecn_echo(stcb, net, high_tsn); + stcb->asoc.last_echo_tsn = high_tsn; + } + } +} + +/* + * common input chunk processing (v4 and v6) + */ +int +sctp_common_input_processing(struct mbuf **mm, int iphlen, int offset, + int length, struct sctphdr *sh, struct sctp_chunkhdr *ch, + struct sctp_inpcb *inp, struct sctp_tcb *stcb, struct sctp_nets *net, + u_int8_t ecn_bits) +{ + /* + * Control chunk processing + */ + u_int32_t high_tsn; + int fwd_tsn_seen = 0, data_processed = 0; + struct mbuf *m = *mm; + int abort_flag = 0; + + sctp_pegs[SCTP_DATAGRAMS_RCVD]++; +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xE0, 1); + sctp_auditing(0, inp, stcb, net); +#endif + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Ok, Common input processing called, m:%x iphlen:%d offset:%d\n", + (u_int)m, iphlen, offset); + } +#endif /* SCTP_DEBUG */ + if (IS_SCTP_CONTROL(ch)) { + /* process the control portion of the SCTP packet */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Processing control\n"); + } +#endif /* SCTP_DEBUG */ + + stcb = sctp_process_control(m, iphlen, &offset, length, sh, ch, + inp, stcb, &net, &fwd_tsn_seen); + } else { + /* + * no control chunks, so pre-process DATA chunks + * (these checks are taken care of by control processing) + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("No control present\n"); + } +#endif /* SCTP_DEBUG */ + + if (stcb == NULL) { + /* out of the blue DATA chunk */ + sctp_handle_ootb(m, iphlen, offset, sh, inp, NULL); + return (1); + } + if (stcb->asoc.my_vtag != ntohl(sh->v_tag)) { + /* v_tag mismatch! */ + sctp_pegs[SCTP_BAD_VTAGS]++; + SCTP_TCB_UNLOCK(stcb); + return (1); + } + } + if (stcb == NULL) { + /* + * no valid TCB for this packet, + * or we found it's a bad packet while processing control, + * or we're done with this packet (done or skip rest of data), + * so we drop it... + */ + return (1); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Ok, control finished time to look for data (%d) offset:%d\n", + length, offset); + } +#endif /* SCTP_DEBUG */ + /* + * DATA chunk processing + */ + /* plow through the data chunks while length > offset */ + stcb->asoc.seen_a_sack_this_pkt = 0; + + if (length > offset) { + int retval; + /* + * First check to make sure our state is correct. + * We would not get here unless we really did have a + * tag, so we don't abort if this happens, just + * dump the chunk silently. + */ + switch (SCTP_GET_STATE(&stcb->asoc)) { + case SCTP_STATE_COOKIE_ECHOED: + /* + * we consider data with valid tags in + * this state shows us the cookie-ack was lost. + * Imply it was there. + */ + stcb->asoc.overall_error_count = 0; + sctp_handle_cookie_ack( + (struct sctp_cookie_ack_chunk *)ch, stcb, net); + break; + case SCTP_STATE_COOKIE_WAIT: + /* + * We consider OOTB any data sent during asoc setup. + */ + sctp_handle_ootb(m, iphlen, offset, sh, inp, NULL); + SCTP_TCB_UNLOCK(stcb); + return (1); + break; + case SCTP_STATE_EMPTY: /* should not happen */ + case SCTP_STATE_INUSE: /* should not happen */ + case SCTP_STATE_SHUTDOWN_RECEIVED: /* This is a peer error */ + case SCTP_STATE_SHUTDOWN_ACK_SENT: + default: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Got data in invalid state %d.. dropping\n", stcb->asoc.state); + } +#endif + SCTP_TCB_UNLOCK(stcb); + return (1); + break; + case SCTP_STATE_OPEN: + case SCTP_STATE_SHUTDOWN_SENT: + break; + } + /* take care of ECN, part 1. */ + if (stcb->asoc.ecn_allowed && + (ecn_bits & (SCTP_ECT0_BIT|SCTP_ECT1_BIT)) ) { + sctp_process_ecn_marked_a(stcb, net, ecn_bits); + } + /* plow through the data chunks while length > offset */ + retval = sctp_process_data(mm, iphlen, &offset, length, sh, + inp, stcb, net, &high_tsn); + if (retval == 2) { + /* The association aborted, NO UNLOCK needed + * since the association is destroyed. + */ + return (0); + } + + data_processed = 1; + if (retval == 0) { + /* take care of ecn part 2. */ + if (stcb->asoc.ecn_allowed && (ecn_bits & (SCTP_ECT0_BIT|SCTP_ECT1_BIT)) ) { + sctp_process_ecn_marked_b(stcb, net, high_tsn, ecn_bits); + + } + } + + /* + * Anything important needs to have been m_copy'ed in + * process_data + */ + } + if ((data_processed == 0) && (fwd_tsn_seen)) { + int was_a_gap = 0; + if (compare_with_wrap(stcb->asoc.highest_tsn_inside_map, + stcb->asoc.cumulative_tsn, MAX_TSN)) { + /* there was a gap before this data was processed */ + was_a_gap = 1; + } + sctp_sack_check(stcb, 1, was_a_gap, &abort_flag); + if (abort_flag) { + /* Again, we aborted so NO UNLOCK needed */ + return (0); + } + } + /* trigger send of any chunks in queue... */ +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xE0, 2); + sctp_auditing(1, inp, stcb, net); +#endif +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Check for chunk output prw:%d tqe:%d tf=%d\n", + stcb->asoc.peers_rwnd, + TAILQ_EMPTY(&stcb->asoc.control_send_queue), + stcb->asoc.total_flight); + } +#endif + if (stcb->asoc.peers_rwnd > 0 || + !TAILQ_EMPTY(&stcb->asoc.control_send_queue) || + (stcb->asoc.peers_rwnd <= 0 && stcb->asoc.total_flight == 0)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("Calling chunk OUTPUT\n"); + } +#endif + sctp_chunk_output(inp, stcb, 3); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("chunk OUTPUT returns\n"); + } +#endif + } + +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xE0, 3); + sctp_auditing(2, inp, stcb, net); +#endif + SCTP_TCB_UNLOCK(stcb); + return (0); +} + +#if defined(__OpenBSD__) +static void +sctp_saveopt(struct sctp_inpcb *inp, struct mbuf **mp, struct ip *ip, + struct mbuf *m) +{ + if (inp->ip_inp.inp.inp_flags & INP_RECVDSTADDR) { + *mp = sbcreatecontrol((caddr_t) &ip->ip_dst, + sizeof(struct in_addr), IP_RECVDSTADDR, IPPROTO_IP); + if (*mp) + mp = &(*mp)->m_next; + } +} +#endif + +extern int sctp_no_csum_on_loopback; + +#if defined(__FreeBSD__) || defined(__APPLE__) +void +sctp_input(m, off) + struct mbuf *m; + int off; +#else +void +#if __STDC__ +sctp_input(struct mbuf *m, ...) +#else +sctp_input(m, va_alist) + struct mbuf *m; +#endif +#endif +{ + int iphlen; + int s; + u_int8_t ecn_bits; + struct ip *ip; + struct sctphdr *sh; + struct sctp_inpcb *inp = NULL; + struct mbuf *opts = 0; +/*#ifdef INET6*/ +/* Don't think this is needed */ +/* struct ip6_recvpktopts opts6;*/ +/*#endif INET6 */ + + u_int32_t check, calc_check; + struct sctp_nets *net; + struct sctp_tcb *stcb = NULL; + struct sctp_chunkhdr *ch; + int refcount_up = 0; + int length, mlen, offset; +#if defined(__OpenBSD__) && defined(IPSEC) + struct inpcb *i_inp; + struct m_tag *mtag; + struct tdb_ident *tdbi; + struct tdb *tdb; + int error; +#endif + +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + int off; + va_list ap; + + va_start(ap, m); + iphlen = off = va_arg(ap, int); + va_end(ap); +#else + iphlen = off; +#endif + net = NULL; + sctp_pegs[SCTP_INPKTS]++; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("V4 input gets a packet iphlen:%d pktlen:%d\n", iphlen, m->m_pkthdr.len); + } +#endif +/*#ifdef INET6*/ +/* Don't think this is needed */ +/* bzero(&opts6, sizeof(opts6));*/ +/*#endif INET6 */ + + /* + * Strip IP options, we don't allow any in or out. + */ + if ((size_t)iphlen > sizeof(struct ip)) { + ip_stripoptions(m, (struct mbuf *)0); + iphlen = sizeof(struct ip); + } + + /* + * Get IP, SCTP, and first chunk header together in first mbuf. + */ + ip = mtod(m, struct ip *); + offset = iphlen + sizeof(*sh) + sizeof(*ch); + if (m->m_len < offset) { + if ((m = m_pullup(m, offset)) == 0) { + sctp_pegs[SCTP_HDR_DROPS]++; + return; + } + ip = mtod(m, struct ip *); + } + sh = (struct sctphdr *)((caddr_t)ip + iphlen); + ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(*sh)); + + /* SCTP does not allow broadcasts or multicasts */ +#if defined(__NetBSD__) || defined(__OpenBSD__) + if (IN_MULTICAST(ip->ip_dst.s_addr)) +#else + if (IN_MULTICAST(ntohl(ip->ip_dst.s_addr))) +#endif + { + sctp_pegs[SCTP_IN_MCAST]++; + goto bad; + } + if (in_broadcast(ip->ip_dst, m->m_pkthdr.rcvif)) { + sctp_pegs[SCTP_IN_MCAST]++; + goto bad; + } + + /* destination port of 0 is illegal, based on RFC2960. */ + if (sh->dest_port == 0) { + sctp_pegs[SCTP_HDR_DROPS]++; + goto bad; + } + + /* validate SCTP checksum */ + if ((sctp_no_csum_on_loopback == 0) || + (m->m_pkthdr.rcvif == NULL) || + (m->m_pkthdr.rcvif->if_type != IFT_LOOP)) { + /* we do NOT validate things from the loopback if the + * sysctl is set to 1. + */ + check = sh->checksum; /* save incoming checksum */ + if ((check == 0) && (sctp_no_csum_on_loopback)) { + /* special hook for where we got a local address + * somehow routed across a non IFT_LOOP type interface + */ + if (ip->ip_src.s_addr == ip->ip_dst.s_addr) + goto sctp_skip_csum_4; + } + sh->checksum = 0; /* prepare for calc */ + calc_check = sctp_calculate_sum(m, &mlen, iphlen); + if (calc_check != check) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Bad CSUM on SCTP packet calc_check:%x check:%x m:%x mlen:%d iphlen:%d\n", + calc_check, check, (u_int)m, mlen, iphlen); + } +#endif + + stcb = sctp_findassociation_addr(m, iphlen, + offset - sizeof(*ch), + sh, ch, &inp, &net); + if ((inp) && (stcb)) { + sctp_send_packet_dropped(stcb, net, m, iphlen, + 1); + sctp_chunk_output(inp, stcb, 2); + } else if ((inp != NULL) && (stcb == NULL)) { + refcount_up = 1; + } + sctp_pegs[SCTP_BAD_CSUM]++; + goto bad; + } + sh->checksum = calc_check; + } else { + sctp_skip_csum_4: + mlen = m->m_pkthdr.len; + } + /* validate mbuf chain length with IP payload length */ +#if defined(__NetBSD__) || defined(__OpenBSD__) + /* Open BSD gives us the len in network order, fix it */ + NTOHS(ip->ip_len); +#endif + if (mlen < (ip->ip_len - iphlen)) { + sctp_pegs[SCTP_HDR_DROPS]++; + goto bad; + } + + /* + * Locate pcb and tcb for datagram + * sctp_findassociation_addr() wants IP/SCTP/first chunk header... + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("V4 find association\n"); + } +#endif + + stcb = sctp_findassociation_addr(m, iphlen, offset - sizeof(*ch), + sh, ch, &inp, &net); + /* inp's ref-count increased && stcb locked */ + if (inp == NULL) { + struct sctp_init_chunk *init_chk, chunk_buf; + + sctp_pegs[SCTP_NOPORTS]++; +#ifdef ICMP_BANDLIM + /* + * we use the bandwidth limiting to protect against + * sending too many ABORTS all at once. In this case + * these count the same as an ICMP message. + */ + if (badport_bandlim(0) < 0) + goto bad; +#endif /* ICMP_BANDLIM */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Sending a ABORT from packet entry!\n"); + } +#endif + if (ch->chunk_type == SCTP_INITIATION) { + /* we do a trick here to get the INIT tag, + * dig in and get the tag from the INIT and + * put it in the common header. + */ + init_chk = (struct sctp_init_chunk *)sctp_m_getptr(m, + iphlen + sizeof(*sh), sizeof(*init_chk), + (u_int8_t *)&chunk_buf); + if (init_chk != NULL) + sh->v_tag = init_chk->init.initiate_tag; + } + sctp_send_abort(m, iphlen, sh, 0, NULL); + goto bad; + } else if (stcb == NULL) { + refcount_up = 1; + } +#ifdef IPSEC + /* + * I very much doubt any of the IPSEC stuff will work but I have + * no idea, so I will leave it in place. + */ + +#ifdef __OpenBSD__ + /* FIX ME: this don't work... :) */ + { + /* Find most recent IPsec tag */ + i_inp = &inp->ip_inp.inp; + mtag = m_tag_find(m, PACKET_TAG_IPSEC_IN_DONE, NULL); + s = splnet(); + if (mtag != NULL) { + tdbi = (struct tdb_ident *)(mtag + 1); + tdb = gettdb(tdbi->spi, &tdbi->dst, tdbi->proto); + } else + tdb = NULL; + ipsp_spd_lookup(m, af, iphlen, &error, IPSP_DIRECTION_IN, + tdb, i_inp); + if (error) { + splx(s); + sctp_pegs[SCTP_HDR_DROPS]++; + goto bad; + } + + /* Latch SA */ + if (i_inp->inp_tdb_in != tdb) { + if (tdb) { + tdb_add_inp(tdb, i_inp, 1); + if (i_inp->inp_ipo == NULL) { + i_inp->inp_ipo = ipsec_add_policy(i_inp, af, + IPSP_DIRECTION_OUT); + if (i_inp->inp_ipo == NULL) { + splx(s); + sctp_pegs[SCTP_HDR_DROPS]++; + goto bad; + } + } + if (i_inp->inp_ipo->ipo_dstid == NULL && + tdb->tdb_srcid != NULL) { + i_inp->inp_ipo->ipo_dstid = tdb->tdb_srcid; + tdb->tdb_srcid->ref_count++; + } + if (i_inp->inp_ipsec_remotecred == NULL && + tdb->tdb_remote_cred != NULL) { + i_inp->inp_ipsec_remotecred = + tdb->tdb_remote_cred; + tdb->tdb_remote_cred->ref_count++; + } + if (i_inp->inp_ipsec_remoteauth == NULL && + tdb->tdb_remote_auth != NULL) { + i_inp->inp_ipsec_remoteauth = + tdb->tdb_remote_auth; + tdb->tdb_remote_auth->ref_count++; + } + } else { /* Just reset */ + TAILQ_REMOVE(&i_inp->inp_tdb_in->tdb_inp_in, i_inp, + inp_tdb_in_next); + i_inp->inp_tdb_in = NULL; + } + } + splx(s); + } +#else + if (ipsec4_in_reject_so(m, inp->ip_inp.inp.inp_socket)) { + ipsecstat.in_polvio++; + sctp_pegs[SCTP_HDR_DROPS]++; + goto bad; + } +#endif +#endif /* IPSEC */ + + /* + * Construct sockaddr format source address. + * Stuff source address and datagram in user buffer. + */ + if ((inp->ip_inp.inp.inp_flags & INP_CONTROLOPTS) +#ifndef __OpenBSD__ + || (inp->sctp_socket->so_options & SO_TIMESTAMP) +#endif + ) { +#ifdef __OpenBSD__ + sctp_saveopt(inp, &opts, ip, m); +#else + ip_savecontrol((struct inpcb *)inp, &opts, ip, m); +#endif + } + + /* + * common chunk processing + */ +#if defined(__FreeBSD__) || defined(__APPLE__) + length = ip->ip_len + iphlen; +#else + length = ip->ip_len - (ip->ip_hl << 2) + iphlen; +#endif + offset -= sizeof(struct sctp_chunkhdr); + + ecn_bits = ip->ip_tos; +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + sctp_common_input_processing(&m, iphlen, offset, length, sh, ch, + inp, stcb, net, ecn_bits); + /* inp's ref-count reduced && stcb unlocked */ + splx(s); + if (m) { + sctp_m_freem(m); + } + if (opts) + sctp_m_freem(opts); + + if ((inp) && (refcount_up)) { + /* reduce ref-count */ + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + + return; +bad: + if (stcb) + SCTP_TCB_UNLOCK(stcb); + + if ((inp) && (refcount_up)) { + /* reduce ref-count */ + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + + if (m) { + sctp_m_freem(m); + } + if (opts) + sctp_m_freem(opts); + return; +} diff --git a/sys/netinet/sctp_input.h b/sys/netinet/sctp_input.h new file mode 100644 index 0000000000..6d2216fa77 --- /dev/null +++ b/sys/netinet/sctp_input.h @@ -0,0 +1,45 @@ +/* $KAME: sctp_input.h,v 1.5 2004/08/17 04:06:17 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_input.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +#ifndef __sctp_input_h__ +#define __sctp_input_h__ + +/* + * Copyright (C) 2002 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(_KERNEL) || defined(KERNEL) +int sctp_common_input_processing(struct mbuf **, int, int, int, + struct sctphdr *, struct sctp_chunkhdr *, struct sctp_inpcb *, + struct sctp_tcb *, struct sctp_nets *, u_int8_t); + +void +sctp_handle_stream_reset_response(struct sctp_tcb *, struct sctp_stream_reset_response *resp); + +#endif +#endif diff --git a/sys/netinet/sctp_output.c b/sys/netinet/sctp_output.c new file mode 100644 index 0000000000..43f9c9785e --- /dev/null +++ b/sys/netinet/sctp_output.c @@ -0,0 +1,10493 @@ +/* $KAME: sctp_output.c,v 1.46 2005/03/06 16:04:17 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_output.c,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +/* + * Copyright (C) 2002, 2003, 2004 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if !(defined(__OpenBSD__) || defined (__APPLE__)) +#include "opt_ipsec.h" +#endif +#if defined(__FreeBSD__) +#include "opt_compat.h" +#include "opt_inet6.h" +#include "opt_inet.h" +#endif +#if defined(__NetBSD__) +#include "opt_inet.h" +#endif +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif +#include +#include +#include +#include +#ifndef __OpenBSD__ +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef INET6 +#include +#endif + +#if (defined(__FreeBSD__) && __FreeBSD_version >= 500000) +#include +#else +#include +#endif +#include + +#include +#include + +#if defined(__FreeBSD__) +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include + +#ifdef INET6 +#include +#include +#include +#include + +#if defined(__FreeBSD__) || defined(__NetBSD__) +#include +#elif defined(__OpenBSD__) +#include +#endif + +#include + +#endif /* INET6 */ + +#include + +#if defined(HAVE_NRL_INPCB) || defined(__FreeBSD__) +#ifndef in6pcb +#define in6pcb inpcb +#endif +#endif + +#include + +#ifdef IPSEC +#ifndef __OpenBSD__ +#include +#include +#else +#undef IPSEC +#endif +#endif /* IPSEC */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef SCTP_DEBUG +extern uint32_t sctp_debug_on; +#endif + +extern int sctp_peer_chunk_oh; + +static int +sctp_find_cmsg(int c_type, void *data, struct mbuf *control, int cpsize) +{ + struct cmsghdr cmh; + int tlen, at; + + tlen = control->m_len; + at = 0; + /* + * Independent of how many mbufs, find the c_type inside the control + * structure and copy out the data. + */ + while (at < tlen) { + if ((tlen-at) < (int)CMSG_ALIGN(sizeof(cmh))) { + /* not enough room for one more we are done. */ + return (0); + } + m_copydata(control, at, sizeof(cmh), (caddr_t)&cmh); + if ((cmh.cmsg_len + at) > tlen) { + /* + * this is real messed up since there is not enough + * data here to cover the cmsg header. We are done. + */ + return (0); + } + if ((cmh.cmsg_level == IPPROTO_SCTP) && + (c_type == cmh.cmsg_type)) { + /* found the one we want, copy it out */ + at += CMSG_ALIGN(sizeof(struct cmsghdr)); + if ((int)(cmh.cmsg_len - CMSG_ALIGN(sizeof(struct cmsghdr))) < cpsize) { + /* + * space of cmsg_len after header not + * big enough + */ + return (0); + } + m_copydata(control, at, cpsize, data); + return (1); + } else { + at += CMSG_ALIGN(cmh.cmsg_len); + if (cmh.cmsg_len == 0) { + break; + } + } + } + /* not found */ + return (0); +} + +static struct mbuf * +sctp_add_addr_to_mbuf(struct mbuf *m, struct ifaddr *ifa) +{ + struct sctp_paramhdr *parmh; + struct mbuf *mret; + int len; + if (ifa->ifa_addr->sa_family == AF_INET) { + len = sizeof(struct sctp_ipv4addr_param); + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + len = sizeof(struct sctp_ipv6addr_param); + } else { + /* unknown type */ + return (m); + } + + if (M_TRAILINGSPACE(m) >= len) { + /* easy side we just drop it on the end */ + parmh = (struct sctp_paramhdr *)(m->m_data + m->m_len); + mret = m; + } else { + /* Need more space */ + mret = m; + while (mret->m_next != NULL) { + mret = mret->m_next; + } + MGET(mret->m_next, M_DONTWAIT, MT_DATA); + if (mret->m_next == NULL) { + /* We are hosed, can't add more addresses */ + return (m); + } + mret = mret->m_next; + parmh = mtod(mret, struct sctp_paramhdr *); + } + /* now add the parameter */ + if (ifa->ifa_addr->sa_family == AF_INET) { + struct sctp_ipv4addr_param *ipv4p; + struct sockaddr_in *sin; + sin = (struct sockaddr_in *)ifa->ifa_addr; + ipv4p = (struct sctp_ipv4addr_param *)parmh; + parmh->param_type = htons(SCTP_IPV4_ADDRESS); + parmh->param_length = htons(len); + ipv4p->addr = sin->sin_addr.s_addr; + mret->m_len += len; + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + struct sctp_ipv6addr_param *ipv6p; + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + ipv6p = (struct sctp_ipv6addr_param *)parmh; + parmh->param_type = htons(SCTP_IPV6_ADDRESS); + parmh->param_length = htons(len); + memcpy(ipv6p->addr, &sin6->sin6_addr, + sizeof(ipv6p->addr)); + /* clear embedded scope in the address */ + in6_clearscope((struct in6_addr *)ipv6p->addr); + mret->m_len += len; + } else { + return (m); + } + return (mret); +} + + + +static struct mbuf * +sctp_add_cookie(struct sctp_inpcb *inp, struct mbuf *init, int init_offset, + struct mbuf *initack, int initack_offset, struct sctp_state_cookie *stc_in) +{ + struct mbuf *copy_init, *copy_initack, *m_at, *sig, *mret; + struct sctp_state_cookie *stc; + struct sctp_paramhdr *ph; + uint8_t *signature; + int sig_offset; + uint16_t cookie_sz; + + mret = NULL; + + MGET(mret, M_DONTWAIT, MT_DATA); + if (mret == NULL) { + return (NULL); + } + copy_init = sctp_m_copym(init, init_offset, M_COPYALL, M_DONTWAIT); + if (copy_init == NULL) { + sctp_m_freem(mret); + return (NULL); + } + copy_initack = sctp_m_copym(initack, initack_offset, M_COPYALL, + M_DONTWAIT); + if (copy_initack == NULL) { + sctp_m_freem(mret); + sctp_m_freem(copy_init); + return (NULL); + } + /* easy side we just drop it on the end */ + ph = mtod(mret, struct sctp_paramhdr *); + mret->m_len = sizeof(struct sctp_state_cookie) + + sizeof(struct sctp_paramhdr); + stc = (struct sctp_state_cookie *)((caddr_t)ph + + sizeof(struct sctp_paramhdr)); + ph->param_type = htons(SCTP_STATE_COOKIE); + ph->param_length = 0; /* fill in at the end */ + /* Fill in the stc cookie data */ + *stc = *stc_in; + + /* tack the INIT and then the INIT-ACK onto the chain */ + cookie_sz = 0; + m_at = mret; + for (m_at = mret; m_at; m_at = m_at->m_next) { + cookie_sz += m_at->m_len; + if (m_at->m_next == NULL) { + m_at->m_next = copy_init; + break; + } + } + + for (m_at = copy_init; m_at; m_at = m_at->m_next) { + cookie_sz += m_at->m_len; + if (m_at->m_next == NULL) { + m_at->m_next = copy_initack; + break; + } + } + + for (m_at = copy_initack; m_at; m_at = m_at->m_next) { + cookie_sz += m_at->m_len; + if (m_at->m_next == NULL) { + break; + } + } + MGET(sig, M_DONTWAIT, MT_DATA); + if (sig == NULL) { + /* no space */ + sctp_m_freem(mret); + sctp_m_freem(copy_init); + sctp_m_freem(copy_initack); + return (NULL); + } + sig->m_len = 0; + m_at->m_next = sig; + sig_offset = 0; + signature = (uint8_t *)(mtod(sig, caddr_t) + sig_offset); + /* Time to sign the cookie */ + sctp_hash_digest_m((char *)inp->sctp_ep.secret_key[ + (int)(inp->sctp_ep.current_secret_number)], + SCTP_SECRET_SIZE, mret, sizeof(struct sctp_paramhdr), + (uint8_t *)signature); + sig->m_len += SCTP_SIGNATURE_SIZE; + cookie_sz += SCTP_SIGNATURE_SIZE; + + ph->param_length = htons(cookie_sz); + return (mret); +} + + +static struct sockaddr_in * +sctp_is_v4_ifa_addr_prefered (struct ifaddr *ifa, uint8_t loopscope, uint8_t ipv4_scope, uint8_t *sin_loop, uint8_t *sin_local) +{ + struct sockaddr_in *sin; + /* + * Here we determine if its a prefered address. A + * prefered address means it is the same scope or + * higher scope then the destination. + * L = loopback, P = private, G = global + * ----------------------------------------- + * src | dest | result + *----------------------------------------- + * L | L | yes + *----------------------------------------- + * P | L | yes + *----------------------------------------- + * G | L | yes + *----------------------------------------- + * L | P | no + *----------------------------------------- + * P | P | yes + *----------------------------------------- + * G | P | no + *----------------------------------------- + * L | G | no + *----------------------------------------- + * P | G | no + *----------------------------------------- + * G | G | yes + *----------------------------------------- + */ + + if (ifa->ifa_addr->sa_family != AF_INET) { + /* forget non-v4 */ + return (NULL); + } + /* Ok the address may be ok */ + sin = (struct sockaddr_in *)ifa->ifa_addr; + if (sin->sin_addr.s_addr == 0) { + return (NULL); + } + *sin_local = *sin_loop = 0; + if ((ifa->ifa_ifp->if_type == IFT_LOOP) || + (IN4_ISLOOPBACK_ADDRESS(&sin->sin_addr))) { + *sin_loop = 1; + *sin_local = 1; + } + if ((IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) { + *sin_local = 1; + } + if (!loopscope && *sin_loop) { + /* Its a loopback address and we don't have loop scope */ + return (NULL); + } + if (!ipv4_scope && *sin_local) { + /* Its a private address, and we don't have private address scope */ + return (NULL); + } + if (((ipv4_scope == 0) && (loopscope == 0)) && (*sin_local)) { + /* its a global src and a private dest */ + return (NULL); + } + /* its a prefered address */ + return (sin); +} + +static struct sockaddr_in * +sctp_is_v4_ifa_addr_acceptable (struct ifaddr *ifa, uint8_t loopscope, uint8_t ipv4_scope, uint8_t *sin_loop, uint8_t *sin_local) +{ + struct sockaddr_in *sin; + /* + * Here we determine if its a acceptable address. A + * acceptable address means it is the same scope or + * higher scope but we can allow for NAT which means + * its ok to have a global dest and a private src. + * + * L = loopback, P = private, G = global + * ----------------------------------------- + * src | dest | result + *----------------------------------------- + * L | L | yes + *----------------------------------------- + * P | L | yes + *----------------------------------------- + * G | L | yes + *----------------------------------------- + * L | P | no + *----------------------------------------- + * P | P | yes + *----------------------------------------- + * G | P | yes - probably this won't work. + *----------------------------------------- + * L | G | no + *----------------------------------------- + * P | G | yes + *----------------------------------------- + * G | G | yes + *----------------------------------------- + */ + + if (ifa->ifa_addr->sa_family != AF_INET) { + /* forget non-v4 */ + return (NULL); + } + /* Ok the address may be ok */ + sin = (struct sockaddr_in *)ifa->ifa_addr; + if (sin->sin_addr.s_addr == 0) { + return (NULL); + } + *sin_local = *sin_loop = 0; + if ((ifa->ifa_ifp->if_type == IFT_LOOP) || + (IN4_ISLOOPBACK_ADDRESS(&sin->sin_addr))) { + *sin_loop = 1; + *sin_local = 1; + } + if ((IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) { + *sin_local = 1; + } + if (!loopscope && *sin_loop) { + /* Its a loopback address and we don't have loop scope */ + return (NULL); + } + /* its an acceptable address */ + return (sin); +} + +/* + * This treats the address list on the ep as a restricted list + * (negative list). If a the passed address is listed, then + * the address is NOT allowed on the association. + */ +int +sctp_is_addr_restricted(struct sctp_tcb *stcb, struct sockaddr *addr) +{ + struct sctp_laddr *laddr; +#ifdef SCTP_DEBUG + int cnt=0; +#endif + if (stcb == NULL) { + /* There are no restrictions, no TCB :-) */ + return (0); + } +#ifdef SCTP_DEBUG + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, sctp_nxt_addr) { + cnt++; + } + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("There are %d addresses on the restricted list\n", cnt); + } + cnt = 0; +#endif + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, sctp_nxt_addr) { + if (laddr->ifa == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Help I have fallen and I can't get up!\n"); + } +#endif + continue; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + cnt++; + printf("Restricted address[%d]:", cnt); + sctp_print_address(laddr->ifa->ifa_addr); + } +#endif + if (sctp_cmpaddr(addr, laddr->ifa->ifa_addr) == 1) { + /* Yes it is on the list */ + return (1); + } + } + return (0); +} + +static int +sctp_is_addr_in_ep(struct sctp_inpcb *inp, struct ifaddr *ifa) +{ + struct sctp_laddr *laddr; + + if (ifa == NULL) + return (0); + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Help I have fallen and I can't get up!\n"); + } +#endif + continue; + } + if (laddr->ifa->ifa_addr == NULL) + continue; + if (laddr->ifa == ifa) + /* same pointer */ + return (1); + if (laddr->ifa->ifa_addr->sa_family != ifa->ifa_addr->sa_family) { + /* skip non compatible address comparison */ + continue; + } + if (sctp_cmpaddr(ifa->ifa_addr, laddr->ifa->ifa_addr) == 1) { + /* Yes it is restricted */ + return (1); + } + } + return (0); +} + + + +static struct in_addr +sctp_choose_v4_boundspecific_inp(struct sctp_inpcb *inp, + struct rtentry *rt, + uint8_t ipv4_scope, + uint8_t loopscope) +{ + struct in_addr ans; + struct sctp_laddr *laddr; + struct sockaddr_in *sin; + struct ifnet *ifn; + struct ifaddr *ifa; + uint8_t sin_loop, sin_local; + + /* first question, is the ifn we will emit on + * in our list, if so, we want that one. + */ + ifn = rt->rt_ifp; + if (ifn) { + /* is a prefered one on the interface we route out? */ + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + sin = sctp_is_v4_ifa_addr_prefered (ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + if (sctp_is_addr_in_ep(inp, ifa)) { + return (sin->sin_addr); + } + } + /* is an acceptable one on the interface we route out? */ + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + sin = sctp_is_v4_ifa_addr_acceptable (ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + if (sctp_is_addr_in_ep(inp, ifa)) { + return (sin->sin_addr); + } + } + } + /* ok, what about a prefered address in the inp */ + for (laddr = LIST_FIRST(&inp->sctp_addr_list); + laddr && (laddr != inp->next_addr_touse); + laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin = sctp_is_v4_ifa_addr_prefered (laddr->ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + return (sin->sin_addr); + + } + /* ok, what about an acceptable address in the inp */ + for (laddr = LIST_FIRST(&inp->sctp_addr_list); + laddr && (laddr != inp->next_addr_touse); + laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin = sctp_is_v4_ifa_addr_acceptable (laddr->ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + return (sin->sin_addr); + + } + + /* no address bound can be a source for the destination we are in trouble */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Src address selection for EP, no acceptable src address found for address\n"); + } +#endif + memset(&ans, 0, sizeof(ans)); + return (ans); +} + + + +static struct in_addr +sctp_choose_v4_boundspecific_stcb(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_nets *net, + struct rtentry *rt, + uint8_t ipv4_scope, + uint8_t loopscope, + int non_asoc_addr_ok) +{ + /* + * Here we have two cases, bound all asconf + * allowed. bound all asconf not allowed. + * + */ + struct sctp_laddr *laddr, *starting_point; + struct in_addr ans; + struct ifnet *ifn; + struct ifaddr *ifa; + uint8_t sin_loop, sin_local, start_at_beginning=0; + struct sockaddr_in *sin; + + /* first question, is the ifn we will emit on + * in our list, if so, we want that one. + */ + ifn = rt->rt_ifp; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) { + /* + * Here we use the list of addresses on the endpoint. Then + * the addresses listed on the "restricted" list is just that, + * address that have not been added and can't be used (unless + * the non_asoc_addr_ok is set). + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Have a STCB - asconf allowed, not bound all have a netgative list\n"); + } +#endif + /* first question, is the ifn we will emit on + * in our list, if so, we want that one. + */ + if (ifn) { + /* first try for an prefered address on the ep */ + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (sctp_is_addr_in_ep(inp, ifa)) { + sin = sctp_is_v4_ifa_addr_prefered (ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + if ((non_asoc_addr_ok == 0) && + (sctp_is_addr_restricted(stcb, (struct sockaddr *)sin))) { + /* on the no-no list */ + continue; + } + return (sin->sin_addr); + } + } + /* next try for an acceptable address on the ep */ + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (sctp_is_addr_in_ep(inp, ifa)) { + sin = sctp_is_v4_ifa_addr_acceptable (ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + if ((non_asoc_addr_ok == 0) && + (sctp_is_addr_restricted(stcb, (struct sockaddr *)sin))) { + /* on the no-no list */ + continue; + } + return (sin->sin_addr); + } + } + + } + /* if we can't find one like that then we must + * look at all addresses bound to pick one at + * first prefereable then secondly acceptable. + */ + starting_point = stcb->asoc.last_used_address; + sctpv4_from_the_top: + if (stcb->asoc.last_used_address == NULL) { + start_at_beginning=1; + stcb->asoc.last_used_address = LIST_FIRST(&inp->sctp_addr_list); + } + /* search beginning with the last used address */ + for (laddr = stcb->asoc.last_used_address; laddr; + laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin = sctp_is_v4_ifa_addr_prefered (laddr->ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + if ((non_asoc_addr_ok == 0) && + (sctp_is_addr_restricted(stcb, (struct sockaddr *)sin))) { + /* on the no-no list */ + continue; + } + return (sin->sin_addr); + + } + if (start_at_beginning == 0) { + stcb->asoc.last_used_address = NULL; + goto sctpv4_from_the_top; + } + /* now try for any higher scope than the destination */ + stcb->asoc.last_used_address = starting_point; + start_at_beginning = 0; + sctpv4_from_the_top2: + if (stcb->asoc.last_used_address == NULL) { + start_at_beginning=1; + stcb->asoc.last_used_address = LIST_FIRST(&inp->sctp_addr_list); + } + /* search beginning with the last used address */ + for (laddr = stcb->asoc.last_used_address; laddr; + laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin = sctp_is_v4_ifa_addr_acceptable (laddr->ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + if ((non_asoc_addr_ok == 0) && + (sctp_is_addr_restricted(stcb, (struct sockaddr *)sin))) { + /* on the no-no list */ + continue; + } + return (sin->sin_addr); + } + if (start_at_beginning == 0) { + stcb->asoc.last_used_address = NULL; + goto sctpv4_from_the_top2; + } + } else { + /* + * Here we have an address list on the association, thats the + * only valid source addresses that we can use. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Have a STCB - no asconf allowed, not bound all have a postive list\n"); + } +#endif + /* First look at all addresses for one that is on + * the interface we route out + */ + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, + sctp_nxt_addr) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin = sctp_is_v4_ifa_addr_prefered (laddr->ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + /* first question, is laddr->ifa an address associated with the emit interface */ + if (ifn) { + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (laddr->ifa == ifa) { + sin = (struct sockaddr_in *)laddr->ifa->ifa_addr; + return (sin->sin_addr); + } + if (sctp_cmpaddr(ifa->ifa_addr, laddr->ifa->ifa_addr) == 1) { + sin = (struct sockaddr_in *)laddr->ifa->ifa_addr; + return (sin->sin_addr); + } + } + } + } + /* what about an acceptable one on the interface? */ + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, + sctp_nxt_addr) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin = sctp_is_v4_ifa_addr_acceptable (laddr->ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + /* first question, is laddr->ifa an address associated with the emit interface */ + if (ifn) { + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (laddr->ifa == ifa) { + sin = (struct sockaddr_in *)laddr->ifa->ifa_addr; + return (sin->sin_addr); + } + if (sctp_cmpaddr(ifa->ifa_addr, laddr->ifa->ifa_addr) == 1) { + sin = (struct sockaddr_in *)laddr->ifa->ifa_addr; + return (sin->sin_addr); + } + } + } + } + /* ok, next one that is preferable in general */ + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, + sctp_nxt_addr) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin = sctp_is_v4_ifa_addr_prefered (laddr->ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + return (sin->sin_addr); + } + + /* last, what about one that is acceptable */ + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, + sctp_nxt_addr) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin = sctp_is_v4_ifa_addr_acceptable (laddr->ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + return (sin->sin_addr); + } + } + memset(&ans, 0, sizeof(ans)); + return (ans); +} + +static struct sockaddr_in * +sctp_select_v4_nth_prefered_addr_from_ifn_boundall (struct ifnet *ifn, struct sctp_tcb *stcb, int non_asoc_addr_ok, + uint8_t loopscope, uint8_t ipv4_scope, int cur_addr_num) +{ + struct ifaddr *ifa; + struct sockaddr_in *sin; + uint8_t sin_loop, sin_local; + int num_eligible_addr = 0; + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + sin = sctp_is_v4_ifa_addr_prefered (ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + if (stcb) { + if ((non_asoc_addr_ok == 0) && sctp_is_addr_restricted(stcb, (struct sockaddr *)sin)) { + /* It is restricted for some reason.. probably + * not yet added. + */ + continue; + } + } + if (cur_addr_num == num_eligible_addr) { + return (sin); + } + } + return (NULL); +} + + +static int +sctp_count_v4_num_prefered_boundall (struct ifnet *ifn, struct sctp_tcb *stcb, int non_asoc_addr_ok, + uint8_t loopscope, uint8_t ipv4_scope, uint8_t *sin_loop, uint8_t *sin_local) +{ + struct ifaddr *ifa; + struct sockaddr_in *sin; + int num_eligible_addr = 0; + + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + sin = sctp_is_v4_ifa_addr_prefered (ifa, loopscope, ipv4_scope, sin_loop, sin_local); + if (sin == NULL) + continue; + if (stcb) { + if ((non_asoc_addr_ok == 0) && sctp_is_addr_restricted(stcb, (struct sockaddr *)sin)) { + /* It is restricted for some reason.. probably + * not yet added. + */ + continue; + } + } + num_eligible_addr++; + } + return (num_eligible_addr); + +} + +static struct in_addr +sctp_choose_v4_boundall(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_nets *net, + struct rtentry *rt, + uint8_t ipv4_scope, + uint8_t loopscope, + int non_asoc_addr_ok) +{ + int cur_addr_num=0, num_prefered=0; + uint8_t sin_loop, sin_local; + struct ifnet *ifn; + struct sockaddr_in *sin; + struct in_addr ans; + struct ifaddr *ifa; + /* + * For v4 we can use (in boundall) any address in the association. If + * non_asoc_addr_ok is set we can use any address (at least in theory). + * So we look for prefered addresses first. If we find one, we use it. + * Otherwise we next try to get an address on the interface, which we + * should be able to do (unless non_asoc_addr_ok is false and we are + * routed out that way). In these cases where we can't use the address + * of the interface we go through all the ifn's looking for an address + * we can use and fill that in. Punting means we send back address + * 0, which will probably cause problems actually since then IP will + * fill in the address of the route ifn, which means we probably already + * rejected it.. i.e. here comes an abort :-<. + */ + ifn = rt->rt_ifp; + if (net) { + cur_addr_num = net->indx_of_eligible_next_to_use; + } + if (ifn == NULL) { + goto bound_all_v4_plan_c; + } + num_prefered = sctp_count_v4_num_prefered_boundall (ifn, stcb, non_asoc_addr_ok, loopscope, ipv4_scope, &sin_loop, &sin_local); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Found %d prefered source addresses\n", num_prefered); + } +#endif + if (num_prefered == 0) { + /* no eligible addresses, we must use some other + * interface address if we can find one. + */ + goto bound_all_v4_plan_b; + } + /* Ok we have num_eligible_addr set with how many we can use, + * this may vary from call to call due to addresses being deprecated etc.. + */ + if (cur_addr_num >= num_prefered) { + cur_addr_num = 0; + } + /* select the nth address from the list (where cur_addr_num is the nth) and + * 0 is the first one, 1 is the second one etc... + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("cur_addr_num:%d\n", cur_addr_num); + } +#endif + sin = sctp_select_v4_nth_prefered_addr_from_ifn_boundall (ifn, stcb, non_asoc_addr_ok, loopscope, + ipv4_scope, cur_addr_num); + + /* if sin is NULL something changed??, plan_a now */ + if (sin) { + return (sin->sin_addr); + } + + /* + * plan_b: Look at the interface that we emit on + * and see if we can find an acceptable address. + */ + bound_all_v4_plan_b: + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + sin = sctp_is_v4_ifa_addr_acceptable (ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + if (stcb) { + if ((non_asoc_addr_ok == 0) && sctp_is_addr_restricted(stcb, (struct sockaddr *)sin)) { + /* It is restricted for some reason.. probably + * not yet added. + */ + continue; + } + } + return (sin->sin_addr); + } + /* + * plan_c: Look at all interfaces and find a prefered + * address. If we reache here we are in trouble I think. + */ + bound_all_v4_plan_c: + for (ifn = TAILQ_FIRST(&ifnet); + ifn && (ifn != inp->next_ifn_touse); + ifn=TAILQ_NEXT(ifn, if_list)) { + if (loopscope == 0 && ifn->if_type == IFT_LOOP) { + /* wrong base scope */ + continue; + } + if (ifn == rt->rt_ifp) + /* already looked at this guy */ + continue; + num_prefered = sctp_count_v4_num_prefered_boundall (ifn, stcb, non_asoc_addr_ok, + loopscope, ipv4_scope, &sin_loop, &sin_local); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Found ifn:%x %d prefered source addresses\n", (u_int)ifn, num_prefered); + } +#endif + if (num_prefered == 0) { + /* + * None on this interface. + */ + continue; + } + /* Ok we have num_eligible_addr set with how many we can use, + * this may vary from call to call due to addresses being deprecated etc.. + */ + if (cur_addr_num >= num_prefered) { + cur_addr_num = 0; + } + sin = sctp_select_v4_nth_prefered_addr_from_ifn_boundall (ifn, stcb, non_asoc_addr_ok, loopscope, + ipv4_scope, cur_addr_num); + if (sin == NULL) + continue; + return (sin->sin_addr); + + } + + /* + * plan_d: We are in deep trouble. No prefered address on + * any interface. And the emit interface does not + * even have an acceptable address. Take anything + * we can get! If this does not work we are + * probably going to emit a packet that will + * illicit an ABORT, falling through. + */ + + for (ifn = TAILQ_FIRST(&ifnet); + ifn && (ifn != inp->next_ifn_touse); + ifn=TAILQ_NEXT(ifn, if_list)) { + if (loopscope == 0 && ifn->if_type == IFT_LOOP) { + /* wrong base scope */ + continue; + } + if (ifn == rt->rt_ifp) + /* already looked at this guy */ + continue; + + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + sin = sctp_is_v4_ifa_addr_acceptable (ifa, loopscope, ipv4_scope, &sin_loop, &sin_local); + if (sin == NULL) + continue; + if (stcb) { + if ((non_asoc_addr_ok == 0) && sctp_is_addr_restricted(stcb, (struct sockaddr *)sin)) { + /* It is restricted for some reason.. probably + * not yet added. + */ + continue; + } + } + return (sin->sin_addr); + } + } + /* + * Ok we can find NO address to source from that is + * not on our negative list. It is either the special + * ASCONF case where we are sourceing from a intf that + * has been ifconfig'd to a different address (i.e. + * it holds a ADD/DEL/SET-PRIM and the proper lookup + * address. OR we are hosed, and this baby is going + * to abort the association. + */ + if (non_asoc_addr_ok) { + return (((struct sockaddr_in *)(rt->rt_ifa->ifa_addr))->sin_addr); + } else { + memset(&ans, 0, sizeof(ans)); + return (ans); + } +} + + + +/* tcb may be NULL */ +struct in_addr +sctp_ipv4_source_address_selection(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, struct route *ro, struct sctp_nets *net, + int non_asoc_addr_ok) +{ + struct in_addr ans; + struct sockaddr_in *to = (struct sockaddr_in *)&ro->ro_dst; + uint8_t ipv4_scope, loopscope; + /* + * Rules: + * - Find the route if needed, cache if I can. + * - Look at interface address in route, Is it + * in the bound list. If so we have the best source. + * - If not we must rotate amongst the addresses. + * + * Cavets and issues + * + * Do we need to pay attention to scope. We can have + * a private address or a global address we are sourcing + * or sending to. So if we draw it out + * source * dest * result + * ------------------------------------------ + * a Private * Global * NAT? + * ------------------------------------------ + * b Private * Private * No problem + * ------------------------------------------ + * c Global * Private * Huh, How will this work? + * ------------------------------------------ + * d Global * Global * No Problem + * ------------------------------------------ + * + * And then we add to that what happens if there are multiple + * addresses assigned to an interface. Remember the ifa on a + * ifn is a linked list of addresses. So one interface can + * have more than one IPv4 address. What happens if we + * have both a private and a global address? Do we then + * use context of destination to sort out which one is + * best? And what about NAT's sending P->G may get you + * a NAT translation, or should you select the G thats + * on the interface in preference. + * + * Decisions: + * + * - count the number of addresses on the interface. + * - if its one, no problem except case . For + * we will assume a NAT out there. + * - if there are more than one, then we need to worry + * about scope P or G. We should prefer G -> G and + * P -> P if possible. Then as a secondary fall back + * to mixed types G->P being a last ditch one. + * - The above all works for bound all, but bound + * specific we need to use the same concept but instead + * only consider the bound addresses. If the bound set + * is NOT assigned to the interface then we must use + * rotation amongst them. + * + * Notes: For v4, we can always punt and let ip_output + * decide by sending back a source of 0.0.0.0 + */ + + if (ro->ro_rt == NULL) { + /* + * Need a route to cache. + * + */ +#if defined(__FreeBSD__) || defined(__APPLE__) + rtalloc_ign(ro, 0UL); +#else + rtalloc(ro); +#endif + } + if (ro->ro_rt == NULL) { + /* No route to host .. punt */ + memset(&ans, 0, sizeof(ans)); + return (ans); + } + /* Setup our scopes */ + if (stcb) { + ipv4_scope = stcb->asoc.ipv4_local_scope; + loopscope = stcb->asoc.loopback_scope; + } else { + /* Scope based on outbound address */ + if ((IN4_ISPRIVATE_ADDRESS(&to->sin_addr))) { + ipv4_scope = 1; + loopscope = 0; + } else if (IN4_ISLOOPBACK_ADDRESS(&to->sin_addr)) { + ipv4_scope = 1; + loopscope = 1; + } else { + ipv4_scope = 0; + loopscope = 0; + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Scope setup loop:%d ipv4_scope:%d\n", + loopscope, ipv4_scope); + } +#endif + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + /* + * When bound to all if the address list is set + * it is a negative list. Addresses being added + * by asconf. + */ + return (sctp_choose_v4_boundall(inp, stcb, net, ro->ro_rt, + ipv4_scope, loopscope, non_asoc_addr_ok)); + } + /* + * Three possiblities here: + * + * a) stcb is NULL, which means we operate only from + * the list of addresses (ifa's) bound to the assoc and + * we care not about the list. + * b) stcb is NOT-NULL, which means we have an assoc structure and + * auto-asconf is on. This means that the list of addresses is + * a NOT list. We use the list from the inp, but any listed address + * in our list is NOT yet added. However if the non_asoc_addr_ok is + * set we CAN use an address NOT available (i.e. being added). Its + * a negative list. + * c) stcb is NOT-NULL, which means we have an assoc structure and + * auto-asconf is off. This means that the list of addresses is + * the ONLY addresses I can use.. its positive. + * + * Note we collapse b & c into the same function just like in + * the v6 address selection. + */ + if (stcb) { + return (sctp_choose_v4_boundspecific_stcb(inp, stcb, net, + ro->ro_rt, ipv4_scope, loopscope, non_asoc_addr_ok)); + } else { + return (sctp_choose_v4_boundspecific_inp(inp, ro->ro_rt, + ipv4_scope, loopscope)); + } + /* this should not be reached */ + memset(&ans, 0, sizeof(ans)); + return (ans); +} + + + +static struct sockaddr_in6 * +sctp_is_v6_ifa_addr_acceptable (struct ifaddr *ifa, int loopscope, int loc_scope, int *sin_loop, int *sin_local) +{ + struct in6_ifaddr *ifa6; + struct sockaddr_in6 *sin6; + + if (ifa->ifa_addr->sa_family != AF_INET6) { + /* forget non-v6 */ + return (NULL); + } + ifa6 = (struct in6_ifaddr *)ifa; + /* ok to use deprecated addresses? */ + if (!ip6_use_deprecated) { + if (IFA6_IS_DEPRECATED(ifa6)) { + /* can't use this type */ + return (NULL); + } + } + /* are we ok, with the current state of this address? */ + if (ifa6->ia6_flags & + (IN6_IFF_DETACHED | IN6_IFF_NOTREADY | IN6_IFF_ANYCAST)) { + /* Can't use these types */ + return (NULL); + } + /* Ok the address may be ok */ + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + *sin_local = *sin_loop = 0; + if ((ifa->ifa_ifp->if_type == IFT_LOOP) || + (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr))) { + *sin_loop = 1; + } + if (!loopscope && *sin_loop) { + /* Its a loopback address and we don't have loop scope */ + return (NULL); + } + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + /* we skip unspecifed addresses */ + return (NULL); + } + + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { + *sin_local = 1; + } + if (!loc_scope && *sin_local) { + /* Its a link local address, and we don't have link local scope */ + return (NULL); + } + return (sin6); +} + + +static struct sockaddr_in6 * +sctp_choose_v6_boundspecific_stcb(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_nets *net, + struct rtentry *rt, + uint8_t loc_scope, + uint8_t loopscope, + int non_asoc_addr_ok) +{ + /* + * Each endpoint has a list of local addresses associated + * with it. The address list is either a "negative list" i.e. + * those addresses that are NOT allowed to be used as a source OR + * a "postive list" i.e. those addresses that CAN be used. + * + * Its a negative list if asconf is allowed. What we do + * in this case is use the ep address list BUT we have + * to cross check it against the negative list. + * + * In the case where NO asconf is allowed, we have just + * a straight association level list that we must use to + * find a source address. + */ + struct sctp_laddr *laddr, *starting_point; + struct sockaddr_in6 *sin6; + int sin_loop, sin_local; + int start_at_beginning=0; + struct ifnet *ifn; + struct ifaddr *ifa; + + ifn = rt->rt_ifp; + if (inp->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Have a STCB - asconf allowed, not bound all have a netgative list\n"); + } +#endif + /* first question, is the ifn we will emit on + * in our list, if so, we want that one. + */ + if (ifn) { + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (sctp_is_addr_in_ep(inp, ifa)) { + sin6 = sctp_is_v6_ifa_addr_acceptable (ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + if ((non_asoc_addr_ok == 0) && + (sctp_is_addr_restricted(stcb, (struct sockaddr *)sin6))) { + /* on the no-no list */ + continue; + } + return (sin6); + } + } + } + starting_point = stcb->asoc.last_used_address; + /* First try for matching scope */ + sctp_from_the_top: + if (stcb->asoc.last_used_address == NULL) { + start_at_beginning=1; + stcb->asoc.last_used_address = LIST_FIRST(&inp->sctp_addr_list); + } + /* search beginning with the last used address */ + for (laddr = stcb->asoc.last_used_address; laddr; + laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin6 = sctp_is_v6_ifa_addr_acceptable (laddr->ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + if ((non_asoc_addr_ok == 0) && (sctp_is_addr_restricted(stcb, (struct sockaddr *)sin6))) { + /* on the no-no list */ + continue; + } + /* is it of matching scope ? */ + if ((loopscope == 0) && + (loc_scope == 0) && + (sin_loop == 0) && + (sin_local == 0)) { + /* all of global scope we are ok with it */ + return (sin6); + } + if (loopscope && sin_loop) + /* both on the loopback, thats ok */ + return (sin6); + if (loc_scope && sin_local) + /* both local scope */ + return (sin6); + + } + if (start_at_beginning == 0) { + stcb->asoc.last_used_address = NULL; + goto sctp_from_the_top; + } + /* now try for any higher scope than the destination */ + stcb->asoc.last_used_address = starting_point; + start_at_beginning = 0; + sctp_from_the_top2: + if (stcb->asoc.last_used_address == NULL) { + start_at_beginning=1; + stcb->asoc.last_used_address = LIST_FIRST(&inp->sctp_addr_list); + } + /* search beginning with the last used address */ + for (laddr = stcb->asoc.last_used_address; laddr; + laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin6 = sctp_is_v6_ifa_addr_acceptable (laddr->ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + if ((non_asoc_addr_ok == 0) && (sctp_is_addr_restricted(stcb, (struct sockaddr *)sin6))) { + /* on the no-no list */ + continue; + } + return (sin6); + } + if (start_at_beginning == 0) { + stcb->asoc.last_used_address = NULL; + goto sctp_from_the_top2; + } + } else { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Have a STCB - no asconf allowed, not bound all have a postive list\n"); + } +#endif + /* First try for interface output match */ + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, + sctp_nxt_addr) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin6 = sctp_is_v6_ifa_addr_acceptable (laddr->ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + /* first question, is laddr->ifa an address associated with the emit interface */ + if (ifn) { + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (laddr->ifa == ifa) { + sin6 = (struct sockaddr_in6 *)laddr->ifa->ifa_addr; + return (sin6); + } + if (sctp_cmpaddr(ifa->ifa_addr, laddr->ifa->ifa_addr) == 1) { + sin6 = (struct sockaddr_in6 *)laddr->ifa->ifa_addr; + return (sin6); + } + } + } + } + /* Next try for matching scope */ + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, + sctp_nxt_addr) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin6 = sctp_is_v6_ifa_addr_acceptable (laddr->ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + + if ((loopscope == 0) && + (loc_scope == 0) && + (sin_loop == 0) && + (sin_local == 0)) { + /* all of global scope we are ok with it */ + return (sin6); + } + if (loopscope && sin_loop) + /* both on the loopback, thats ok */ + return (sin6); + if (loc_scope && sin_local) + /* both local scope */ + return (sin6); + } + /* ok, now try for a higher scope in the source address */ + /* First try for matching scope */ + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, + sctp_nxt_addr) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin6 = sctp_is_v6_ifa_addr_acceptable (laddr->ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + return (sin6); + } + } + return (NULL); +} + +static struct sockaddr_in6 * +sctp_choose_v6_boundspecific_inp(struct sctp_inpcb *inp, + struct rtentry *rt, + uint8_t loc_scope, + uint8_t loopscope) +{ + /* + * Here we are bound specific and have only + * an inp. We must find an address that is bound + * that we can give out as a src address. We + * prefer two addresses of same scope if we can + * find them that way. + */ + struct sctp_laddr *laddr; + struct sockaddr_in6 *sin6; + struct ifnet *ifn; + struct ifaddr *ifa; + int sin_loop, sin_local; + + /* first question, is the ifn we will emit on + * in our list, if so, we want that one. + */ + + ifn = rt->rt_ifp; + if (ifn) { + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + sin6 = sctp_is_v6_ifa_addr_acceptable (ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + if (sctp_is_addr_in_ep(inp, ifa)) { + return (sin6); + } + } + } + for (laddr = LIST_FIRST(&inp->sctp_addr_list); + laddr && (laddr != inp->next_addr_touse); + laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin6 = sctp_is_v6_ifa_addr_acceptable (laddr->ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + + if ((loopscope == 0) && + (loc_scope == 0) && + (sin_loop == 0) && + (sin_local == 0)) { + /* all of global scope we are ok with it */ + return (sin6); + } + if (loopscope && sin_loop) + /* both on the loopback, thats ok */ + return (sin6); + if (loc_scope && sin_local) + /* both local scope */ + return (sin6); + + } + /* if we reach here, we could not find two addresses + * of the same scope to give out. Lets look for any higher level + * scope for a source address. + */ + for (laddr = LIST_FIRST(&inp->sctp_addr_list); + laddr && (laddr != inp->next_addr_touse); + laddr = LIST_NEXT(laddr, sctp_nxt_addr)) { + if (laddr->ifa == NULL) { + /* address has been removed */ + continue; + } + sin6 = sctp_is_v6_ifa_addr_acceptable (laddr->ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + return (sin6); + } + /* no address bound can be a source for the destination */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Src address selection for EP, no acceptable src address found for address\n"); + } +#endif + return (NULL); +} + + +static struct sockaddr_in6 * +sctp_select_v6_nth_addr_from_ifn_boundall (struct ifnet *ifn, struct sctp_tcb *stcb, int non_asoc_addr_ok, uint8_t loopscope, + uint8_t loc_scope, int cur_addr_num, int match_scope) +{ + struct ifaddr *ifa; + struct sockaddr_in6 *sin6; + int sin_loop, sin_local; + int num_eligible_addr = 0; + + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + sin6 = sctp_is_v6_ifa_addr_acceptable (ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + if (stcb) { + if ((non_asoc_addr_ok == 0) && sctp_is_addr_restricted(stcb, (struct sockaddr *)sin6)) { + /* It is restricted for some reason.. probably + * not yet added. + */ + continue; + } + } + if (match_scope) { + /* Here we are asked to match scope if possible */ + if (loopscope && sin_loop) + /* src and destination are loopback scope */ + return (sin6); + if (loc_scope && sin_local) + /* src and destination are local scope */ + return (sin6); + if ((loopscope == 0) && + (loc_scope == 0) && + (sin_loop == 0) && + (sin_local == 0)) { + /* src and destination are global scope */ + return (sin6); + } + continue; + } + if (num_eligible_addr == cur_addr_num) { + /* this is it */ + return (sin6); + } + num_eligible_addr++; + } + return (NULL); +} + + +static int +sctp_count_v6_num_eligible_boundall (struct ifnet *ifn, struct sctp_tcb *stcb, + int non_asoc_addr_ok, uint8_t loopscope, uint8_t loc_scope) +{ + struct ifaddr *ifa; + struct sockaddr_in6 *sin6; + int num_eligible_addr = 0; + int sin_loop, sin_local; + + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + sin6 = sctp_is_v6_ifa_addr_acceptable (ifa, loopscope, loc_scope, &sin_loop, &sin_local); + if (sin6 == NULL) + continue; + if (stcb) { + if ((non_asoc_addr_ok == 0) && sctp_is_addr_restricted(stcb, (struct sockaddr *)sin6)) { + /* It is restricted for some reason.. probably + * not yet added. + */ + continue; + } + } + num_eligible_addr++; + } + return (num_eligible_addr); +} + + +static struct sockaddr_in6 * +sctp_choose_v6_boundall(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_nets *net, + struct rtentry *rt, + uint8_t loc_scope, + uint8_t loopscope, + int non_asoc_addr_ok) +{ + /* Ok, we are bound all SO any address + * is ok to use as long as it is NOT in the negative + * list. + */ + int num_eligible_addr; + int cur_addr_num=0; + int started_at_beginning=0; + int match_scope_prefered; + /* first question is, how many eligible addresses are + * there for the destination ifn that we are using that + * are within the proper scope? + */ + struct ifnet *ifn; + struct sockaddr_in6 *sin6; + + ifn = rt->rt_ifp; + if (net) { + cur_addr_num = net->indx_of_eligible_next_to_use; + } + if (cur_addr_num == 0) { + match_scope_prefered = 1; + } else { + match_scope_prefered = 0; + } + num_eligible_addr = sctp_count_v6_num_eligible_boundall (ifn, stcb, non_asoc_addr_ok, loopscope, loc_scope); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Found %d eligible source addresses\n", num_eligible_addr); + } +#endif + if (num_eligible_addr == 0) { + /* no eligible addresses, we must use some other + * interface address if we can find one. + */ + goto bound_all_v6_plan_b; + } + /* Ok we have num_eligible_addr set with how many we can use, + * this may vary from call to call due to addresses being deprecated etc.. + */ + if (cur_addr_num >= num_eligible_addr) { + cur_addr_num = 0; + } + /* select the nth address from the list (where cur_addr_num is the nth) and + * 0 is the first one, 1 is the second one etc... + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("cur_addr_num:%d match_scope_prefered:%d select it\n", + cur_addr_num, match_scope_prefered); + } +#endif + sin6 = sctp_select_v6_nth_addr_from_ifn_boundall (ifn, stcb, non_asoc_addr_ok, loopscope, + loc_scope, cur_addr_num, match_scope_prefered); + if (match_scope_prefered && (sin6 == NULL)) { + /* retry without the preference for matching scope */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("retry with no match_scope_prefered\n"); + } +#endif + sin6 = sctp_select_v6_nth_addr_from_ifn_boundall (ifn, stcb, non_asoc_addr_ok, loopscope, + loc_scope, cur_addr_num, 0); + } + if (sin6) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Selected address %d ifn:%x for the route\n", cur_addr_num, (u_int)ifn); + } +#endif + if (net) { + /* store so we get the next one */ + if (cur_addr_num < 255) + net->indx_of_eligible_next_to_use = cur_addr_num + 1; + else + net->indx_of_eligible_next_to_use = 0; + } + return (sin6); + } + num_eligible_addr = 0; + bound_all_v6_plan_b: + /* ok, if we reach here we either fell through + * due to something changing during an interupt (unlikely) + * or we have NO eligible source addresses for the ifn + * of the route (most likely). We must look at all the other + * interfaces EXCEPT rt->rt_ifp and do the same game. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("bound-all Plan B\n"); + } +#endif + if (inp->next_ifn_touse == NULL) { + started_at_beginning=1; + inp->next_ifn_touse = TAILQ_FIRST(&ifnet); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Start at first IFN:%x\n", (u_int)inp->next_ifn_touse); + } +#endif + } else { + inp->next_ifn_touse = TAILQ_NEXT(inp->next_ifn_touse, if_list); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Resume at IFN:%x\n", (u_int)inp->next_ifn_touse); + } +#endif + if (inp->next_ifn_touse == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("IFN Resets\n"); + } +#endif + started_at_beginning=1; + inp->next_ifn_touse = TAILQ_FIRST(&ifnet); + } + } + for (ifn = inp->next_ifn_touse; ifn; + ifn = TAILQ_NEXT(ifn, if_list)) { + if (loopscope == 0 && ifn->if_type == IFT_LOOP) { + /* wrong base scope */ + continue; + } + if (loc_scope && (ifn->if_index != loc_scope)) { + /* by definition the scope (from to->sin6_scopeid) + * must match that of the interface. If not then + * we could pick a wrong scope for the address. + * Ususally we don't hit plan-b since the route + * handles this. However we can hit plan-b when + * we send to local-host so the route is the + * loopback interface, but the destination is a + * link local. + */ + continue; + } + if (ifn == rt->rt_ifp) { + /* already looked at this guy */ + continue; + } + /* Address rotation will only work when we are not + * rotating sourced interfaces and are using the interface + * of the route. We would need to have a per interface index + * in order to do proper rotation. + */ + num_eligible_addr = sctp_count_v6_num_eligible_boundall (ifn, stcb, non_asoc_addr_ok, loopscope, loc_scope); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("IFN:%x has %d eligible\n", (u_int)ifn, num_eligible_addr); + } +#endif + if (num_eligible_addr == 0) { + /* none we can use */ + continue; + } + /* Ok we have num_eligible_addr set with how many we can use, + * this may vary from call to call due to addresses being deprecated etc.. + */ + inp->next_ifn_touse = ifn; + + /* select the first one we can find with perference for matching scope. + */ + sin6 = sctp_select_v6_nth_addr_from_ifn_boundall (ifn, stcb, non_asoc_addr_ok, loopscope, loc_scope, 0, 1); + if (sin6 == NULL) { + /* can't find one with matching scope how about a source with higher + * scope + */ + sin6 = sctp_select_v6_nth_addr_from_ifn_boundall (ifn, stcb, non_asoc_addr_ok, loopscope, loc_scope, 0, 0); + if (sin6 == NULL) + /* Hmm, can't find one in the interface now */ + continue; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Selected the %d'th address of ifn:%x\n", + cur_addr_num, + (u_int)ifn); + } +#endif + return (sin6); + } + if (started_at_beginning == 0) { + /* we have not been through all of them yet, force + * us to go through them all. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Force a recycle\n"); + } +#endif + inp->next_ifn_touse = NULL; + goto bound_all_v6_plan_b; + } + return (NULL); + +} + +/* stcb and net may be NULL */ +struct in6_addr +sctp_ipv6_source_address_selection(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, struct route *ro, struct sctp_nets *net, + int non_asoc_addr_ok) +{ + struct in6_addr ans; + struct sockaddr_in6 *rt_addr; + uint8_t loc_scope, loopscope; + struct sockaddr_in6 *to = (struct sockaddr_in6 *)&ro->ro_dst; + + /* + * This routine is tricky standard v6 src address + * selection cannot take into account what we have + * bound etc, so we can't use it. + * + * Instead here is what we must do: + * 1) Make sure we have a route, if we + * don't have a route we can never reach the peer. + * 2) Once we have a route, determine the scope of the + * route. Link local, loopback or global. + * 3) Next we divide into three types. Either we + * are bound all.. which means we want to use + * one of the addresses of the interface we are + * going out. + * 4a) We have not stcb, which means we are using the + * specific addresses bound on an inp, in this + * case we are similar to the stcb case (4b below) + * accept the list is always a positive list. + * 4b) We are bound specific with a stcb, which means we have a + * list of bound addresses and we must see if the + * ifn of the route is actually one of the bound addresses. + * If not, then we must rotate addresses amongst properly + * scoped bound addresses, if so we use the address + * of the interface. + * 5) Always, no matter which path we take through the above + * we must be sure the source address we use is allowed to + * be used. I.e. IN6_IFF_DETACHED, IN6_IFF_NOTREADY, and IN6_IFF_ANYCAST + * addresses cannot be used. + * 6) Addresses that are deprecated MAY be used + * if (!ip6_use_deprecated) { + * if (IFA6_IS_DEPRECATED(ifa6)) { + * skip the address + * } + * } + */ + + /*** 1> determine route, if not already done */ + if (ro->ro_rt == NULL) { + /* + * Need a route to cache. + */ +#ifndef SCOPEDROUTING + int scope_save; + scope_save = to->sin6_scope_id; + to->sin6_scope_id = 0; +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) + rtalloc_ign(ro, 0UL); +#else + rtalloc(ro); +#endif +#ifndef SCOPEDROUTING + to->sin6_scope_id = scope_save; +#endif + } + if (ro->ro_rt == NULL) { + /* + * no route to host. this packet is going no-where. + * We probably should make sure we arrange to send back + * an error. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("No route to host, this packet cannot be sent!\n"); + } +#endif + memset(&ans, 0, sizeof(ans)); + return (ans); + } + + /*** 2a> determine scope for outbound address/route */ + loc_scope = loopscope = 0; + /* + * We base our scope on the outbound packet scope and route, + * NOT the TCB (if there is one). This way in local scope we will only + * use a local scope src address when we send to a local address. + */ + + if (IN6_IS_ADDR_LOOPBACK(&to->sin6_addr)) { + /* If the route goes to the loopback address OR + * the address is a loopback address, we are loopback + * scope. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Loopback scope is set\n"); + } +#endif + loc_scope = 0; + loopscope = 1; + if (net != NULL) { + /* mark it as local */ + net->addr_is_local = 1; + } + + } else if (IN6_IS_ADDR_LINKLOCAL(&to->sin6_addr)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Link local scope is set, id:%d\n", to->sin6_scope_id); + } +#endif + if (to->sin6_scope_id) + loc_scope = to->sin6_scope_id; + else { + loc_scope = 1; + } + loopscope = 0; + } else { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Global scope is set\n"); + } +#endif + } + + /* now, depending on which way we are bound we call the appropriate + * routine to do steps 3-6 + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Destination address:"); + sctp_print_address((struct sockaddr *)to); + } +#endif + + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Calling bound-all src addr selection for v6\n"); + } +#endif + rt_addr = sctp_choose_v6_boundall(inp, stcb, net, ro->ro_rt, loc_scope, loopscope, non_asoc_addr_ok); + } else { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Calling bound-specific src addr selection for v6\n"); + } +#endif + if (stcb) + rt_addr = sctp_choose_v6_boundspecific_stcb(inp, stcb, net, ro->ro_rt, loc_scope, loopscope, non_asoc_addr_ok); + else + /* we can't have a non-asoc address since we have no association */ + rt_addr = sctp_choose_v6_boundspecific_inp(inp, ro->ro_rt, loc_scope, loopscope); + } + if (rt_addr == NULL) { + /* no suitable address? */ + struct in6_addr in6; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("V6 packet will reach dead-end no suitable src address\n"); + } +#endif + memset(&in6, 0, sizeof(in6)); + return (in6); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Source address selected is:"); + sctp_print_address((struct sockaddr *)rt_addr); + } +#endif + return (rt_addr->sin6_addr); +} + +static uint8_t +sctp_get_ect(struct sctp_tcb *stcb, + struct sctp_tmit_chunk *chk) +{ + uint8_t this_random; + + /* Huh? */ + if (sctp_ecn == 0) + return (0); + + if (sctp_ecn_nonce == 0) + /* no nonce, always return ECT0 */ + return (SCTP_ECT0_BIT); + + if (stcb->asoc.peer_supports_ecn_nonce == 0) { + /* Peer does NOT support it, so we send a ECT0 only */ + return (SCTP_ECT0_BIT); + } + + if (chk == NULL) + return (SCTP_ECT0_BIT); + + if (((stcb->asoc.hb_random_idx == 3) && + (stcb->asoc.hb_ect_randombit > 7)) || + (stcb->asoc.hb_random_idx > 3)) { + uint32_t rndval; + rndval = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep); + memcpy(stcb->asoc.hb_random_values, &rndval, + sizeof(stcb->asoc.hb_random_values)); + this_random = stcb->asoc.hb_random_values[0]; + stcb->asoc.hb_random_idx = 0; + stcb->asoc.hb_ect_randombit = 0; + } else { + if (stcb->asoc.hb_ect_randombit > 7) { + stcb->asoc.hb_ect_randombit = 0; + stcb->asoc.hb_random_idx++; + } + this_random = stcb->asoc.hb_random_values[stcb->asoc.hb_random_idx]; + } + if ((this_random >> stcb->asoc.hb_ect_randombit) & 0x01) { + if (chk != NULL) + /* ECN Nonce stuff */ + chk->rec.data.ect_nonce = SCTP_ECT1_BIT; + stcb->asoc.hb_ect_randombit++; + return (SCTP_ECT1_BIT); + } else { + stcb->asoc.hb_ect_randombit++; + return (SCTP_ECT0_BIT); + } +} + +extern int sctp_no_csum_on_loopback; + +static int +sctp_lowlevel_chunk_output(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, /* may be NULL */ + struct sctp_nets *net, + struct sockaddr *to, + struct mbuf *m, + int nofragment_flag, + int ecn_ok, + struct sctp_tmit_chunk *chk, + int out_of_asoc_ok) + /* nofragment_flag to tell if IP_DF should be set (IPv4 only) */ +{ + /* + * Given a mbuf chain (via m_next) that holds a packet header + * WITH a SCTPHDR but no IP header, endpoint inp and sa structure. + * - calculate SCTP checksum and fill in + * - prepend a IP address header + * - if boundall use INADDR_ANY + * - if boundspecific do source address selection + * - set fragmentation option for ipV4 + * - On return from IP output, check/adjust mtu size + * - of output interface and smallest_mtu size as well. + */ + struct sctphdr *sctphdr; + int o_flgs; + uint32_t csum; + int ret; + unsigned int have_mtu; + struct route *ro; + + if ((net) && (net->dest_state & SCTP_ADDR_OUT_OF_SCOPE)) { + sctp_m_freem(m); + return (EFAULT); + } + if ((m->m_flags & M_PKTHDR) == 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Software error: sctp_lowlevel_chunk_output() called with non pkthdr!\n"); + } +#endif + sctp_m_freem(m); + return (EFAULT); + } + /* Calculate the csum and fill in the length of the packet */ + sctphdr = mtod(m, struct sctphdr *); + have_mtu = 0; + if (sctp_no_csum_on_loopback && + (stcb) && + (stcb->asoc.loopback_scope)) { + sctphdr->checksum = 0; + m->m_pkthdr.len = sctp_calculate_len(m); + } else { + sctphdr->checksum = 0; + csum = sctp_calculate_sum(m, &m->m_pkthdr.len, 0); + sctphdr->checksum = csum; + } + if (to->sa_family == AF_INET) { + struct ip *ip; + struct route iproute; + M_PREPEND(m, sizeof(struct ip), M_DONTWAIT); + if (m == NULL) { + /* failed to prepend data, give up */ + return (ENOMEM); + } + ip = mtod(m, struct ip *); + ip->ip_v = IPVERSION; + ip->ip_hl = (sizeof(struct ip) >> 2); + if (nofragment_flag) { +#if defined(WITH_CONVERT_IP_OFF) || defined(__FreeBSD__) +#if defined( __OpenBSD__) || defined(__NetBSD__) + /* OpenBSD has WITH_CONVERT_IP_OFF defined?? */ + ip->ip_off = htons(IP_DF); +#else + ip->ip_off = IP_DF; +#endif +#else + ip->ip_off = htons(IP_DF); +#endif + } else + ip->ip_off = 0; + +/* FreeBSD and Apple have RANDOM_IP_ID switch */ +#if defined(RANDOM_IP_ID) || defined(__NetBSD__) || defined(__OpenBSD__) + ip->ip_id = htons(ip_randomid()); +#else + ip->ip_id = htons(ip_id++); +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) + ip->ip_ttl = inp->ip_inp.inp.inp_ip_ttl; +#else + ip->ip_ttl = inp->inp_ip_ttl; +#endif +#if defined(__OpenBSD__) || defined(__NetBSD__) + ip->ip_len = htons(m->m_pkthdr.len); +#else + ip->ip_len = m->m_pkthdr.len; +#endif + if (stcb) { + if ((stcb->asoc.ecn_allowed) && ecn_ok) { + /* Enable ECN */ +#if defined(__FreeBSD__) || defined (__APPLE__) + ip->ip_tos = (u_char)((inp->ip_inp.inp.inp_ip_tos & 0x000000fc) | + sctp_get_ect(stcb, chk)); +#elif defined(__NetBSD__) + ip->ip_tos = (u_char)((inp->ip_inp.inp.inp_ip.ip_tos & 0x000000fc) | + sctp_get_ect(stcb, chk)); +#else + ip->ip_tos = (u_char)((inp->inp_ip_tos & 0x000000fc) | + sctp_get_ect(stcb, chk)); +#endif + } else { + /* No ECN */ +#if defined(__FreeBSD__) || defined(__APPLE__) + ip->ip_tos = inp->ip_inp.inp.inp_ip_tos; +#elif defined(__NetBSD__) + ip->ip_tos = inp->ip_inp.inp.inp_ip.ip_tos; +#else + ip->ip_tos = inp->inp_ip_tos; +#endif + } + } else { + /* no association at all */ +#if defined(__FreeBSD__) || defined(__APPLE__) + ip->ip_tos = inp->ip_inp.inp.inp_ip_tos; +#else + ip->ip_tos = inp->inp_ip_tos; +#endif + } + ip->ip_p = IPPROTO_SCTP; + ip->ip_sum = 0; + if (net == NULL) { + ro = &iproute; + memset(&iproute, 0, sizeof(iproute)); + memcpy(&ro->ro_dst, to, to->sa_len); + } else { + ro = (struct route *)&net->ro; + } + /* Now the address selection part */ + ip->ip_dst.s_addr = ((struct sockaddr_in *)to)->sin_addr.s_addr; + + /* call the routine to select the src address */ + if (net) { + if (net->src_addr_selected == 0) { + /* Cache the source address */ + ((struct sockaddr_in *)&net->ro._s_addr)->sin_addr = sctp_ipv4_source_address_selection(inp, + stcb, + ro, net, out_of_asoc_ok); + if (ro->ro_rt) + net->src_addr_selected = 1; + } + ip->ip_src = ((struct sockaddr_in *)&net->ro._s_addr)->sin_addr; + } else { + ip->ip_src = sctp_ipv4_source_address_selection(inp, + stcb, ro, net, out_of_asoc_ok); + } + /* + * If source address selection fails and we find no route then + * the ip_ouput should fail as well with a NO_ROUTE_TO_HOST + * type error. We probably should catch that somewhere and + * abort the association right away (assuming this is an INIT + * being sent). + */ + if ((ro->ro_rt == NULL)) { + /* + * src addr selection failed to find a route (or valid + * source addr), so we can't get there from here! + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("low_level_output: dropped v4 packet- no valid source addr\n"); + printf("Destination was %x\n", (u_int)(ntohl(ip->ip_dst.s_addr))); + } +#endif /* SCTP_DEBUG */ + if (net) { + if ((net->dest_state & SCTP_ADDR_REACHABLE) && stcb) + sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, + stcb, + SCTP_FAILED_THRESHOLD, + (void *)net); + net->dest_state &= ~SCTP_ADDR_REACHABLE; + net->dest_state |= SCTP_ADDR_NOT_REACHABLE; + if (stcb) { + if (net == stcb->asoc.primary_destination) { + /* need a new primary */ + struct sctp_nets *alt; + alt = sctp_find_alternate_net(stcb, net); + if (alt != net) { + if (sctp_set_primary_addr(stcb, + (struct sockaddr *)NULL, + alt) == 0) { + net->dest_state |= SCTP_ADDR_WAS_PRIMARY; + net->src_addr_selected = 0; + } + } + } + } + } + sctp_m_freem(m); + return (EHOSTUNREACH); + } else { + have_mtu = ro->ro_rt->rt_ifp->if_mtu; + } + + o_flgs = (IP_RAWOUTPUT | (inp->sctp_socket->so_options & (SO_DONTROUTE | SO_BROADCAST))); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Calling ipv4 output routine from low level src addr:%x\n", + (u_int)(ntohl(ip->ip_src.s_addr))); + printf("Destination is %x\n", (u_int)(ntohl(ip->ip_dst.s_addr))); + printf("RTP route is %p through\n", ro->ro_rt); + } +#endif + if ((have_mtu) && (net) && (have_mtu > net->mtu)) { + ro->ro_rt->rt_ifp->if_mtu = net->mtu; + } + ret = ip_output(m, inp->ip_inp.inp.inp_options, + ro, o_flgs, inp->ip_inp.inp.inp_moptions +#if defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD_version >= 480000) + , (struct inpcb *)NULL +#endif +#if defined(__NetBSD__) + ,(struct socket *)inp->sctp_socket +#endif + +); + if ((ro->ro_rt) && (have_mtu) && (net) && (have_mtu > net->mtu)) { + ro->ro_rt->rt_ifp->if_mtu = have_mtu; + } + sctp_pegs[SCTP_DATAGRAMS_SENT]++; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Ip output returns %d\n", ret); + } +#endif + if (net == NULL) { + /* free tempy routes */ + if (ro->ro_rt) + RTFREE(ro->ro_rt); + } else { + /* PMTU check versus smallest asoc MTU goes here */ + if (ro->ro_rt != NULL) { + if (ro->ro_rt->rt_rmx.rmx_mtu && + (stcb->asoc.smallest_mtu > ro->ro_rt->rt_rmx.rmx_mtu)) { + sctp_mtu_size_reset(inp, &stcb->asoc, + ro->ro_rt->rt_rmx.rmx_mtu); + } + } else { + /* route was freed */ + net->src_addr_selected = 0; + } + } + return (ret); + } +#ifdef INET6 + else if (to->sa_family == AF_INET6) { + struct ip6_hdr *ip6h; +#ifdef NEW_STRUCT_ROUTE + struct route ip6route; +#else + struct route_in6 ip6route; +#endif + struct ifnet *ifp; + u_char flowTop; + uint16_t flowBottom; + u_char tosBottom, tosTop; + struct sockaddr_in6 *sin6, tmp, *lsa6, lsa6_tmp; + struct sockaddr_in6 lsa6_storage; + int prev_scope=0; + int error; + u_short prev_port=0; + + M_PREPEND(m, sizeof(struct ip6_hdr), M_DONTWAIT); + if (m == NULL) { + /* failed to prepend data, give up */ + return (ENOMEM); + } + ip6h = mtod(m, struct ip6_hdr *); + + /* + * We assume here that inp_flow is in host byte order within + * the TCB! + */ + flowBottom = ((struct in6pcb *)inp)->in6p_flowinfo & 0x0000ffff; + flowTop = ((((struct in6pcb *)inp)->in6p_flowinfo & 0x000f0000) >> 16); + + tosTop = (((((struct in6pcb *)inp)->in6p_flowinfo & 0xf0) >> 4) | IPV6_VERSION); + + /* protect *sin6 from overwrite */ + sin6 = (struct sockaddr_in6 *)to; + tmp = *sin6; + sin6 = &tmp; + + /* KAME hack: embed scopeid */ +#if defined(SCTP_BASE_FREEBSD) || defined(__APPLE__) + if (in6_embedscope(&sin6->sin6_addr, sin6, NULL, NULL) != 0) +#else + if (in6_embedscope(&sin6->sin6_addr, sin6) != 0) +#endif + return (EINVAL); + if (net == NULL) { + memset(&ip6route, 0, sizeof(ip6route)); + ro = (struct route *)&ip6route; + memcpy(&ro->ro_dst, sin6, sin6->sin6_len); + } else { + ro = (struct route *)&net->ro; + } + if (stcb != NULL) { + if ((stcb->asoc.ecn_allowed) && ecn_ok) { + /* Enable ECN */ + tosBottom = (((((struct in6pcb *)inp)->in6p_flowinfo & 0x0c) | sctp_get_ect(stcb, chk)) << 4); + } else { + /* No ECN */ + tosBottom = ((((struct in6pcb *)inp)->in6p_flowinfo & 0x0c) << 4); + } + } else { + /* we could get no asoc if it is a O-O-T-B packet */ + tosBottom = ((((struct in6pcb *)inp)->in6p_flowinfo & 0x0c) << 4); + } + ip6h->ip6_flow = htonl(((tosTop << 24) | ((tosBottom|flowTop) << 16) | flowBottom)); + ip6h->ip6_nxt = IPPROTO_SCTP; + ip6h->ip6_plen = m->m_pkthdr.len; + ip6h->ip6_dst = sin6->sin6_addr; + + /* + * Add SRC address selection here: + * we can only reuse to a limited degree the kame src-addr-sel, + * since we can try their selection but it may not be bound. + */ + bzero(&lsa6_tmp, sizeof(lsa6_tmp)); + lsa6_tmp.sin6_family = AF_INET6; + lsa6_tmp.sin6_len = sizeof(lsa6_tmp); + lsa6 = &lsa6_tmp; + if (net) { + if (net->src_addr_selected == 0) { + /* Cache the source address */ + ((struct sockaddr_in6 *)&net->ro._s_addr)->sin6_addr = sctp_ipv6_source_address_selection(inp, + stcb, ro, net, out_of_asoc_ok); + + if (ro->ro_rt) + net->src_addr_selected = 1; + } + lsa6->sin6_addr = ((struct sockaddr_in6 *)&net->ro._s_addr)->sin6_addr; + } else { + lsa6->sin6_addr = sctp_ipv6_source_address_selection( + inp, stcb, ro, net, out_of_asoc_ok); + } + lsa6->sin6_port = inp->sctp_lport; + + if ((ro->ro_rt == NULL)) { + /* + * src addr selection failed to find a route (or valid + * source addr), so we can't get there from here! + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("low_level_output: dropped v6 pkt- no valid source addr\n"); + } +#endif + sctp_m_freem(m); + if (net) { + if ((net->dest_state & SCTP_ADDR_REACHABLE) && stcb) + sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, + stcb, + SCTP_FAILED_THRESHOLD, + (void *)net); + net->dest_state &= ~SCTP_ADDR_REACHABLE; + net->dest_state |= SCTP_ADDR_NOT_REACHABLE; + if (stcb) { + if (net == stcb->asoc.primary_destination) { + /* need a new primary */ + struct sctp_nets *alt; + alt = sctp_find_alternate_net(stcb, net); + if (alt != net) { + if (sctp_set_primary_addr(stcb, + (struct sockaddr *)NULL, + alt) == 0) { + net->dest_state |= SCTP_ADDR_WAS_PRIMARY; + net->src_addr_selected = 0; + } + } + } + } + } + return (EHOSTUNREACH); + } + +#ifndef SCOPEDROUTING + /* + * XXX: sa6 may not have a valid sin6_scope_id in + * the non-SCOPEDROUTING case. + */ + bzero(&lsa6_storage, sizeof(lsa6_storage)); + lsa6_storage.sin6_family = AF_INET6; + lsa6_storage.sin6_len = sizeof(lsa6_storage); + if ((error = in6_recoverscope(&lsa6_storage, &lsa6->sin6_addr, + NULL)) != 0) { + sctp_m_freem(m); + return (error); + } + /* XXX */ + lsa6_storage.sin6_addr = lsa6->sin6_addr; + lsa6_storage.sin6_port = inp->sctp_lport; + lsa6 = &lsa6_storage; +#endif /* SCOPEDROUTING */ + ip6h->ip6_src = lsa6->sin6_addr; + + /* + * We set the hop limit now since there is a good chance that + * our ro pointer is now filled + */ + ip6h->ip6_hlim = in6_selecthlim((struct in6pcb *)&inp->ip_inp.inp, + (ro ? + (ro->ro_rt ? (ro->ro_rt->rt_ifp) : (NULL)) : + (NULL))); + o_flgs = 0; + ifp = ro->ro_rt->rt_ifp; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + /* Copy to be sure something bad is not happening */ + sin6->sin6_addr = ip6h->ip6_dst; + lsa6->sin6_addr = ip6h->ip6_src; + + printf("Calling ipv6 output routine from low level\n"); + printf("src: "); + sctp_print_address((struct sockaddr *)lsa6); + printf("dst: "); + sctp_print_address((struct sockaddr *)sin6); + } +#endif /* SCTP_DEBUG */ + if (net) { + sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; + /* preserve the port and scope for link local send */ + prev_scope = sin6->sin6_scope_id; + prev_port = sin6->sin6_port; + } + ret = ip6_output(m, ((struct in6pcb *)inp)->in6p_outputopts, +#ifdef NEW_STRUCT_ROUTE + ro, +#else + (struct route_in6 *)ro, +#endif + o_flgs, + ((struct in6pcb *)inp)->in6p_moptions, +#if defined(__NetBSD__) + (struct socket *)inp->sctp_socket, +#endif + &ifp +#if (defined(__FreeBSD__) && __FreeBSD_version >= 480000) + , NULL +#endif + ); + if (net) { + /* for link local this must be done */ + sin6->sin6_scope_id = prev_scope; + sin6->sin6_port = prev_port; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("return from send is %d\n", ret); + } +#endif /* SCTP_DEBUG_OUTPUT */ + sctp_pegs[SCTP_DATAGRAMS_SENT]++; + if (net == NULL) { + /* Now if we had a temp route free it */ + if (ro->ro_rt) { + RTFREE(ro->ro_rt); + } + } else { + /* PMTU check versus smallest asoc MTU goes here */ + if (ro->ro_rt == NULL) { + /* Route was freed */ + net->src_addr_selected = 0; + } + if (ro->ro_rt != NULL) { + if (ro->ro_rt->rt_rmx.rmx_mtu && + (stcb->asoc.smallest_mtu > ro->ro_rt->rt_rmx.rmx_mtu)) { + sctp_mtu_size_reset(inp, + &stcb->asoc, + ro->ro_rt->rt_rmx.rmx_mtu); + } + } else if (ifp) { +#if (defined(SCTP_BASE_FREEBSD) && __FreeBSD_version < 500000) || defined(__APPLE__) +#define ND_IFINFO(ifp) (&nd_ifinfo[ifp->if_index]) +#endif /* SCTP_BASE_FREEBSD */ + if (ND_IFINFO(ifp)->linkmtu && + (stcb->asoc.smallest_mtu > ND_IFINFO(ifp)->linkmtu)) { + sctp_mtu_size_reset(inp, + &stcb->asoc, + ND_IFINFO(ifp)->linkmtu); + } + } + } + return (ret); + } +#endif + else { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Unknown protocol (TSNH) type %d\n", ((struct sockaddr *)to)->sa_family); + } +#endif + sctp_m_freem(m); + return (EFAULT); + } +} + +static +int sctp_is_address_in_scope(struct ifaddr *ifa, + int ipv4_addr_legal, + int ipv6_addr_legal, + int loopback_scope, + int ipv4_local_scope, + int local_scope, + int site_scope) +{ + if ((loopback_scope == 0) && + (ifa->ifa_ifp) && + (ifa->ifa_ifp->if_type == IFT_LOOP)) { + /* skip loopback if not in scope * + */ + return (0); + } + if ((ifa->ifa_addr->sa_family == AF_INET) && ipv4_addr_legal) { + struct sockaddr_in *sin; + sin = (struct sockaddr_in *)ifa->ifa_addr; + if (sin->sin_addr.s_addr == 0) { + /* not in scope , unspecified */ + return (0); + } + if ((ipv4_local_scope == 0) && + (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) { + /* private address not in scope */ + return (0); + } + } else if ((ifa->ifa_addr->sa_family == AF_INET6) && ipv6_addr_legal) { + struct sockaddr_in6 *sin6; + struct in6_ifaddr *ifa6; + + ifa6 = (struct in6_ifaddr *)ifa; + /* ok to use deprecated addresses? */ + if (!ip6_use_deprecated) { + if (ifa6->ia6_flags & + IN6_IFF_DEPRECATED) { + return (0); + } + } + if (ifa6->ia6_flags & + (IN6_IFF_DETACHED | + IN6_IFF_ANYCAST | + IN6_IFF_NOTREADY)) { + return (0); + } + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + /* skip unspecifed addresses */ + return (0); + } + if (/*(local_scope == 0) && */ + (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))) { + return (0); + } + if ((site_scope == 0) && + (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) { + return (0); + } + } else { + return (0); + } + return (1); +} + + +void +sctp_send_initiate(struct sctp_inpcb *inp, struct sctp_tcb *stcb) +{ + struct mbuf *m, *m_at, *m_last; + struct sctp_nets *net; + struct sctp_init_msg *initm; + struct sctp_supported_addr_param *sup_addr; + struct sctp_ecn_supported_param *ecn; + struct sctp_prsctp_supported_param *prsctp; + struct sctp_ecn_nonce_supported_param *ecn_nonce; + struct sctp_supported_chunk_types_param *pr_supported; + int cnt_inits_to=0; + int padval, ret; + + /* INIT's always go to the primary (and usually ONLY address) */ + m_last = NULL; + net = stcb->asoc.primary_destination; + if (net == NULL) { + net = TAILQ_FIRST(&stcb->asoc.nets); + if (net == NULL) { + /* TSNH */ + return; + } + /* we confirm any address we send an INIT to */ + net->dest_state &= ~SCTP_ADDR_UNCONFIRMED; + sctp_set_primary_addr(stcb, NULL, net); + } else { + /* we confirm any address we send an INIT to */ + net->dest_state &= ~SCTP_ADDR_UNCONFIRMED; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Sending INIT to "); + sctp_print_address ((struct sockaddr *)&net->ro._l_addr); + } +#endif + if (((struct sockaddr *)&(net->ro._l_addr))->sa_family == AF_INET6) { + /* special hook, if we are sending to link local + * it will not show up in our private address count. + */ + struct sockaddr_in6 *sin6l; + sin6l = &net->ro._l_addr.sin6; + if (IN6_IS_ADDR_LINKLOCAL(&sin6l->sin6_addr)) + cnt_inits_to = 1; + } + if (callout_pending(&net->rxt_timer.timer)) { + /* This case should not happen */ + return; + } + /* start the INIT timer */ + if (sctp_timer_start(SCTP_TIMER_TYPE_INIT, inp, stcb, net)) { + /* we are hosed since I can't start the INIT timer? */ + return; + } + MGETHDR(m, M_DONTWAIT, MT_HEADER); + if (m == NULL) { + /* No memory, INIT timer will re-attempt. */ + return; + } + /* make it into a M_EXT */ + MCLGET(m, M_DONTWAIT); + if ((m->m_flags & M_EXT) != M_EXT) { + /* Failed to get cluster buffer */ + sctp_m_freem(m); + return; + } + m->m_data += SCTP_MIN_OVERHEAD; + m->m_len = sizeof(struct sctp_init_msg); + /* Now lets put the SCTP header in place */ + initm = mtod(m, struct sctp_init_msg *); + initm->sh.src_port = inp->sctp_lport; + initm->sh.dest_port = stcb->rport; + initm->sh.v_tag = 0; + initm->sh.checksum = 0; /* calculate later */ + /* now the chunk header */ + initm->msg.ch.chunk_type = SCTP_INITIATION; + initm->msg.ch.chunk_flags = 0; + /* fill in later from mbuf we build */ + initm->msg.ch.chunk_length = 0; + /* place in my tag */ + initm->msg.init.initiate_tag = htonl(stcb->asoc.my_vtag); + /* set up some of the credits. */ + initm->msg.init.a_rwnd = htonl(max(inp->sctp_socket->so_rcv.sb_hiwat, + SCTP_MINIMAL_RWND)); + + initm->msg.init.num_outbound_streams = htons(stcb->asoc.pre_open_streams); + initm->msg.init.num_inbound_streams = htons(stcb->asoc.max_inbound_streams); + initm->msg.init.initial_tsn = htonl(stcb->asoc.init_seq_number); + /* now the address restriction */ + sup_addr = (struct sctp_supported_addr_param *)((caddr_t)initm + + sizeof(*initm)); + sup_addr->ph.param_type = htons(SCTP_SUPPORTED_ADDRTYPE); + /* we support 2 types IPv6/IPv4 */ + sup_addr->ph.param_length = htons(sizeof(*sup_addr) + + sizeof(uint16_t)); + sup_addr->addr_type[0] = htons(SCTP_IPV4_ADDRESS); + sup_addr->addr_type[1] = htons(SCTP_IPV6_ADDRESS); + m->m_len += sizeof(*sup_addr) + sizeof(uint16_t); + +/* if (inp->sctp_flags & SCTP_PCB_FLAGS_ADAPTIONEVNT) {*/ + if (inp->sctp_ep.adaption_layer_indicator) { + struct sctp_adaption_layer_indication *ali; + ali = (struct sctp_adaption_layer_indication *)( + (caddr_t)sup_addr + sizeof(*sup_addr) + sizeof(uint16_t)); + ali->ph.param_type = htons(SCTP_ULP_ADAPTION); + ali->ph.param_length = htons(sizeof(*ali)); + ali->indication = ntohl(inp->sctp_ep.adaption_layer_indicator); + m->m_len += sizeof(*ali); + ecn = (struct sctp_ecn_supported_param *)((caddr_t)ali + + sizeof(*ali)); + } else { + ecn = (struct sctp_ecn_supported_param *)((caddr_t)sup_addr + + sizeof(*sup_addr) + sizeof(uint16_t)); + } + + /* now any cookie time extensions */ + if (stcb->asoc.cookie_preserve_req) { + struct sctp_cookie_perserve_param *cookie_preserve; + cookie_preserve = (struct sctp_cookie_perserve_param *)(ecn); + cookie_preserve->ph.param_type = htons(SCTP_COOKIE_PRESERVE); + cookie_preserve->ph.param_length = htons( + sizeof(*cookie_preserve)); + cookie_preserve->time = htonl(stcb->asoc.cookie_preserve_req); + m->m_len += sizeof(*cookie_preserve); + ecn = (struct sctp_ecn_supported_param *)( + (caddr_t)cookie_preserve + sizeof(*cookie_preserve)); + stcb->asoc.cookie_preserve_req = 0; + } + + /* ECN parameter */ + if (sctp_ecn == 1) { + ecn->ph.param_type = htons(SCTP_ECN_CAPABLE); + ecn->ph.param_length = htons(sizeof(*ecn)); + m->m_len += sizeof(*ecn); + prsctp = (struct sctp_prsctp_supported_param *)((caddr_t)ecn + + sizeof(*ecn)); + } else { + prsctp = (struct sctp_prsctp_supported_param *)((caddr_t)ecn); + } + /* And now tell the peer we do pr-sctp */ + prsctp->ph.param_type = htons(SCTP_PRSCTP_SUPPORTED); + prsctp->ph.param_length = htons(sizeof(*prsctp)); + m->m_len += sizeof(*prsctp); + + + /* And now tell the peer we do all the extensions */ + pr_supported = (struct sctp_supported_chunk_types_param *)((caddr_t)prsctp + + sizeof(*prsctp)); + + pr_supported->ph.param_type = htons(SCTP_SUPPORTED_CHUNK_EXT); + pr_supported->ph.param_length = htons(sizeof(*pr_supported) + SCTP_EXT_COUNT); + pr_supported->chunk_types[0] = SCTP_ASCONF; + pr_supported->chunk_types[1] = SCTP_ASCONF_ACK; + pr_supported->chunk_types[2] = SCTP_FORWARD_CUM_TSN; + pr_supported->chunk_types[3] = SCTP_PACKET_DROPPED; + pr_supported->chunk_types[4] = SCTP_STREAM_RESET; + pr_supported->chunk_types[5] = 0; /* pad */ + pr_supported->chunk_types[6] = 0; /* pad */ + pr_supported->chunk_types[7] = 0; /* pad */ + + m->m_len += (sizeof(*pr_supported) + SCTP_EXT_COUNT + SCTP_PAD_EXT_COUNT); + /* ECN nonce: And now tell the peer we support ECN nonce */ + + if (sctp_ecn_nonce) { + ecn_nonce = (struct sctp_ecn_nonce_supported_param *)((caddr_t)pr_supported + + sizeof(*pr_supported) + SCTP_EXT_COUNT + SCTP_PAD_EXT_COUNT); + ecn_nonce->ph.param_type = htons(SCTP_ECN_NONCE_SUPPORTED); + ecn_nonce->ph.param_length = htons(sizeof(*ecn_nonce)); + m->m_len += sizeof(*ecn_nonce); + } + + m_at = m; + /* now the addresses */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + struct ifnet *ifn; + struct ifaddr *ifa; + int cnt; + + cnt = cnt_inits_to; + TAILQ_FOREACH(ifn, &ifnet, if_list) { + if ((stcb->asoc.loopback_scope == 0) && + (ifn->if_type == IFT_LOOP)) { + /* + * Skip loopback devices if loopback_scope + * not set + */ + continue; + } + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (sctp_is_address_in_scope(ifa, + stcb->asoc.ipv4_addr_legal, + stcb->asoc.ipv6_addr_legal, + stcb->asoc.loopback_scope, + stcb->asoc.ipv4_local_scope, + stcb->asoc.local_scope, + stcb->asoc.site_scope) == 0) { + continue; + } + cnt++; + } + } + if (cnt > 1) { + TAILQ_FOREACH(ifn, &ifnet, if_list) { + if ((stcb->asoc.loopback_scope == 0) && + (ifn->if_type == IFT_LOOP)) { + /* + * Skip loopback devices if loopback_scope + * not set + */ + continue; + } + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (sctp_is_address_in_scope(ifa, + stcb->asoc.ipv4_addr_legal, + stcb->asoc.ipv6_addr_legal, + stcb->asoc.loopback_scope, + stcb->asoc.ipv4_local_scope, + stcb->asoc.local_scope, + stcb->asoc.site_scope) == 0) { + continue; + } + m_at = sctp_add_addr_to_mbuf(m_at, ifa); + } + } + } + } else { + struct sctp_laddr *laddr; + int cnt; + cnt = cnt_inits_to; + /* First, how many ? */ + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == NULL) { + continue; + } + if (laddr->ifa->ifa_addr == NULL) + continue; + if (sctp_is_address_in_scope(laddr->ifa, + stcb->asoc.ipv4_addr_legal, + stcb->asoc.ipv6_addr_legal, + stcb->asoc.loopback_scope, + stcb->asoc.ipv4_local_scope, + stcb->asoc.local_scope, + stcb->asoc.site_scope) == 0) { + continue; + } + cnt++; + } + /* To get through a NAT we only list addresses if + * we have more than one. That way if you just + * bind a single address we let the source of the init + * dictate our address. + */ + if (cnt > 1) { + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == NULL) { + continue; + } + if (laddr->ifa->ifa_addr == NULL) { + continue; + } + + if (sctp_is_address_in_scope(laddr->ifa, + stcb->asoc.ipv4_addr_legal, + stcb->asoc.ipv6_addr_legal, + stcb->asoc.loopback_scope, + stcb->asoc.ipv4_local_scope, + stcb->asoc.local_scope, + stcb->asoc.site_scope) == 0) { + continue; + } + m_at = sctp_add_addr_to_mbuf(m_at, laddr->ifa); + } + } + } + /* calulate the size and update pkt header and chunk header */ + m->m_pkthdr.len = 0; + for (m_at = m; m_at; m_at = m_at->m_next) { + if (m_at->m_next == NULL) + m_last = m_at; + m->m_pkthdr.len += m_at->m_len; + } + initm->msg.ch.chunk_length = htons((m->m_pkthdr.len - + sizeof(struct sctphdr))); + /* We pass 0 here to NOT set IP_DF if its IPv4, we + * ignore the return here since the timer will drive + * a retranmission. + */ + + /* I don't expect this to execute but we will be safe here */ + padval = m->m_pkthdr.len % 4; + if ((padval) && (m_last)) { + /* The compiler worries that m_last may not be + * set even though I think it is impossible :-> + * however we add m_last here just in case. + */ + int ret; + ret = sctp_add_pad_tombuf(m_last, (4-padval)); + if (ret) { + /* Houston we have a problem, no space */ + sctp_m_freem(m); + return; + } + m->m_pkthdr.len += padval; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Calling lowlevel output stcb:%x net:%x\n", + (u_int)stcb, (u_int)net); + } +#endif + ret = sctp_lowlevel_chunk_output(inp, stcb, net, + (struct sockaddr *)&net->ro._l_addr, m, 0, 0, NULL, 0); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Low level output returns %d\n", ret); + } +#endif + sctp_timer_start(SCTP_TIMER_TYPE_INIT, inp, stcb, net); + SCTP_GETTIME_TIMEVAL(&net->last_sent_time); +} + +struct mbuf * +sctp_arethere_unrecognized_parameters(struct mbuf *in_initpkt, + int param_offset, int *abort_processing, struct sctp_chunkhdr *cp) +{ + /* Given a mbuf containing an INIT or INIT-ACK + * with the param_offset being equal to the + * beginning of the params i.e. (iphlen + sizeof(struct sctp_init_msg) + * parse through the parameters to the end of the mbuf verifying + * that all parameters are known. + * + * For unknown parameters build and return a mbuf with + * UNRECOGNIZED_PARAMETER errors. If the flags indicate + * to stop processing this chunk stop, and set *abort_processing + * to 1. + * + * By having param_offset be pre-set to where parameters begin + * it is hoped that this routine may be reused in the future + * by new features. + */ + struct sctp_paramhdr *phdr, params; + + struct mbuf *mat, *op_err; + char tempbuf[2048]; + int at, limit, pad_needed; + uint16_t ptype, plen; + int err_at; + + *abort_processing = 0; + mat = in_initpkt; + err_at = 0; + limit = ntohs(cp->chunk_length) - sizeof(struct sctp_init_chunk); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Limit is %d bytes\n", limit); + } +#endif + at = param_offset; + op_err = NULL; + + phdr = sctp_get_next_param(mat, at, ¶ms, sizeof(params)); + while ((phdr != NULL) && ((size_t)limit >= sizeof(struct sctp_paramhdr))) { + ptype = ntohs(phdr->param_type); + plen = ntohs(phdr->param_length); + limit -= SCTP_SIZE32(plen); + if (plen < sizeof(struct sctp_paramhdr)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("sctp_output.c:Impossible length in parameter < %d\n", plen); + } +#endif + *abort_processing = 1; + break; + } + /* All parameters for all chunks that we + * know/understand are listed here. We process + * them other places and make appropriate + * stop actions per the upper bits. However + * this is the generic routine processor's can + * call to get back an operr.. to either incorporate (init-ack) + * or send. + */ + if ((ptype == SCTP_HEARTBEAT_INFO) || + (ptype == SCTP_IPV4_ADDRESS) || + (ptype == SCTP_IPV6_ADDRESS) || + (ptype == SCTP_STATE_COOKIE) || + (ptype == SCTP_UNRECOG_PARAM) || + (ptype == SCTP_COOKIE_PRESERVE) || + (ptype == SCTP_SUPPORTED_ADDRTYPE) || + (ptype == SCTP_PRSCTP_SUPPORTED) || + (ptype == SCTP_ADD_IP_ADDRESS) || + (ptype == SCTP_DEL_IP_ADDRESS) || + (ptype == SCTP_ECN_CAPABLE) || + (ptype == SCTP_ULP_ADAPTION) || + (ptype == SCTP_ERROR_CAUSE_IND) || + (ptype == SCTP_SET_PRIM_ADDR) || + (ptype == SCTP_SUCCESS_REPORT) || + (ptype == SCTP_ULP_ADAPTION) || + (ptype == SCTP_SUPPORTED_CHUNK_EXT) || + (ptype == SCTP_ECN_NONCE_SUPPORTED) + ) { + /* no skip it */ + at += SCTP_SIZE32(plen); + } else if (ptype == SCTP_HOSTNAME_ADDRESS) { + /* We can NOT handle HOST NAME addresses!! */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Can't handle hostname addresses.. abort processing\n"); + } +#endif + *abort_processing = 1; + if (op_err == NULL) { + /* Ok need to try to get a mbuf */ + MGETHDR(op_err, M_DONTWAIT, MT_DATA); + if (op_err) { + op_err->m_len = 0; + op_err->m_pkthdr.len = 0; + /* pre-reserve space for ip and sctp header and chunk hdr*/ + op_err->m_data += sizeof(struct ip6_hdr); + op_err->m_data += sizeof(struct sctphdr); + op_err->m_data += sizeof(struct sctp_chunkhdr); + } + } + if (op_err) { + /* If we have space */ + struct sctp_paramhdr s; + if (err_at % 4) { + u_int32_t cpthis=0; + pad_needed = 4 - (err_at % 4); + m_copyback(op_err, err_at, pad_needed, (caddr_t)&cpthis); + err_at += pad_needed; + } + s.param_type = htons(SCTP_CAUSE_UNRESOLV_ADDR); + s.param_length = htons(sizeof(s) + plen); + m_copyback(op_err, err_at, sizeof(s), (caddr_t)&s); + err_at += sizeof(s); + phdr = sctp_get_next_param(mat, at, (struct sctp_paramhdr *)tempbuf, plen); + if (phdr == NULL) { + sctp_m_freem(op_err); + /* we are out of memory but we + * still need to have a look at what to + * do (the system is in trouble though). + */ + return (NULL); + } + m_copyback(op_err, err_at, plen, (caddr_t)phdr); + err_at += plen; + } + return (op_err); + } else { + /* we do not recognize the parameter + * figure out what we do. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Got parameter type %x - unknown\n", + (u_int)ptype); + } +#endif + if ((ptype & 0x4000) == 0x4000) { + /* Report bit is set?? */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Report bit is set\n"); + } +#endif + if (op_err == NULL) { + /* Ok need to try to get an mbuf */ + MGETHDR(op_err, M_DONTWAIT, MT_DATA); + if (op_err) { + op_err->m_len = 0; + op_err->m_pkthdr.len = 0; + op_err->m_data += sizeof(struct ip6_hdr); + op_err->m_data += sizeof(struct sctphdr); + op_err->m_data += sizeof(struct sctp_chunkhdr); + } + } + if (op_err) { + /* If we have space */ + struct sctp_paramhdr s; + if (err_at % 4) { + u_int32_t cpthis=0; + pad_needed = 4 - (err_at % 4); + m_copyback(op_err, err_at, pad_needed, (caddr_t)&cpthis); + err_at += pad_needed; + } + s.param_type = htons(SCTP_UNRECOG_PARAM); + s.param_length = htons(sizeof(s) + plen); + m_copyback(op_err, err_at, sizeof(s), (caddr_t)&s); + err_at += sizeof(s); + if (plen > sizeof(tempbuf)) { + plen = sizeof(tempbuf); + } + phdr = sctp_get_next_param(mat, at, (struct sctp_paramhdr *)tempbuf, plen); + if (phdr == NULL) { + sctp_m_freem(op_err); + /* we are out of memory but we + * still need to have a look at what to + * do (the system is in trouble though). + */ + goto more_processing; + } + m_copyback(op_err, err_at, plen, (caddr_t)phdr); + err_at += plen; + } + } + more_processing: + if ((ptype & 0x8000) == 0x0000) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Abort bit is now setting1\n"); + } +#endif + return (op_err); + } else { + /* skip this chunk and continue processing */ + at += SCTP_SIZE32(plen); + } + + } + phdr = sctp_get_next_param(mat, at, ¶ms, sizeof(params)); + } + return (op_err); +} + +static int +sctp_are_there_new_addresses(struct sctp_association *asoc, + struct mbuf *in_initpkt, int iphlen, int offset) +{ + /* + * Given a INIT packet, look through the packet to verify that + * there are NO new addresses. As we go through the parameters + * add reports of any un-understood parameters that require an + * error. Also we must return (1) to drop the packet if we see + * a un-understood parameter that tells us to drop the chunk. + */ + struct sockaddr_in sin4, *sa4; + struct sockaddr_in6 sin6, *sa6; + struct sockaddr *sa_touse; + struct sockaddr *sa; + struct sctp_paramhdr *phdr, params; + struct ip *iph; + struct mbuf *mat; + uint16_t ptype, plen; + int err_at; + uint8_t fnd; + struct sctp_nets *net; + + memset(&sin4, 0, sizeof(sin4)); + memset(&sin6, 0, sizeof(sin6)); + sin4.sin_family = AF_INET; + sin4.sin_len = sizeof(sin4); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(sin6); + + sa_touse = NULL; + /* First what about the src address of the pkt ? */ + iph = mtod(in_initpkt, struct ip *); + if (iph->ip_v == IPVERSION) { + /* source addr is IPv4 */ + sin4.sin_addr = iph->ip_src; + sa_touse = (struct sockaddr *)&sin4; + } else if (iph->ip_v == (IPV6_VERSION >> 4)) { + /* source addr is IPv6 */ + struct ip6_hdr *ip6h; + ip6h = mtod(in_initpkt, struct ip6_hdr *); + sin6.sin6_addr = ip6h->ip6_src; + sa_touse = (struct sockaddr *)&sin6; + } else { + return (1); + } + + fnd = 0; + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + sa = (struct sockaddr *)&net->ro._l_addr; + if (sa->sa_family == sa_touse->sa_family) { + if (sa->sa_family == AF_INET) { + sa4 = (struct sockaddr_in *)sa; + if (sa4->sin_addr.s_addr == + sin4.sin_addr.s_addr) { + fnd = 1; + break; + } + } else if (sa->sa_family == AF_INET6) { + sa6 = (struct sockaddr_in6 *)sa; + if (SCTP6_ARE_ADDR_EQUAL(&sa6->sin6_addr, + &sin6.sin6_addr)) { + fnd = 1; + break; + } + } + } + } + if (fnd == 0) { + /* New address added! no need to look futher. */ + return (1); + } + /* Ok so far lets munge through the rest of the packet */ + mat = in_initpkt; + err_at = 0; + sa_touse = NULL; + offset += sizeof(struct sctp_init_chunk); + phdr = sctp_get_next_param(mat, offset, ¶ms, sizeof(params)); + while (phdr) { + ptype = ntohs(phdr->param_type); + plen = ntohs(phdr->param_length); + if (ptype == SCTP_IPV4_ADDRESS) { + struct sctp_ipv4addr_param *p4, p4_buf; + + phdr = sctp_get_next_param(mat, offset, + (struct sctp_paramhdr *)&p4_buf, sizeof(p4_buf)); + if (plen != sizeof(struct sctp_ipv4addr_param) || + phdr == NULL) { + return (1); + } + p4 = (struct sctp_ipv4addr_param *)phdr; + sin4.sin_addr.s_addr = p4->addr; + sa_touse = (struct sockaddr *)&sin4; + } else if (ptype == SCTP_IPV6_ADDRESS) { + struct sctp_ipv6addr_param *p6, p6_buf; + + phdr = sctp_get_next_param(mat, offset, + (struct sctp_paramhdr *)&p6_buf, sizeof(p6_buf)); + if (plen != sizeof(struct sctp_ipv6addr_param) || + phdr == NULL) { + return (1); + } + p6 = (struct sctp_ipv6addr_param *)phdr; + memcpy((caddr_t)&sin6.sin6_addr, p6->addr, + sizeof(p6->addr)); + sa_touse = (struct sockaddr *)&sin4; + } + + if (sa_touse) { + /* ok, sa_touse points to one to check */ + fnd = 0; + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + sa = (struct sockaddr *)&net->ro._l_addr; + if (sa->sa_family != sa_touse->sa_family) { + continue; + } + if (sa->sa_family == AF_INET) { + sa4 = (struct sockaddr_in *)sa; + if (sa4->sin_addr.s_addr == + sin4.sin_addr.s_addr) { + fnd = 1; + break; + } + } else if (sa->sa_family == AF_INET6) { + sa6 = (struct sockaddr_in6 *)sa; + if (SCTP6_ARE_ADDR_EQUAL( + &sa6->sin6_addr, &sin6.sin6_addr)) { + fnd = 1; + break; + } + } + } + if (!fnd) { + /* New addr added! no need to look further */ + return (1); + } + } + offset += SCTP_SIZE32(plen); + phdr = sctp_get_next_param(mat, offset, ¶ms, sizeof(params)); + } + return (0); +} + +/* + * Given a MBUF chain that was sent into us containing an + * INIT. Build a INIT-ACK with COOKIE and send back. + * We assume that the in_initpkt has done a pullup to + * include IPv6/4header, SCTP header and initial part of + * INIT message (i.e. the struct sctp_init_msg). + */ +void +sctp_send_initiate_ack(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct mbuf *init_pkt, int iphlen, int offset, struct sctphdr *sh, + struct sctp_init_chunk *init_chk) +{ + struct sctp_association *asoc; + struct mbuf *m, *m_at, *m_tmp, *m_cookie, *op_err, *m_last; + struct sctp_init_msg *initackm_out; + struct sctp_ecn_supported_param *ecn; + struct sctp_prsctp_supported_param *prsctp; + struct sctp_ecn_nonce_supported_param *ecn_nonce; + struct sctp_supported_chunk_types_param *pr_supported; + struct sockaddr_storage store; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct route *ro; + struct ip *iph; + struct ip6_hdr *ip6; + struct sockaddr *to; + struct sctp_state_cookie stc; + struct sctp_nets *net=NULL; + int cnt_inits_to=0; + uint16_t his_limit, i_want; + int abort_flag, padval, sz_of; + + if (stcb) { + asoc = &stcb->asoc; + } else { + asoc = NULL; + } + m_last = NULL; + if ((asoc != NULL) && + (SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_WAIT) && + (sctp_are_there_new_addresses(asoc, init_pkt, iphlen, offset))) { + /* new addresses, out of here in non-cookie-wait states */ + /* + * Send a ABORT, we don't add the new address error clause though + * we even set the T bit and copy in the 0 tag.. this looks no + * different than if no listner was present. + */ + sctp_send_abort(init_pkt, iphlen, sh, 0, NULL); + return; + } + abort_flag = 0; + op_err = sctp_arethere_unrecognized_parameters(init_pkt, + (offset+sizeof(struct sctp_init_chunk)), + &abort_flag, (struct sctp_chunkhdr *)init_chk); + if (abort_flag) { + sctp_send_abort(init_pkt, iphlen, sh, init_chk->init.initiate_tag, op_err); + return; + } + MGETHDR(m, M_DONTWAIT, MT_HEADER); + if (m == NULL) { + /* No memory, INIT timer will re-attempt. */ + if (op_err) + sctp_m_freem(op_err); + return; + } + MCLGET(m, M_DONTWAIT); + if ((m->m_flags & M_EXT) != M_EXT) { + /* Failed to get cluster buffer */ + if (op_err) + sctp_m_freem(op_err); + sctp_m_freem(m); + return; + } + m->m_data += SCTP_MIN_OVERHEAD; + m->m_pkthdr.rcvif = 0; + m->m_len = sizeof(struct sctp_init_msg); + + /* the time I built cookie */ + SCTP_GETTIME_TIMEVAL(&stc.time_entered); + + /* populate any tie tags */ + if (asoc != NULL) { + /* unlock before tag selections */ + SCTP_TCB_UNLOCK(stcb); + if (asoc->my_vtag_nonce == 0) + asoc->my_vtag_nonce = sctp_select_a_tag(inp); + stc.tie_tag_my_vtag = asoc->my_vtag_nonce; + + if (asoc->peer_vtag_nonce == 0) + asoc->peer_vtag_nonce = sctp_select_a_tag(inp); + stc.tie_tag_peer_vtag = asoc->peer_vtag_nonce; + + stc.cookie_life = asoc->cookie_life; + net = asoc->primary_destination; + /* now we must relock */ + SCTP_INP_RLOCK(inp); + /* we may be in trouble here if the inp got freed + * most likely this set of tests will protect + * us but there is a chance not. + */ + if (inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE|SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { + if (op_err) + sctp_m_freem(op_err); + sctp_m_freem(m); + sctp_send_abort(init_pkt, iphlen, sh, 0, NULL); + return; + } + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(stcb->sctp_ep); + } else { + stc.tie_tag_my_vtag = 0; + stc.tie_tag_peer_vtag = 0; + /* life I will award this cookie */ + stc.cookie_life = inp->sctp_ep.def_cookie_life; + } + + /* copy in the ports for later check */ + stc.myport = sh->dest_port; + stc.peerport = sh->src_port; + + /* + * If we wanted to honor cookie life extentions, we would add + * to stc.cookie_life. For now we should NOT honor any extension + */ + stc.site_scope = stc.local_scope = stc.loopback_scope = 0; + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + struct inpcb *in_inp; + /* Its a V6 socket */ + in_inp = (struct inpcb *)inp; + stc.ipv6_addr_legal = 1; + /* Now look at the binding flag to see if V4 will be legal */ + if ( +#if defined(__FreeBSD__) || defined(__APPLE__) + (in_inp->inp_flags & IN6P_IPV6_V6ONLY) +#elif defined(__OpenBSD__) + (0) /* For openbsd we do dual bind only */ +#else + (((struct in6pcb *)in_inp)->in6p_flags & IN6P_IPV6_V6ONLY) +#endif + == 0) { + stc.ipv4_addr_legal = 1; + } else { + /* V4 addresses are NOT legal on the association */ + stc.ipv4_addr_legal = 0; + } + } else { + /* Its a V4 socket, no - V6 */ + stc.ipv4_addr_legal = 1; + stc.ipv6_addr_legal = 0; + } + +#ifdef SCTP_DONT_DO_PRIVADDR_SCOPE + stc.ipv4_scope = 1; +#else + stc.ipv4_scope = 0; +#endif + /* now for scope setup */ + memset((caddr_t)&store, 0, sizeof(store)); + sin = (struct sockaddr_in *)&store; + sin6 = (struct sockaddr_in6 *)&store; + if (net == NULL) { + to = (struct sockaddr *)&store; + iph = mtod(init_pkt, struct ip *); + if (iph->ip_v == IPVERSION) { + struct in_addr addr; + struct route iproute; + + sin->sin_family = AF_INET; + sin->sin_len = sizeof(struct sockaddr_in); + sin->sin_port = sh->src_port; + sin->sin_addr = iph->ip_src; + /* lookup address */ + stc.address[0] = sin->sin_addr.s_addr; + stc.address[1] = 0; + stc.address[2] = 0; + stc.address[3] = 0; + stc.addr_type = SCTP_IPV4_ADDRESS; + /* local from address */ + memset(&iproute, 0, sizeof(iproute)); + ro = &iproute; + memcpy(&ro->ro_dst, sin, sizeof(*sin)); + addr = sctp_ipv4_source_address_selection(inp, NULL, + ro, NULL, 0); + if (ro->ro_rt) { + RTFREE(ro->ro_rt); + } + stc.laddress[0] = addr.s_addr; + stc.laddress[1] = 0; + stc.laddress[2] = 0; + stc.laddress[3] = 0; + stc.laddr_type = SCTP_IPV4_ADDRESS; + /* scope_id is only for v6 */ + stc.scope_id = 0; +#ifndef SCTP_DONT_DO_PRIVADDR_SCOPE + if (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) { + stc.ipv4_scope = 1; + } +#else + stc.ipv4_scope = 1; +#endif /* SCTP_DONT_DO_PRIVADDR_SCOPE */ + /* Must use the address in this case */ + if (sctp_is_address_on_local_host((struct sockaddr *)sin)) { + stc.loopback_scope = 1; + stc.ipv4_scope = 1; + stc.site_scope = 1; + stc.local_scope = 1; + } + } else if (iph->ip_v == (IPV6_VERSION >> 4)) { + struct in6_addr addr; +#ifdef NEW_STRUCT_ROUTE + struct route iproute6; +#else + struct route_in6 iproute6; +#endif + ip6 = mtod(init_pkt, struct ip6_hdr *); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_port = sh->src_port; + sin6->sin6_addr = ip6->ip6_src; + /* lookup address */ + memcpy(&stc.address, &sin6->sin6_addr, + sizeof(struct in6_addr)); + sin6->sin6_scope_id = 0; + stc.addr_type = SCTP_IPV6_ADDRESS; + stc.scope_id = 0; + if (sctp_is_address_on_local_host((struct sockaddr *)sin6)) { + stc.loopback_scope = 1; + stc.local_scope = 1; + stc.site_scope = 1; + stc.ipv4_scope = 1; + } else if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { + /* + * If the new destination is a LINK_LOCAL + * we must have common both site and local + * scope. Don't set local scope though since + * we must depend on the source to be added + * implicitly. We cannot assure just because + * we share one link that all links are common. + */ + stc.local_scope = 0; + stc.site_scope = 1; + stc.ipv4_scope = 1; + /* we start counting for the private + * address stuff at 1. since the link + * local we source from won't show + * up in our scoped cou8nt. + */ + cnt_inits_to=1; + /* pull out the scope_id from incoming pkt */ + (void)in6_recoverscope(sin6, &ip6->ip6_src, + init_pkt->m_pkthdr.rcvif); +#if defined(SCTP_BASE_FREEBSD) || defined(__APPLE__) + in6_embedscope(&sin6->sin6_addr, sin6, NULL, + NULL); +#else + in6_embedscope(&sin6->sin6_addr, sin6); +#endif + stc.scope_id = sin6->sin6_scope_id; + } else if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) { + /* + * If the new destination is SITE_LOCAL + * then we must have site scope in common. + */ + stc.site_scope = 1; + } + /* local from address */ + memset(&iproute6, 0, sizeof(iproute6)); + ro = (struct route *)&iproute6; + memcpy(&ro->ro_dst, sin6, sizeof(*sin6)); + addr = sctp_ipv6_source_address_selection(inp, NULL, + ro, NULL, 0); + if (ro->ro_rt) { + RTFREE(ro->ro_rt); + } + memcpy(&stc.laddress, &addr, sizeof(struct in6_addr)); + stc.laddr_type = SCTP_IPV6_ADDRESS; + } + } else { + /* set the scope per the existing tcb */ + struct sctp_nets *lnet; + + stc.loopback_scope = asoc->loopback_scope; + stc.ipv4_scope = asoc->ipv4_local_scope; + stc.site_scope = asoc->site_scope; + stc.local_scope = asoc->local_scope; + TAILQ_FOREACH(lnet, &asoc->nets, sctp_next) { + if (lnet->ro._l_addr.sin6.sin6_family == AF_INET6) { + if (IN6_IS_ADDR_LINKLOCAL(&lnet->ro._l_addr.sin6.sin6_addr)) { + /* if we have a LL address, start counting + * at 1. + */ + cnt_inits_to = 1; + } + } + } + + /* use the net pointer */ + to = (struct sockaddr *)&net->ro._l_addr; + if (to->sa_family == AF_INET) { + sin = (struct sockaddr_in *)to; + stc.address[0] = sin->sin_addr.s_addr; + stc.address[1] = 0; + stc.address[2] = 0; + stc.address[3] = 0; + stc.addr_type = SCTP_IPV4_ADDRESS; + if (net->src_addr_selected == 0) { + /* strange case here, the INIT + * should have did the selection. + */ + net->ro._s_addr.sin.sin_addr = + sctp_ipv4_source_address_selection(inp, + stcb, (struct route *)&net->ro, net, 0); + net->src_addr_selected = 1; + + } + + stc.laddress[0] = net->ro._s_addr.sin.sin_addr.s_addr; + stc.laddress[1] = 0; + stc.laddress[2] = 0; + stc.laddress[3] = 0; + stc.laddr_type = SCTP_IPV4_ADDRESS; + } else if (to->sa_family == AF_INET6) { + sin6 = (struct sockaddr_in6 *)to; + memcpy(&stc.address, &sin6->sin6_addr, + sizeof(struct in6_addr)); + stc.addr_type = SCTP_IPV6_ADDRESS; + if (net->src_addr_selected == 0) { + /* strange case here, the INIT + * should have did the selection. + */ + net->ro._s_addr.sin6.sin6_addr = + sctp_ipv6_source_address_selection(inp, + stcb, (struct route *)&net->ro, net, 0); + net->src_addr_selected = 1; + } + memcpy(&stc.laddress, &net->ro._l_addr.sin6.sin6_addr, + sizeof(struct in6_addr)); + stc.laddr_type = SCTP_IPV6_ADDRESS; + } + } + /* Now lets put the SCTP header in place */ + initackm_out = mtod(m, struct sctp_init_msg *); + initackm_out->sh.src_port = inp->sctp_lport; + initackm_out->sh.dest_port = sh->src_port; + initackm_out->sh.v_tag = init_chk->init.initiate_tag; + /* Save it off for quick ref */ + stc.peers_vtag = init_chk->init.initiate_tag; + initackm_out->sh.checksum = 0; /* calculate later */ + /* who are we */ + strncpy(stc.identification, SCTP_VERSION_STRING, + min(strlen(SCTP_VERSION_STRING), sizeof(stc.identification))); + /* now the chunk header */ + initackm_out->msg.ch.chunk_type = SCTP_INITIATION_ACK; + initackm_out->msg.ch.chunk_flags = 0; + /* fill in later from mbuf we build */ + initackm_out->msg.ch.chunk_length = 0; + /* place in my tag */ + if ((asoc != NULL) && + ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED))) { + /* re-use the v-tags and init-seq here */ + initackm_out->msg.init.initiate_tag = htonl(asoc->my_vtag); + initackm_out->msg.init.initial_tsn = htonl(asoc->init_seq_number); + } else { + initackm_out->msg.init.initiate_tag = htonl(sctp_select_a_tag(inp)); + /* get a TSN to use too */ + initackm_out->msg.init.initial_tsn = htonl(sctp_select_initial_TSN(&inp->sctp_ep)); + } + /* save away my tag to */ + stc.my_vtag = initackm_out->msg.init.initiate_tag; + + /* set up some of the credits. */ + initackm_out->msg.init.a_rwnd = htonl(max(inp->sctp_socket->so_rcv.sb_hiwat, SCTP_MINIMAL_RWND)); + /* set what I want */ + his_limit = ntohs(init_chk->init.num_inbound_streams); + /* choose what I want */ + if (asoc != NULL) { + if (asoc->streamoutcnt > inp->sctp_ep.pre_open_stream_count) { + i_want = asoc->streamoutcnt; + } else { + i_want = inp->sctp_ep.pre_open_stream_count; + } + } else { + i_want = inp->sctp_ep.pre_open_stream_count; + } + if (his_limit < i_want) { + /* I Want more :< */ + initackm_out->msg.init.num_outbound_streams = init_chk->init.num_inbound_streams; + } else { + /* I can have what I want :> */ + initackm_out->msg.init.num_outbound_streams = htons(i_want); + } + /* tell him his limt. */ + initackm_out->msg.init.num_inbound_streams = + htons(inp->sctp_ep.max_open_streams_intome); + /* setup the ECN pointer */ + +/* if (inp->sctp_flags & SCTP_PCB_FLAGS_ADAPTIONEVNT) {*/ + if (inp->sctp_ep.adaption_layer_indicator) { + struct sctp_adaption_layer_indication *ali; + ali = (struct sctp_adaption_layer_indication *)( + (caddr_t)initackm_out + sizeof(*initackm_out)); + ali->ph.param_type = htons(SCTP_ULP_ADAPTION); + ali->ph.param_length = htons(sizeof(*ali)); + ali->indication = ntohl(inp->sctp_ep.adaption_layer_indicator); + m->m_len += sizeof(*ali); + ecn = (struct sctp_ecn_supported_param *)((caddr_t)ali + + sizeof(*ali)); + } else { + ecn = (struct sctp_ecn_supported_param*)( + (caddr_t)initackm_out + sizeof(*initackm_out)); + } + + /* ECN parameter */ + if (sctp_ecn == 1) { + ecn->ph.param_type = htons(SCTP_ECN_CAPABLE); + ecn->ph.param_length = htons(sizeof(*ecn)); + m->m_len += sizeof(*ecn); + + prsctp = (struct sctp_prsctp_supported_param *)((caddr_t)ecn + + sizeof(*ecn)); + } else { + prsctp = (struct sctp_prsctp_supported_param *)((caddr_t)ecn); + } + /* And now tell the peer we do pr-sctp */ + prsctp->ph.param_type = htons(SCTP_PRSCTP_SUPPORTED); + prsctp->ph.param_length = htons(sizeof(*prsctp)); + m->m_len += sizeof(*prsctp); + + + /* And now tell the peer we do all the extensions */ + pr_supported = (struct sctp_supported_chunk_types_param *)((caddr_t)prsctp + + sizeof(*prsctp)); + + pr_supported->ph.param_type = htons(SCTP_SUPPORTED_CHUNK_EXT); + pr_supported->ph.param_length = htons(sizeof(*pr_supported) + SCTP_EXT_COUNT); + pr_supported->chunk_types[0] = SCTP_ASCONF; + pr_supported->chunk_types[1] = SCTP_ASCONF_ACK; + pr_supported->chunk_types[2] = SCTP_FORWARD_CUM_TSN; + pr_supported->chunk_types[3] = SCTP_PACKET_DROPPED; + pr_supported->chunk_types[4] = SCTP_STREAM_RESET; + pr_supported->chunk_types[5] = 0; /* pad */ + pr_supported->chunk_types[6] = 0; /* pad */ + pr_supported->chunk_types[7] = 0; /* pad */ + + m->m_len += (sizeof(*pr_supported) + SCTP_EXT_COUNT + SCTP_PAD_EXT_COUNT); + if (sctp_ecn_nonce) { + /* ECN nonce: And now tell the peer we support ECN nonce */ + ecn_nonce = (struct sctp_ecn_nonce_supported_param *)((caddr_t)pr_supported + + sizeof(*pr_supported) + SCTP_EXT_COUNT + SCTP_PAD_EXT_COUNT); + ecn_nonce->ph.param_type = htons(SCTP_ECN_NONCE_SUPPORTED); + ecn_nonce->ph.param_length = htons(sizeof(*ecn_nonce)); + m->m_len += sizeof(*ecn_nonce); + } + + m_at = m; + /* now the addresses */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + struct ifnet *ifn; + struct ifaddr *ifa; + int cnt = cnt_inits_to; + + TAILQ_FOREACH(ifn, &ifnet, if_list) { + if ((stc.loopback_scope == 0) && + (ifn->if_type == IFT_LOOP)) { + /* + * Skip loopback devices if loopback_scope + * not set + */ + continue; + } + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (sctp_is_address_in_scope(ifa, + stc.ipv4_addr_legal, stc.ipv6_addr_legal, + stc.loopback_scope, stc.ipv4_scope, + stc.local_scope, stc.site_scope) == 0) { + continue; + } + cnt++; + } + } + if (cnt > 1) { + TAILQ_FOREACH(ifn, &ifnet, if_list) { + if ((stc.loopback_scope == 0) && + (ifn->if_type == IFT_LOOP)) { + /* + * Skip loopback devices if + * loopback_scope not set + */ + continue; + } + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (sctp_is_address_in_scope(ifa, + stc.ipv4_addr_legal, + stc.ipv6_addr_legal, + stc.loopback_scope, stc.ipv4_scope, + stc.local_scope, stc.site_scope) == 0) { + continue; + } + m_at = sctp_add_addr_to_mbuf(m_at, ifa); + } + } + } + } else { + struct sctp_laddr *laddr; + int cnt; + cnt = cnt_inits_to; + /* First, how many ? */ + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == NULL) { + continue; + } + if (laddr->ifa->ifa_addr == NULL) + continue; + if (sctp_is_address_in_scope(laddr->ifa, + stc.ipv4_addr_legal, stc.ipv6_addr_legal, + stc.loopback_scope, stc.ipv4_scope, + stc.local_scope, stc.site_scope) == 0) { + continue; + } + cnt++; + } + /* If we bind a single address only we won't list + * any. This way you can get through a NAT + */ + if (cnt > 1) { + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Help I have fallen and I can't get up!\n"); + } +#endif + continue; + } + if (laddr->ifa->ifa_addr == NULL) + continue; + if (sctp_is_address_in_scope(laddr->ifa, + stc.ipv4_addr_legal, stc.ipv6_addr_legal, + stc.loopback_scope, stc.ipv4_scope, + stc.local_scope, stc.site_scope) == 0) { + continue; + } + m_at = sctp_add_addr_to_mbuf(m_at, laddr->ifa); + } + } + } + + /* tack on the operational error if present */ + if (op_err) { + if (op_err->m_pkthdr.len % 4) { + /* must add a pad to the param */ + u_int32_t cpthis=0; + int padlen; + padlen = 4 - (op_err->m_pkthdr.len % 4); + m_copyback(op_err, op_err->m_pkthdr.len, padlen, (caddr_t)&cpthis); + } + while (m_at->m_next != NULL) { + m_at = m_at->m_next; + } + m_at->m_next = op_err; + while (m_at->m_next != NULL) { + m_at = m_at->m_next; + } + } + /* Get total size of init packet */ + sz_of = SCTP_SIZE32(ntohs(init_chk->ch.chunk_length)); + /* pre-calulate the size and update pkt header and chunk header */ + m->m_pkthdr.len = 0; + for (m_tmp = m; m_tmp; m_tmp = m_tmp->m_next) { + m->m_pkthdr.len += m_tmp->m_len; + if (m_tmp->m_next == NULL) { + /* m_tmp should now point to last one */ + break; + } + } + /* + * Figure now the size of the cookie. We know the size of the + * INIT-ACK. The Cookie is going to be the size of INIT, INIT-ACK, + * COOKIE-STRUCTURE and SIGNATURE. + */ + + /* + * take our earlier INIT calc and add in the sz we just calculated + * minus the size of the sctphdr (its not included in chunk size + */ + + /* add once for the INIT-ACK */ + sz_of += (m->m_pkthdr.len - sizeof(struct sctphdr)); + + /* add a second time for the INIT-ACK in the cookie */ + sz_of += (m->m_pkthdr.len - sizeof(struct sctphdr)); + + /* Now add the cookie header and cookie message struct */ + sz_of += sizeof(struct sctp_state_cookie_param); + /* ...and add the size of our signature */ + sz_of += SCTP_SIGNATURE_SIZE; + initackm_out->msg.ch.chunk_length = htons(sz_of); + + /* Now we must build a cookie */ + m_cookie = sctp_add_cookie(inp, init_pkt, offset, m, + sizeof(struct sctphdr), &stc); + if (m_cookie == NULL) { + /* memory problem */ + sctp_m_freem(m); + return; + } + /* Now append the cookie to the end and update the space/size */ + m_tmp->m_next = m_cookie; + + /* + * We pass 0 here to NOT set IP_DF if its IPv4, we ignore the + * return here since the timer will drive a retranmission. + */ + padval = m->m_pkthdr.len % 4; + if ((padval) && (m_last)) { + /* see my previous comments on m_last */ + int ret; + ret = sctp_add_pad_tombuf(m_last, (4-padval)); + if (ret) { + /* Houston we have a problem, no space */ + sctp_m_freem(m); + return; + } + m->m_pkthdr.len += padval; + } + sctp_lowlevel_chunk_output(inp, NULL, NULL, to, m, 0, 0, NULL, 0); +} + + +static void +sctp_insert_on_wheel(struct sctp_association *asoc, + struct sctp_stream_out *strq) +{ + struct sctp_stream_out *stre, *strn; + stre = TAILQ_FIRST(&asoc->out_wheel); + if (stre == NULL) { + /* only one on wheel */ + TAILQ_INSERT_HEAD(&asoc->out_wheel, strq, next_spoke); + return; + } + for (; stre; stre = strn) { + strn = TAILQ_NEXT(stre, next_spoke); + if (stre->stream_no > strq->stream_no) { + TAILQ_INSERT_BEFORE(stre, strq, next_spoke); + return; + } else if (stre->stream_no == strq->stream_no) { + /* huh, should not happen */ + return; + } else if (strn == NULL) { + /* next one is null */ + TAILQ_INSERT_AFTER(&asoc->out_wheel, stre, strq, + next_spoke); + } + } +} + +static void +sctp_remove_from_wheel(struct sctp_association *asoc, + struct sctp_stream_out *strq) +{ + /* take off and then setup so we know it is not on the wheel */ + TAILQ_REMOVE(&asoc->out_wheel, strq, next_spoke); + strq->next_spoke.tqe_next = NULL; + strq->next_spoke.tqe_prev = NULL; +} + + +static void +sctp_prune_prsctp(struct sctp_tcb *stcb, + struct sctp_association *asoc, + struct sctp_sndrcvinfo *srcv, + int dataout + ) +{ + int freed_spc=0; + struct sctp_tmit_chunk *chk, *nchk; + if ((asoc->peer_supports_prsctp) && (asoc->sent_queue_cnt_removeable > 0)) { + TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { + /* + * Look for chunks marked with the PR_SCTP + * flag AND the buffer space flag. If the one + * being sent is equal or greater priority then + * purge the old one and free some space. + */ + if ((chk->flags & (SCTP_PR_SCTP_ENABLED | + SCTP_PR_SCTP_BUFFER)) == + (SCTP_PR_SCTP_ENABLED|SCTP_PR_SCTP_BUFFER)) { + /* + * This one is PR-SCTP AND buffer space + * limited type + */ + if (chk->rec.data.timetodrop.tv_sec >= (long)srcv->sinfo_timetolive) { + /* Lower numbers equates to + * higher priority so if the + * one we are looking at has a + * larger or equal priority we + * want to drop the data and + * NOT retransmit it. + */ + if (chk->data) { + /* We release the + * book_size if the + * mbuf is here + */ + int ret_spc; + int cause; + if (chk->sent > SCTP_DATAGRAM_UNSENT) + cause = SCTP_RESPONSE_TO_USER_REQ|SCTP_NOTIFY_DATAGRAM_SENT; + else + cause = SCTP_RESPONSE_TO_USER_REQ|SCTP_NOTIFY_DATAGRAM_UNSENT; + ret_spc = sctp_release_pr_sctp_chunk(stcb, chk, + cause, + &asoc->sent_queue); + freed_spc += ret_spc; + if (freed_spc >= dataout) { + return; + } + } /* if chunk was present */ + } /* if of sufficent priority */ + } /* if chunk has enabled */ + } /* tailqforeach */ + + chk = TAILQ_FIRST(&asoc->send_queue); + while (chk) { + nchk = TAILQ_NEXT(chk, sctp_next); + /* Here we must move to the sent queue and mark */ + if ((chk->flags & (SCTP_PR_SCTP_ENABLED | + SCTP_PR_SCTP_BUFFER)) == + (SCTP_PR_SCTP_ENABLED|SCTP_PR_SCTP_BUFFER)) { + if (chk->rec.data.timetodrop.tv_sec >= (long)srcv->sinfo_timetolive) { + if (chk->data) { + /* We release the + * book_size if the + * mbuf is here + */ + int ret_spc; + ret_spc = sctp_release_pr_sctp_chunk(stcb, chk, + SCTP_RESPONSE_TO_USER_REQ|SCTP_NOTIFY_DATAGRAM_UNSENT, + &asoc->send_queue); + + freed_spc += ret_spc; + if (freed_spc >= dataout) { + return; + } + } /* end if chk->data */ + } /* end if right class */ + } /* end if chk pr-sctp */ + chk = nchk; + } /* end while (chk) */ + } /* if enabled in asoc */ +} + +static void +sctp_prepare_chunk(struct sctp_tmit_chunk *template, + struct sctp_tcb *stcb, + struct sctp_sndrcvinfo *srcv, + struct sctp_stream_out *strq, + struct sctp_nets *net) +{ + bzero(template, sizeof(struct sctp_tmit_chunk)); + template->sent = SCTP_DATAGRAM_UNSENT; + if ((stcb->asoc.peer_supports_prsctp) && + (srcv->sinfo_flags & (MSG_PR_SCTP_TTL|MSG_PR_SCTP_BUF)) && + (srcv->sinfo_timetolive > 0) + ) { + /* If: + * Peer supports PR-SCTP + * The flags is set against this send for PR-SCTP + * And timetolive is a postive value, zero is reserved + * to mean a reliable send for both buffer/time + * related one. + */ + if (srcv->sinfo_flags & MSG_PR_SCTP_BUF) { + /* + * Time to live is a priority stored in tv_sec + * when doing the buffer drop thing. + */ + template->rec.data.timetodrop.tv_sec = srcv->sinfo_timetolive; + } else { + struct timeval tv; + + SCTP_GETTIME_TIMEVAL(&template->rec.data.timetodrop); + tv.tv_sec = srcv->sinfo_timetolive / 1000; + tv.tv_usec = (srcv->sinfo_timetolive * 1000) % 1000000; +#ifndef __FreeBSD__ + timeradd(&template->rec.data.timetodrop, &tv, + &template->rec.data.timetodrop); +#else + timevaladd(&template->rec.data.timetodrop, &tv); +#endif + } + } + if ((srcv->sinfo_flags & MSG_UNORDERED) == 0) { + template->rec.data.stream_seq = strq->next_sequence_sent; + } else { + template->rec.data.stream_seq = 0; + } + template->rec.data.TSN_seq = 0; /* not yet assigned */ + + template->rec.data.stream_number = srcv->sinfo_stream; + template->rec.data.payloadtype = srcv->sinfo_ppid; + template->rec.data.context = srcv->sinfo_context; + template->rec.data.doing_fast_retransmit = 0; + template->rec.data.ect_nonce = 0; /* ECN Nonce */ + + if (srcv->sinfo_flags & MSG_ADDR_OVER) { + template->whoTo = net; + } else { + if (stcb->asoc.primary_destination) + template->whoTo = stcb->asoc.primary_destination; + else { + /* TSNH */ + template->whoTo = net; + } + } + /* the actual chunk flags */ + if (srcv->sinfo_flags & MSG_UNORDERED) { + template->rec.data.rcv_flags = SCTP_DATA_UNORDERED; + } else { + template->rec.data.rcv_flags = 0; + } + /* no flags yet, FRAGMENT_OK goes here */ + template->flags = 0; + /* PR sctp flags */ + if (stcb->asoc.peer_supports_prsctp) { + if (srcv->sinfo_timetolive > 0) { + /* + * We only set the flag if timetolive (or + * priority) was set to a positive number. + * Zero is reserved specifically to be + * EXCLUDED and sent reliable. + */ + if (srcv->sinfo_flags & MSG_PR_SCTP_TTL) { + template->flags |= SCTP_PR_SCTP_ENABLED; + } + if (srcv->sinfo_flags & MSG_PR_SCTP_BUF) { + template->flags |= SCTP_PR_SCTP_BUFFER; + } + } + } + template->asoc = &stcb->asoc; +} + + +int +sctp_get_frag_point(struct sctp_tcb *stcb, + struct sctp_association *asoc) +{ + int siz, ovh; + + /* For endpoints that have both 6 and 4 addresses + * we must reserver room for the 6 ip header, for + * those that are only dealing with V4 we use + * a larger frag point. + */ + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + ovh = SCTP_MED_OVERHEAD; + } else { + ovh = SCTP_MED_V4_OVERHEAD; + } + + if (stcb->sctp_ep->sctp_frag_point > asoc->smallest_mtu) + siz = asoc->smallest_mtu - ovh; + else + siz = (stcb->sctp_ep->sctp_frag_point - ovh); +/* + if (siz > (MCLBYTES-sizeof(struct sctp_data_chunk))) { */ + /* A data chunk MUST fit in a cluster */ +/* siz = (MCLBYTES - sizeof(struct sctp_data_chunk));*/ +/* }*/ + + if (siz % 4) { + /* make it an even word boundary please */ + siz -= (siz % 4); + } + return (siz); +} +extern unsigned int sctp_max_chunks_on_queue; + +#define SBLOCKWAIT(f) (((f)&MSG_DONTWAIT) ? M_NOWAIT : M_WAITOK) + +static int +sctp_msg_append(struct sctp_tcb *stcb, + struct sctp_nets *net, + struct mbuf *m, + struct sctp_sndrcvinfo *srcv, + int flags) +{ + struct socket *so; + struct sctp_association *asoc; + struct sctp_stream_out *strq; + struct sctp_tmit_chunk *chk; + struct sctpchunk_listhead tmp; + struct sctp_tmit_chunk template; + struct mbuf *n, *mnext; + struct mbuf *mm; + unsigned int dataout, siz; + int mbcnt = 0; + int mbcnt_e = 0; + int error = 0; + + if ((stcb == NULL) || (net == NULL) || (m == NULL) || (srcv == NULL)) { + /* Software fault, you blew it on the call */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("software error in sctp_msg_append:1\n"); + printf("stcb:%p net:%p m:%p srcv:%p\n", + stcb, net, m, srcv); + } +#endif + if (m) + sctp_m_freem(m); + return (EFAULT); + } + so = stcb->sctp_socket; + asoc = &stcb->asoc; + if (srcv->sinfo_flags & MSG_ABORT) { + if ((SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_WAIT) && + (SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_ECHOED)) { + /* It has to be up before we abort */ + /* how big is the user initiated abort? */ + if ((m->m_flags & M_PKTHDR) && (m->m_pkthdr.len)) { + dataout = m->m_pkthdr.len; + } else { + /* we must count */ + dataout = 0; + for (n = m; n; n = n->m_next) { + dataout += n->m_len; + } + } + M_PREPEND(m, sizeof(struct sctp_paramhdr), M_DONTWAIT); + if (m) { + struct sctp_paramhdr *ph; + m->m_len = sizeof(struct sctp_paramhdr) + dataout; + ph = mtod(m, struct sctp_paramhdr *); + ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT); + ph->param_length = htons(m->m_len); + } + sctp_abort_an_association(stcb->sctp_ep, stcb, SCTP_RESPONSE_TO_USER_REQ, m); + m = NULL; + } else { + /* Only free if we don't send an abort */ + ; + } + goto out; + } + if ((SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) || + (asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) { + /* got data while shutting down */ + error = ECONNRESET; + goto out; + } + + if (srcv->sinfo_stream >= asoc->streamoutcnt) { + /* Invalid stream number */ + error = EINVAL; + goto out; + } + if (asoc->strmout == NULL) { + /* huh? software error */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("software error in sctp_msg_append:2\n"); + } +#endif + error = EFAULT; + goto out; + } + strq = &asoc->strmout[srcv->sinfo_stream]; + /* how big is it ? */ + if ((m->m_flags & M_PKTHDR) && (m->m_pkthdr.len)) { + dataout = m->m_pkthdr.len; + } else { + /* we must count */ + dataout = 0; + for (n = m; n; n = n->m_next) { + dataout += n->m_len; + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Attempt to send out %d bytes\n", + dataout); + } +#endif + + /* lock the socket buf */ + SOCKBUF_LOCK(&so->so_snd); + error = sblock(&so->so_snd, SBLOCKWAIT(flags)); + if (error) + goto out_locked; + + if (dataout > so->so_snd.sb_hiwat) { + /* It will NEVER fit */ + error = EMSGSIZE; + goto release; + } + if ((srcv->sinfo_flags & MSG_EOF) && + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) && + (dataout == 0) + ) { + goto zap_by_it_all; + } + if ((so->so_snd.sb_hiwat < + (dataout + asoc->total_output_queue_size)) || + (asoc->chunks_on_out_queue > sctp_max_chunks_on_queue) || + (asoc->total_output_mbuf_queue_size > + so->so_snd.sb_mbmax) + ) { + /* XXX Buffer space hunt for data to skip */ + if (asoc->peer_supports_prsctp) { + sctp_prune_prsctp(stcb, asoc, srcv, dataout); + } + while ((so->so_snd.sb_hiwat < + (dataout + asoc->total_output_queue_size)) || + (asoc->chunks_on_out_queue > sctp_max_chunks_on_queue) || + (asoc->total_output_mbuf_queue_size > + so->so_snd.sb_mbmax)) { + struct sctp_inpcb *inp; + /* Now did we free up enough room? */ + if ((so->so_state & SS_NBIO) +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + || (flags & MSG_NBIO) +#endif + ) { + /* Non-blocking io in place */ + error = EWOULDBLOCK; + goto release; + } + /* + * We store off a pointer to the endpoint. + * Since on return from this we must check to + * see if an so_error is set. If so we may have + * been reset and our stcb destroyed. Returning + * an error will cause the correct error return + * through and fix this all. + */ + inp = stcb->sctp_ep; + /* + * Not sure how else to do this since + * the level we suspended at is not + * known deep down where we are. I will + * drop to spl0() so that others can + * get in. + */ + + inp->sctp_tcb_at_block = (void *)stcb; + inp->error_on_block = 0; + sbunlock(&so->so_snd); + error = sbwait(&so->so_snd); + /* + * XXX: This is ugly but I have + * recreated most of what goes on to + * block in the sb. UGHH + * May want to add the bit about being + * no longer connected.. but this then + * further dooms the UDP model NOT to + * allow this. + */ + inp->sctp_tcb_at_block = 0; + if (inp->error_on_block) + error = inp->error_on_block; + if (so->so_error) + error = so->so_error; + if (error) { + goto out_locked; + } + error = sblock(&so->so_snd, M_WAITOK); + if (error) + goto out_locked; + /* Otherwise we cycle back and recheck + * the space + */ +#if defined(__FreeBSD__) && __FreeBSD_version >= 502115 + if (so->so_rcv.sb_state & SBS_CANTSENDMORE) { +#else + if (so->so_state & SS_CANTSENDMORE) { +#endif + error = EPIPE; + goto release; + } + if (so->so_error) { + error = so->so_error; + goto release; + } + } + } + /* If we have a packet header fix it if it was broke */ + if (m->m_flags & M_PKTHDR) { + m->m_pkthdr.len = dataout; + } + /* use the smallest one, user set value or + * smallest mtu of the asoc + */ + siz = sctp_get_frag_point(stcb, asoc); + SOCKBUF_UNLOCK(&so->so_snd); + if ((dataout) && (dataout <= siz)) { + /* Fast path */ + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + error = ENOMEM; + SOCKBUF_LOCK(&so->so_snd); + goto release; + } + sctp_prepare_chunk(chk, stcb, srcv, strq, net); + chk->whoTo->ref_count++; + chk->rec.data.rcv_flags |= SCTP_DATA_NOT_FRAG; + + /* no flags yet, FRAGMENT_OK goes here */ + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + asoc->chunks_on_out_queue++; + chk->data = m; + m = NULL; + /* Total in the MSIZE */ + for (mm = chk->data; mm; mm = mm->m_next) { + mbcnt += MSIZE; + if (mm->m_flags & M_EXT) { + mbcnt += chk->data->m_ext.ext_size; + } + } + /* fix up the send_size if it is not present */ + chk->send_size = dataout; + chk->book_size = chk->send_size; + chk->mbcnt = mbcnt; + /* ok, we are commited */ + if ((srcv->sinfo_flags & MSG_UNORDERED) == 0) { + /* bump the ssn if we are unordered. */ + strq->next_sequence_sent++; + } + chk->data->m_nextpkt = 0; + asoc->stream_queue_cnt++; + TAILQ_INSERT_TAIL(&strq->outqueue, chk, sctp_next); + /* now check if this stream is on the wheel */ + if ((strq->next_spoke.tqe_next == NULL) && + (strq->next_spoke.tqe_prev == NULL)) { + /* Insert it on the wheel since it is not + * on it currently + */ + sctp_insert_on_wheel(asoc, strq); + } + } else if ((dataout) && (dataout > siz)) { + /* Slow path */ + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NO_FRAGMENT) && + (dataout > siz)) { + error = EMSGSIZE; + SOCKBUF_LOCK(&so->so_snd); + goto release; + } + /* setup the template */ + sctp_prepare_chunk(&template, stcb, srcv, strq, net); + + n = m; + while (dataout > siz) { + /* + * We can wait since this is called from the user + * send side + */ + n->m_nextpkt = m_split(n, siz, M_WAIT); + if (n->m_nextpkt == NULL) { + error = EFAULT; + SOCKBUF_LOCK(&so->so_snd); + goto release; + } + dataout -= siz; + n = n->m_nextpkt; + } + /* + * ok, now we have a chain on m where m->m_nextpkt points to + * the next chunk and m/m->m_next chain is the piece to send. + * We must go through the chains and thread them on to + * sctp_tmit_chunk chains and place them all on the stream + * queue, breaking the m->m_nextpkt pointers as we go. + */ + n = m; + TAILQ_INIT(&tmp); + while (n) { + /* + * first go through and allocate a sctp_tmit chunk + * for each chunk piece + */ + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* + * ok we must spin through and dump anything + * we have allocated and then jump to the + * no_membad + */ + chk = TAILQ_FIRST(&tmp); + while (chk) { + TAILQ_REMOVE(&tmp, chk, sctp_next); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + asoc->chunks_on_out_queue--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&tmp); + } + error = ENOMEM; + SOCKBUF_LOCK(&so->so_snd); + goto release; + } + sctppcbinfo.ipi_count_chunk++; + asoc->chunks_on_out_queue++; + + sctppcbinfo.ipi_gencnt_chunk++; + *chk = template; + chk->whoTo->ref_count++; + chk->data = n; + /* Total in the MSIZE */ + mbcnt_e = 0; + for (mm = chk->data; mm; mm = mm->m_next) { + mbcnt_e += MSIZE; + if (mm->m_flags & M_EXT) { + mbcnt_e += chk->data->m_ext.ext_size; + } + } + /* now fix the chk->send_size */ + if (chk->data->m_flags & M_PKTHDR) { + chk->send_size = chk->data->m_pkthdr.len; + } else { + struct mbuf *nn; + chk->send_size = 0; + for (nn = chk->data; nn; nn = nn->m_next) { + chk->send_size += nn->m_len; + } + } + chk->book_size = chk->send_size; + chk->mbcnt = mbcnt_e; + mbcnt += mbcnt_e; + if (chk->flags & SCTP_PR_SCTP_BUFFER) { + asoc->sent_queue_cnt_removeable++; + } + n = n->m_nextpkt; + TAILQ_INSERT_TAIL(&tmp, chk, sctp_next); + } + m = NULL; + /* now that we have enough space for all de-couple the + * chain of mbufs by going through our temp array + * and breaking the pointers. + */ + /* ok, we are commited */ + if ((srcv->sinfo_flags & MSG_UNORDERED) == 0) { + /* bump the ssn if we are unordered. */ + strq->next_sequence_sent++; + } + /* Mark the first/last flags. This will + * result int a 3 for a single item on the list + */ + chk = TAILQ_FIRST(&tmp); + chk->rec.data.rcv_flags |= SCTP_DATA_FIRST_FRAG; + chk = TAILQ_LAST(&tmp, sctpchunk_listhead); + chk->rec.data.rcv_flags |= SCTP_DATA_LAST_FRAG; + /* now break any chains on the queue and + * move it to the streams actual queue. + */ + chk = TAILQ_FIRST(&tmp); + while (chk) { + chk->data->m_nextpkt = 0; + TAILQ_REMOVE(&tmp, chk, sctp_next); + asoc->stream_queue_cnt++; + TAILQ_INSERT_TAIL(&strq->outqueue, chk, sctp_next); + chk = TAILQ_FIRST(&tmp); + } + /* now check if this stream is on the wheel */ + if ((strq->next_spoke.tqe_next == NULL) && + (strq->next_spoke.tqe_prev == NULL)) { + /* Insert it on the wheel since it is not + * on it currently + */ + sctp_insert_on_wheel(asoc, strq); + } + } + SOCKBUF_LOCK(&so->so_snd); + /* has a SHUTDOWN been (also) requested by the user on this asoc? */ +zap_by_it_all: + + if ((srcv->sinfo_flags & MSG_EOF) && + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE)) { + + int some_on_streamwheel = 0; + + if (!TAILQ_EMPTY(&asoc->out_wheel)) { + /* Check to see if some data queued */ + struct sctp_stream_out *outs; + TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) { + if (!TAILQ_EMPTY(&outs->outqueue)) { + some_on_streamwheel = 1; + break; + } + } + } + + if (TAILQ_EMPTY(&asoc->send_queue) && + TAILQ_EMPTY(&asoc->sent_queue) && + (some_on_streamwheel == 0)) { + /* there is nothing queued to send, so I'm done... */ + if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) && + (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { + /* only send SHUTDOWN the first time through */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("%s:%d sends a shutdown\n", + __FILE__, + __LINE__ + ); + } +#endif + sctp_send_shutdown(stcb, stcb->asoc.primary_destination); + asoc->state = SCTP_STATE_SHUTDOWN_SENT; + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, + asoc->primary_destination); + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, + asoc->primary_destination); + } + } else { + /* + * we still got (or just got) data to send, so set + * SHUTDOWN_PENDING + */ + /* + * XXX sockets draft says that MSG_EOF should be sent + * with no data. currently, we will allow user data + * to be sent first and move to SHUTDOWN-PENDING + */ + asoc->state |= SCTP_STATE_SHUTDOWN_PENDING; + } + } +#ifdef SCTP_MBCNT_LOGGING + sctp_log_mbcnt(SCTP_LOG_MBCNT_INCREASE, + asoc->total_output_queue_size, + dataout, + asoc->total_output_mbuf_queue_size, + mbcnt); +#endif + asoc->total_output_queue_size += dataout; + asoc->total_output_mbuf_queue_size += mbcnt; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + so->so_snd.sb_cc += dataout; + so->so_snd.sb_mbcnt += mbcnt; + } + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { + printf("++total out:%d total_mbuf_out:%d\n", + (int)asoc->total_output_queue_size, + (int)asoc->total_output_mbuf_queue_size); + } +#endif + +release: + sbunlock(&so->so_snd); +out_locked: + SOCKBUF_UNLOCK(&so->so_snd); +out: + if (m && m->m_nextpkt) { + n = m; + while (n) { + mnext = n->m_nextpkt; + n->m_nextpkt = NULL; + sctp_m_freem(n); + n = mnext; + } + } else if (m) + sctp_m_freem(m); + + return (error); +} + +static struct mbuf * +sctp_copy_mbufchain(struct mbuf *clonechain, + struct mbuf *outchain) +{ + struct mbuf *appendchain; +#if defined(__FreeBSD__) || defined(__NetBSD__) + /* Supposedly m_copypacket is an optimization, use it if we can */ + if (clonechain->m_flags & M_PKTHDR) { + appendchain = m_copypacket(clonechain, M_DONTWAIT); + sctp_pegs[SCTP_CACHED_SRC]++; + } else + appendchain = m_copy(clonechain, 0, M_COPYALL); +#elif defined(__APPLE__) + appendchain = sctp_m_copym(clonechain, 0, M_COPYALL, M_DONTWAIT); +#else + appendchain = m_copy(clonechain, 0, M_COPYALL); +#endif + + if (appendchain == NULL) { + /* error */ + if (outchain) + sctp_m_freem(outchain); + return (NULL); + } + if (outchain) { + /* tack on to the end */ + struct mbuf *m; + m = outchain; + while (m) { + if (m->m_next == NULL) { + m->m_next = appendchain; + break; + } + m = m->m_next; + } + if (outchain->m_flags & M_PKTHDR) { + int append_tot; + struct mbuf *t; + t = appendchain; + append_tot = 0; + while (t) { + append_tot += t->m_len; + t = t->m_next; + } + outchain->m_pkthdr.len += append_tot; + } + return (outchain); + } else { + return (appendchain); + } +} + +static void +sctp_sendall_iterator(struct sctp_inpcb *inp, struct sctp_tcb *stcb, void *ptr, u_int32_t val) +{ + struct sctp_copy_all *ca; + struct mbuf *m; + int turned_on_nonblock=0, ret; + + ca = (struct sctp_copy_all *)ptr; + if (ca->m == NULL) { + return; + } + if (ca->inp != inp) { + /* TSNH */ + return; + } + m = sctp_copy_mbufchain(ca->m, NULL); + if (m == NULL) { + /* can't copy so we are done */ + ca->cnt_failed++; + return; + } + if ((stcb->sctp_socket->so_state & SS_NBIO) == 0) { + /* we have to do this non-blocking */ + turned_on_nonblock = 1; + stcb->sctp_socket->so_state |= SS_NBIO; + } + ret = sctp_msg_append(stcb, stcb->asoc.primary_destination, m, &ca->sndrcv, 0); + if (turned_on_nonblock) { + /* we turned on non-blocking so turn it off */ + stcb->sctp_socket->so_state &= ~SS_NBIO; + } + if (ret) { + ca->cnt_failed++; + } else { + ca->cnt_sent++; + } +} + +static void +sctp_sendall_completes(void *ptr, u_int32_t val) +{ + struct sctp_copy_all *ca; + ca = (struct sctp_copy_all *)ptr; + /* Do a notify here? + * Kacheong suggests that the notify + * be done at the send time.. so you would + * push up a notification if any send failed. + * Don't know if this is feasable since the + * only failures we have is "memory" related and + * if you cannot get an mbuf to send the data + * you surely can't get an mbuf to send up + * to notify the user you can't send the data :-> + */ + + /* now free everything */ + m_freem(ca->m); + FREE(ca, M_PCB); +} + + +#define MC_ALIGN(m, len) do { \ + (m)->m_data += (MCLBYTES - (len)) & ~(sizeof(long) - 1); \ +} while (0) + + + +static struct mbuf * +sctp_copy_out_all(struct uio *uio, int len) +{ + struct mbuf *ret, *at; + int left, willcpy, cancpy, error; + + MGETHDR(ret, M_WAIT, MT_HEADER); + if (ret == NULL) { + /* TSNH */ + return (NULL); + } + left = len; + ret->m_len = 0; + ret->m_pkthdr.len = len; + MCLGET(ret, M_WAIT); + if (ret == NULL) { + return (NULL); + } + if ((ret->m_flags & M_EXT) == 0) { + m_freem (ret); + return (NULL); + } + cancpy = M_TRAILINGSPACE(ret); + willcpy = min(cancpy, left); + at = ret; + while (left > 0) { + /* Align data to the end */ + MC_ALIGN(at, willcpy); + error = uiomove(mtod(at, caddr_t), willcpy, uio); + if (error) { + err_out_now: + m_freem(ret); + return (NULL); + } + at->m_len = willcpy; + at->m_nextpkt = at->m_next = 0; + left -= willcpy; + if (left > 0) { + MGET(at->m_next, M_WAIT, MT_DATA); + if (at->m_next == NULL) { + goto err_out_now; + } + at = at->m_next; + at->m_len = 0; + MCLGET(at, M_WAIT); + if (at == NULL) { + goto err_out_now; + } + if ((at->m_flags & M_EXT) == 0) { + goto err_out_now; + } + cancpy = M_TRAILINGSPACE(at); + willcpy = min(cancpy, left); + } + } + return (ret); +} + +static int +sctp_sendall (struct sctp_inpcb *inp, struct uio *uio, struct mbuf *m, struct sctp_sndrcvinfo *srcv) +{ + int ret; + struct sctp_copy_all *ca; + MALLOC(ca, struct sctp_copy_all *, + sizeof(struct sctp_copy_all), M_PCB, M_WAIT); + if (ca == NULL) { + m_freem(m); + return (ENOMEM); + } + memset (ca, 0, sizeof(struct sctp_copy_all)); + + ca->inp = inp; + ca->sndrcv = *srcv; + /* take off the sendall flag, it would + * be bad if we failed to do this :-0 + */ + ca->sndrcv.sinfo_flags &= ~MSG_SENDALL; + + /* get length and mbuf chain */ + if (uio) { + ca->sndlen = uio->uio_resid; + ca->m = sctp_copy_out_all(uio, ca->sndlen); + if (ca->m == NULL) { + FREE(ca, M_PCB); + return (ENOMEM); + } + } else { + if ((m->m_flags & M_PKTHDR) == 0) { + struct mbuf *mat; + mat = m; + ca->sndlen = 0; + while(m) { + ca->sndlen += m->m_len; + m = m->m_next; + } + } else { + ca->sndlen = m->m_pkthdr.len; + } + ca->m = m; + } + + ret = sctp_initiate_iterator(sctp_sendall_iterator, SCTP_PCB_ANY_FLAGS, SCTP_ASOC_ANY_STATE, + (void *)ca, 0, sctp_sendall_completes, inp); + if (ret) { +#ifdef SCTP_DEBUG + printf("Failed to initate iterator to takeover associations\n"); +#endif + FREE(ca, M_PCB); + return (EFAULT); + + } + return (0); +} + + +void +sctp_toss_old_cookies(struct sctp_association *asoc) +{ + struct sctp_tmit_chunk *chk, *nchk; + chk = TAILQ_FIRST(&asoc->control_send_queue); + while (chk) { + nchk = TAILQ_NEXT(chk, sctp_next); + if (chk->rec.chunk_id == SCTP_COOKIE_ECHO) { + TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + asoc->ctrl_queue_cnt--; + if (chk->whoTo) + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + } + chk = nchk; + } +} + +void +sctp_toss_old_asconf(struct sctp_tcb *stcb) +{ + struct sctp_association *asoc; + struct sctp_tmit_chunk *chk, *chk_tmp; + + asoc = &stcb->asoc; + for (chk = TAILQ_FIRST(&asoc->control_send_queue); chk != NULL; + chk = chk_tmp) { + /* get next chk */ + chk_tmp = TAILQ_NEXT(chk, sctp_next); + /* find SCTP_ASCONF chunk in queue (only one ever in queue) */ + if (chk->rec.chunk_id == SCTP_ASCONF) { + TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + asoc->ctrl_queue_cnt--; + if (chk->whoTo) + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + } + } +} + + +static void +sctp_clean_up_datalist(struct sctp_tcb *stcb, + struct sctp_association *asoc, + struct sctp_tmit_chunk **data_list, + int bundle_at, + struct sctp_nets *net) +{ + int i; + for (i = 0; i < bundle_at; i++) { + /* off of the send queue */ + if (i) { + /* Any chunk NOT 0 you zap the time + * chunk 0 gets zapped or set based on + * if a RTO measurment is needed. + */ + data_list[i]->do_rtt = 0; + } + /* record time */ + data_list[i]->sent_rcv_time = net->last_sent_time; + TAILQ_REMOVE(&asoc->send_queue, + data_list[i], + sctp_next); + /* on to the sent queue */ + TAILQ_INSERT_TAIL(&asoc->sent_queue, + data_list[i], + sctp_next); + /* This does not lower until the cum-ack passes it */ + asoc->sent_queue_cnt++; + asoc->send_queue_cnt--; + if ((asoc->peers_rwnd <= 0) && + (asoc->total_flight == 0) && + (bundle_at == 1)) { + /* Mark the chunk as being a window probe */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("WINDOW PROBE SET\n"); + } +#endif + sctp_pegs[SCTP_WINDOW_PROBES]++; + data_list[i]->rec.data.state_flags |= SCTP_WINDOW_PROBE; + } else { + data_list[i]->rec.data.state_flags &= ~SCTP_WINDOW_PROBE; + } +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xC2, 3); +#endif + data_list[i]->sent = SCTP_DATAGRAM_SENT; + data_list[i]->snd_count = 1; + net->flight_size += data_list[i]->book_size; + asoc->total_flight += data_list[i]->book_size; + asoc->total_flight_count++; +#ifdef SCTP_LOG_RWND + sctp_log_rwnd(SCTP_DECREASE_PEER_RWND, + asoc->peers_rwnd , data_list[i]->send_size, sctp_peer_chunk_oh); +#endif + asoc->peers_rwnd = sctp_sbspace_sub(asoc->peers_rwnd, + (u_int32_t)(data_list[i]->send_size + sctp_peer_chunk_oh)); + if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { + /* SWS sender side engages */ + asoc->peers_rwnd = 0; + } + } +} + +static void +sctp_clean_up_ctl(struct sctp_association *asoc) +{ + struct sctp_tmit_chunk *chk, *nchk; + for (chk = TAILQ_FIRST(&asoc->control_send_queue); + chk; chk = nchk) { + nchk = TAILQ_NEXT(chk, sctp_next); + if ((chk->rec.chunk_id == SCTP_SELECTIVE_ACK) || + (chk->rec.chunk_id == SCTP_HEARTBEAT_REQUEST) || + (chk->rec.chunk_id == SCTP_HEARTBEAT_ACK) || + (chk->rec.chunk_id == SCTP_SHUTDOWN) || + (chk->rec.chunk_id == SCTP_SHUTDOWN_ACK) || + (chk->rec.chunk_id == SCTP_OPERATION_ERROR) || + (chk->rec.chunk_id == SCTP_PACKET_DROPPED) || + (chk->rec.chunk_id == SCTP_COOKIE_ACK) || + (chk->rec.chunk_id == SCTP_ECN_CWR) || + (chk->rec.chunk_id == SCTP_ASCONF_ACK)) { + /* Stray chunks must be cleaned up */ + clean_up_anyway: + TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + asoc->ctrl_queue_cnt--; + sctp_free_remote_addr(chk->whoTo); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + } else if (chk->rec.chunk_id == SCTP_STREAM_RESET) { + struct sctp_stream_reset_req *strreq; + /* special handling, we must look into the param */ + strreq = mtod(chk->data, struct sctp_stream_reset_req *); + if (strreq->sr_req.ph.param_type == ntohs(SCTP_STR_RESET_RESPONSE)) { + goto clean_up_anyway; + } + } + } +} + +static int +sctp_move_to_outqueue(struct sctp_tcb *stcb, + struct sctp_stream_out *strq) +{ + /* Move from the stream to the send_queue keeping track of the total */ + struct sctp_association *asoc; + int tot_moved = 0; + int failed = 0; + int padval; + struct sctp_tmit_chunk *chk, *nchk; + struct sctp_data_chunk *dchkh; + struct sctpchunk_listhead tmp; + struct mbuf *orig; + + asoc = &stcb->asoc; + TAILQ_INIT(&tmp); + chk = TAILQ_FIRST(&strq->outqueue); + while (chk) { + nchk = TAILQ_NEXT(chk, sctp_next); + /* now put in the chunk header */ + orig = chk->data; + M_PREPEND(chk->data, sizeof(struct sctp_data_chunk), M_DONTWAIT); + if (chk->data == NULL) { + /* HELP */ + failed++; + break; + } + if (orig != chk->data) { + /* A new mbuf was added, account for it */ + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + stcb->sctp_socket->so_snd.sb_mbcnt += MSIZE; + } +#ifdef SCTP_MBCNT_LOGGING + sctp_log_mbcnt(SCTP_LOG_MBCNT_INCREASE, + asoc->total_output_queue_size, + 0, + asoc->total_output_mbuf_queue_size, + MSIZE); +#endif + stcb->asoc.total_output_mbuf_queue_size += MSIZE; + chk->mbcnt += MSIZE; + } + chk->send_size += sizeof(struct sctp_data_chunk); + /* This should NOT have to do anything, but + * I would rather be cautious + */ + if (!failed && ((size_t)chk->data->m_len < sizeof(struct sctp_data_chunk))) { + m_pullup(chk->data, sizeof(struct sctp_data_chunk)); + if (chk->data == NULL) { + failed++; + break; + } + } + dchkh = mtod(chk->data, struct sctp_data_chunk *); + dchkh->ch.chunk_length = htons(chk->send_size); + /* Chunks must be padded to even word boundary */ + padval = chk->send_size % 4; + if (padval) { + /* For fragmented messages this should not + * run except possibly on the last chunk + */ + if (sctp_pad_lastmbuf(chk->data, (4 - padval))) { + /* we are in big big trouble no mbufs :< */ + failed++; + break; + } + chk->send_size += (4 - padval); + } + /* pull from stream queue */ + TAILQ_REMOVE(&strq->outqueue, chk, sctp_next); + asoc->stream_queue_cnt--; + TAILQ_INSERT_TAIL(&tmp, chk, sctp_next); + /* add it in to the size of moved chunks */ + if (chk->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) { + /* we pull only one message */ + break; + } + chk = nchk; + } + if (failed) { + /* Gak, we just lost the user message */ + chk = TAILQ_FIRST(&tmp); + while (chk) { + nchk = TAILQ_NEXT(chk, sctp_next); + TAILQ_REMOVE(&tmp, chk, sctp_next); + + sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, stcb, + (SCTP_NOTIFY_DATAGRAM_UNSENT|SCTP_INTERNAL_ERROR), + chk); + + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + if (chk->whoTo) { + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = NULL; + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = nchk; + } + return (0); + } + /* now pull them off of temp wheel */ + chk = TAILQ_FIRST(&tmp); + while (chk) { + nchk = TAILQ_NEXT(chk, sctp_next); + /* insert on send_queue */ + TAILQ_REMOVE(&tmp, chk, sctp_next); + TAILQ_INSERT_TAIL(&asoc->send_queue, chk, sctp_next); + asoc->send_queue_cnt++; + /* assign TSN */ + chk->rec.data.TSN_seq = asoc->sending_seq++; + + dchkh = mtod(chk->data, struct sctp_data_chunk *); + /* Put the rest of the things in place now. Size + * was done earlier in previous loop prior to + * padding. + */ + dchkh->ch.chunk_type = SCTP_DATA; + dchkh->ch.chunk_flags = chk->rec.data.rcv_flags; + dchkh->dp.tsn = htonl(chk->rec.data.TSN_seq); + dchkh->dp.stream_id = htons(strq->stream_no); + dchkh->dp.stream_sequence = htons(chk->rec.data.stream_seq); + dchkh->dp.protocol_id = chk->rec.data.payloadtype; + /* total count moved */ + tot_moved += chk->send_size; + chk = nchk; + } + return (tot_moved); +} + +static void +sctp_fill_outqueue(struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_association *asoc; + struct sctp_tmit_chunk *chk; + struct sctp_stream_out *strq, *strqn; + int mtu_fromwheel, goal_mtu; + unsigned int moved, seenend, cnt_mvd=0; + + asoc = &stcb->asoc; + /* Attempt to move at least 1 MTU's worth + * onto the wheel for each destination address + */ + goal_mtu = net->cwnd - net->flight_size; + if ((unsigned int)goal_mtu < net->mtu) { + goal_mtu = net->mtu; + } + if (sctp_pegs[SCTP_MOVED_MTU] < (unsigned int)goal_mtu) { + sctp_pegs[SCTP_MOVED_MTU] = goal_mtu; + } + seenend = moved = mtu_fromwheel = 0; + if (asoc->last_out_stream == NULL) { + strq = asoc->last_out_stream = TAILQ_FIRST(&asoc->out_wheel); + if (asoc->last_out_stream == NULL) { + /* huh nothing on the wheel, TSNH */ + return; + } + goto done_it; + } + strq = TAILQ_NEXT(asoc->last_out_stream, next_spoke); + done_it: + if (strq == NULL) { + asoc->last_out_stream = TAILQ_FIRST(&asoc->out_wheel); + } + while (mtu_fromwheel < goal_mtu) { + if (strq == NULL) { + if (seenend == 0) { + seenend = 1; + strq = TAILQ_FIRST(&asoc->out_wheel); + } else if ((moved == 0) && (seenend)) { + /* none left on the wheel */ + sctp_pegs[SCTP_MOVED_NLEF]++; + return; + } else if (moved) { + /* + * clear the flags and rotate back through + * again + */ + moved = 0; + seenend = 0; + strq = TAILQ_FIRST(&asoc->out_wheel); + } + if (strq == NULL) + break; + continue; + } + strqn = TAILQ_NEXT(strq, next_spoke); + if ((chk = TAILQ_FIRST(&strq->outqueue)) == NULL) { + /* none left on this queue, prune a spoke? */ + sctp_remove_from_wheel(asoc, strq); + if (strq == asoc->last_out_stream) { + /* the last one we used went off the wheel */ + asoc->last_out_stream = NULL; + } + strq = strqn; + continue; + } + if (chk->whoTo != net) { + /* Skip this stream, first one on stream + * does not head to our current destination. + */ + strq = strqn; + continue; + } + mtu_fromwheel += sctp_move_to_outqueue(stcb, strq); + cnt_mvd++; + moved++; + asoc->last_out_stream = strq; + strq = strqn; + } + sctp_pegs[SCTP_MOVED_MAX]++; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Ok we moved %d chunks to send queue\n", + moved); + } +#endif + if (sctp_pegs[SCTP_MOVED_QMAX] < cnt_mvd) { + sctp_pegs[SCTP_MOVED_QMAX] = cnt_mvd; + } +} + +void +sctp_fix_ecn_echo(struct sctp_association *asoc) +{ + struct sctp_tmit_chunk *chk; + TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { + if (chk->rec.chunk_id == SCTP_ECN_ECHO) { + chk->sent = SCTP_DATAGRAM_UNSENT; + } + } +} + +static void +sctp_move_to_an_alt(struct sctp_tcb *stcb, + struct sctp_association *asoc, + struct sctp_nets *net) +{ + struct sctp_tmit_chunk *chk; + struct sctp_nets *a_net; + a_net = sctp_find_alternate_net(stcb, net); + if ((a_net != net) && + ((a_net->dest_state & SCTP_ADDR_REACHABLE) == SCTP_ADDR_REACHABLE)) { + /* + * We only proceed if a valid alternate is found that is + * not this one and is reachable. Here we must move all + * chunks queued in the send queue off of the destination + * address to our alternate. + */ + TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) { + if (chk->whoTo == net) { + /* Move the chunk to our alternate */ + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = a_net; + a_net->ref_count++; + } + } + } +} + +static int sctp_from_user_send=0; + +static int +sctp_med_chunk_output(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_association *asoc, + int *num_out, + int *reason_code, + int control_only, int *cwnd_full, int from_where, + struct timeval *now, int *now_filled) +{ + /* + * Ok this is the generic chunk service queue. + * we must do the following: + * - Service the stream queue that is next, moving any message + * (note I must get a complete message i.e. FIRST/MIDDLE and + * LAST to the out queue in one pass) and assigning TSN's + * - Check to see if the cwnd/rwnd allows any output, if so we + * go ahead and fomulate and send the low level chunks. Making + * sure to combine any control in the control chunk queue also. + */ + struct sctp_nets *net; + struct mbuf *outchain; + struct sctp_tmit_chunk *chk, *nchk; + struct sctphdr *shdr; + /* temp arrays for unlinking */ + struct sctp_tmit_chunk *data_list[SCTP_MAX_DATA_BUNDLING]; + int no_fragmentflg, error; + int one_chunk, hbflag; + int asconf, cookie, no_out_cnt; + int bundle_at, ctl_cnt, no_data_chunks, cwnd_full_ind; + unsigned int mtu, r_mtu, omtu; + *num_out = 0; + cwnd_full_ind = 0; + ctl_cnt = no_out_cnt = asconf = cookie = 0; + /* + * First lets prime the pump. For each destination, if there + * is room in the flight size, attempt to pull an MTU's worth + * out of the stream queues into the general send_queue + */ +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xC2, 2); +#endif +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("***********************\n"); + } +#endif + hbflag = 0; + if (control_only) + no_data_chunks = 1; + else + no_data_chunks = 0; + + /* Nothing to possible to send? */ + if (TAILQ_EMPTY(&asoc->control_send_queue) && + TAILQ_EMPTY(&asoc->send_queue) && + TAILQ_EMPTY(&asoc->out_wheel)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("All wheels empty\n"); + } +#endif + return (0); + } + if (asoc->peers_rwnd <= 0) { + /* No room in peers rwnd */ + *cwnd_full = 1; + *reason_code = 1; + if (asoc->total_flight > 0) { + /* we are allowed one chunk in flight */ + no_data_chunks = 1; + sctp_pegs[SCTP_RWND_BLOCKED]++; + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Ok we have done the fillup no_data_chunk=%d tf=%d prw:%d\n", + (int)no_data_chunks, + (int)asoc->total_flight, (int)asoc->peers_rwnd); + } +#endif + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("net:%p fs:%d cwnd:%d\n", + net, net->flight_size, net->cwnd); + } +#endif + if (net->flight_size >= net->cwnd) { + /* skip this network, no room */ + cwnd_full_ind++; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Ok skip fillup->fs:%d > cwnd:%d\n", + net->flight_size, + net->cwnd); + } +#endif + sctp_pegs[SCTP_CWND_NOFILL]++; + continue; + } + /* + * spin through the stream queues moving one message and + * assign TSN's as appropriate. + */ + sctp_fill_outqueue(stcb, net); + } + *cwnd_full = cwnd_full_ind; + /* now service each destination and send out what we can for it */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + int chk_cnt = 0; + TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) { + chk_cnt++; + } + printf("We have %d chunks on the send_queue\n", chk_cnt); + chk_cnt = 0; + TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { + chk_cnt++; + } + printf("We have %d chunks on the sent_queue\n", chk_cnt); + TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { + chk_cnt++; + } + printf("We have %d chunks on the control_queue\n", chk_cnt); + } +#endif + /* If we have data to send, and DSACK is running, stop it + * and build a SACK to dump on to bundle with output. This + * actually MAY make it so the bundling does not occur if + * the SACK is big but I think this is ok because basic SACK + * space is pre-reserved in our fragmentation size choice. + */ + if ((TAILQ_FIRST(&asoc->send_queue) != NULL) && + (no_data_chunks == 0)) { + /* We will be sending something */ + if (callout_pending(&stcb->asoc.dack_timer.timer)) { + /* Yep a callout is pending */ + sctp_timer_stop(SCTP_TIMER_TYPE_RECV, + stcb->sctp_ep, + stcb, NULL); + sctp_send_sack(stcb); + } + } + /* Nothing to send? */ + if ((TAILQ_FIRST(&asoc->control_send_queue) == NULL) && + (TAILQ_FIRST(&asoc->send_queue) == NULL)) { + return (0); + } + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + /* how much can we send? */ + if (net->ref_count < 2) { + /* Ref-count of 1 so we cannot have data or control + * queued to this address. Skip it. + */ + continue; + } + ctl_cnt = bundle_at = 0; + outchain = NULL; + no_fragmentflg = 1; + one_chunk = 0; + + if ((net->ro.ro_rt) && (net->ro.ro_rt->rt_ifp)) { + /* if we have a route and an ifp + * check to see if we have room to + * send to this guy + */ + struct ifnet *ifp; + ifp = net->ro.ro_rt->rt_ifp; + if ((ifp->if_snd.ifq_len + 2) >= ifp->if_snd.ifq_maxlen) { + sctp_pegs[SCTP_IFP_QUEUE_FULL]++; +#ifdef SCTP_LOG_MAXBURST + sctp_log_maxburst(net, ifp->if_snd.ifq_len, ifp->if_snd.ifq_maxlen, SCTP_MAX_IFP_APPLIED); + #endif + continue; + } + } + if (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET) { + mtu = net->mtu - (sizeof(struct ip) + sizeof(struct sctphdr)); + } else { + mtu = net->mtu - (sizeof(struct ip6_hdr) + sizeof(struct sctphdr)); + } + if (mtu > asoc->peers_rwnd) { + if (asoc->total_flight > 0) { + /* We have a packet in flight somewhere */ + r_mtu = asoc->peers_rwnd; + } else { + /* We are always allowed to send one MTU out */ + one_chunk = 1; + r_mtu = mtu; + } + } else { + r_mtu = mtu; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Ok r_mtu is %d mtu is %d for this net:%p one_chunk:%d\n", + r_mtu, mtu, net, one_chunk); + } +#endif + /************************/ + /* Control transmission */ + /************************/ + /* Now first lets go through the control queue */ + for (chk = TAILQ_FIRST(&asoc->control_send_queue); + chk; chk = nchk) { + nchk = TAILQ_NEXT(chk, sctp_next); + if (chk->whoTo != net) { + /* + * No, not sent to the network we are + * looking at + */ + continue; + } + if (chk->data == NULL) { + continue; + } + if ((chk->data->m_flags & M_PKTHDR) == 0) { + /* + * NOTE: the chk queue MUST have the PKTHDR + * flag set on it with a total in the + * m_pkthdr.len field!! else the chunk will + * ALWAYS be skipped + */ + continue; + } + if (chk->sent != SCTP_DATAGRAM_UNSENT) { + /* + * It must be unsent. Cookies and ASCONF's + * hang around but there timers will force + * when marked for resend. + */ + continue; + } + /* Here we do NOT factor the r_mtu */ + if ((chk->data->m_pkthdr.len < (int)mtu) || + (chk->flags & CHUNK_FLAGS_FRAGMENT_OK)) { + /* + * We probably should glom the mbuf chain from + * the chk->data for control but the problem + * is it becomes yet one more level of + * tracking to do if for some reason output + * fails. Then I have got to reconstruct the + * merged control chain.. el yucko.. for now + * we take the easy way and do the copy + */ + outchain = sctp_copy_mbufchain(chk->data, + outchain); + if (outchain == NULL) { + return (ENOMEM); + } + /* update our MTU size */ + mtu -= chk->data->m_pkthdr.len; + if (mtu < 0) { + mtu = 0; + } + /* Do clear IP_DF ? */ + if (chk->flags & CHUNK_FLAGS_FRAGMENT_OK) { + no_fragmentflg = 0; + } + /* Mark things to be removed, if needed */ + if ((chk->rec.chunk_id == SCTP_SELECTIVE_ACK) || + (chk->rec.chunk_id == SCTP_HEARTBEAT_REQUEST) || + (chk->rec.chunk_id == SCTP_HEARTBEAT_ACK) || + (chk->rec.chunk_id == SCTP_SHUTDOWN) || + (chk->rec.chunk_id == SCTP_SHUTDOWN_ACK) || + (chk->rec.chunk_id == SCTP_OPERATION_ERROR) || + (chk->rec.chunk_id == SCTP_COOKIE_ACK) || + (chk->rec.chunk_id == SCTP_ECN_CWR) || + (chk->rec.chunk_id == SCTP_PACKET_DROPPED) || + (chk->rec.chunk_id == SCTP_ASCONF_ACK)) { + + if (chk->rec.chunk_id == SCTP_HEARTBEAT_REQUEST) + hbflag = 1; + /* remove these chunks at the end */ + if (chk->rec.chunk_id == SCTP_SELECTIVE_ACK) { + /* turn off the timer */ + if (callout_pending(&stcb->asoc.dack_timer.timer)) { + sctp_timer_stop(SCTP_TIMER_TYPE_RECV, + inp, stcb, net); + } + } + ctl_cnt++; + } else { + /* + * Other chunks, since they have + * timers running (i.e. COOKIE or + * ASCONF) we just "trust" that it + * gets sent or retransmitted. + */ + ctl_cnt++; + if (chk->rec.chunk_id == SCTP_COOKIE_ECHO) { + cookie = 1; + no_out_cnt = 1; + } else if (chk->rec.chunk_id == SCTP_ASCONF) { + /* + * set hb flag since we can use + * these for RTO + */ + hbflag = 1; + asconf = 1; + } + chk->sent = SCTP_DATAGRAM_SENT; + chk->snd_count++; + } + if (mtu == 0) { + /* + * Ok we are out of room but we can + * output without effecting the flight + * size since this little guy is a + * control only packet. + */ + if (asconf) { + sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, net); + asconf = 0; + } + if (cookie) { + sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net); + cookie = 0; + } + if (outchain->m_len == 0) { + /* + * Special case for when you + * get a 0 len mbuf at the + * head due to the lack of a + * MHDR at the beginning. + */ + outchain->m_len = sizeof(struct sctphdr); + } else { + M_PREPEND(outchain, sizeof(struct sctphdr), M_DONTWAIT); + if (outchain == NULL) { + /* no memory */ + error = ENOBUFS; + goto error_out_again; + } + } + shdr = mtod(outchain, struct sctphdr *); + shdr->src_port = inp->sctp_lport; + shdr->dest_port = stcb->rport; + shdr->v_tag = htonl(stcb->asoc.peer_vtag); + shdr->checksum = 0; + + if ((error = sctp_lowlevel_chunk_output(inp, stcb, net, + (struct sockaddr *)&net->ro._l_addr, + outchain, + no_fragmentflg, 0, NULL, asconf))) { + if (error == ENOBUFS) { + asoc->ifp_had_enobuf = 1; + } + sctp_pegs[SCTP_DATA_OUT_ERR]++; + if (from_where == 0) { + sctp_pegs[SCTP_ERROUT_FRM_USR]++; + } + error_out_again: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { + printf("Gak got ctrl error %d\n", error); + } +#endif + /* error, could not output */ + if (hbflag) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Update HB anyway\n"); + } +#endif + if (*now_filled == 0) { + SCTP_GETTIME_TIMEVAL(&net->last_sent_time); + *now_filled = 1; + *now = net->last_sent_time; + } else { + net->last_sent_time = *now; + } + hbflag = 0; + } + if (error == EHOSTUNREACH) { + /* + * Destination went + * unreachable during + * this send + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Moving data to an alterante\n"); + } +#endif + sctp_move_to_an_alt(stcb, asoc, net); + } + sctp_clean_up_ctl (asoc); + return (error); + } else + asoc->ifp_had_enobuf = 0; + /* Only HB or ASCONF advances time */ + if (hbflag) { + if (*now_filled == 0) { + SCTP_GETTIME_TIMEVAL(&net->last_sent_time); + *now_filled = 1; + *now = net->last_sent_time; + } else { + net->last_sent_time = *now; + } + hbflag = 0; + } + /* + * increase the number we sent, if a + * cookie is sent we don't tell them + * any was sent out. + */ + if (!no_out_cnt) + *num_out += ctl_cnt; + /* recalc a clean slate and setup */ + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + mtu = (net->mtu - SCTP_MIN_OVERHEAD); + } else { + mtu = (net->mtu - SCTP_MIN_V4_OVERHEAD); + } + no_fragmentflg = 1; + } + } + } + /*********************/ + /* Data transmission */ + /*********************/ + /* now lets add any data within the MTU constraints */ + if (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET) { + omtu = net->mtu - (sizeof(struct ip) + sizeof(struct sctphdr)); + } else { + omtu = net->mtu - (sizeof(struct ip6_hdr) + sizeof(struct sctphdr)); + } + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Now to data transmission\n"); + } +#endif + + if (((asoc->state & SCTP_STATE_OPEN) == SCTP_STATE_OPEN) || + (cookie)) { + for (chk = TAILQ_FIRST(&asoc->send_queue); chk; chk = nchk) { + if (no_data_chunks) { + /* let only control go out */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Either nothing to send or we are full\n"); + } +#endif + break; + } + if (net->flight_size >= net->cwnd) { + /* skip this net, no room for data */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("fs:%d > cwnd:%d\n", + net->flight_size, net->cwnd); + } +#endif + sctp_pegs[SCTP_CWND_BLOCKED]++; + *reason_code = 2; + break; + } + nchk = TAILQ_NEXT(chk, sctp_next); + if (chk->whoTo != net) { + /* No, not sent to this net */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("chk->whoTo:%p not %p\n", + chk->whoTo, net); + + } +#endif + continue; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Can we pick up a chunk?\n"); + } +#endif + if ((chk->send_size > omtu) && ((chk->flags & CHUNK_FLAGS_FRAGMENT_OK) == 0)) { + /* strange, we have a chunk that is to bit + * for its destination and yet no fragment ok flag. + * Something went wrong when the PMTU changed...we did + * not mark this chunk for some reason?? I will + * fix it here by letting IP fragment it for now and + * printing a warning. This really should not happen ... + */ +/*#ifdef SCTP_DEBUG*/ + printf("Warning chunk of %d bytes > mtu:%d and yet PMTU disc missed\n", + chk->send_size, mtu); +/*#endif*/ + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + } + + if (((chk->send_size <= mtu) && (chk->send_size <= r_mtu)) || + ((chk->flags & CHUNK_FLAGS_FRAGMENT_OK) && (chk->send_size <= asoc->peers_rwnd))) { + /* ok we will add this one */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Picking up the chunk\n"); + } +#endif + outchain = sctp_copy_mbufchain(chk->data, outchain); + if (outchain == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Gakk no memory\n"); + } +#endif + if (!callout_pending(&net->rxt_timer.timer)) { + sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net); + } + return (ENOMEM); + } + /* upate our MTU size */ + /* Do clear IP_DF ? */ + if (chk->flags & CHUNK_FLAGS_FRAGMENT_OK) { + no_fragmentflg = 0; + } + mtu -= chk->send_size; + r_mtu -= chk->send_size; + data_list[bundle_at++] = chk; + if (bundle_at >= SCTP_MAX_DATA_BUNDLING) { + mtu = 0; + break; + } + if (mtu <= 0) { + mtu = 0; + break; + } + if ((r_mtu <= 0) || one_chunk) { + r_mtu = 0; + break; + } + } else { + /* + * Must be sent in order of the TSN's + * (on a network) + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("ok no more chk:%d > mtu:%d || < r_mtu:%d\n", + chk->send_size, mtu, r_mtu); + } +#endif + + break; + } + }/* for () */ + } /* if asoc.state OPEN */ + /* Is there something to send for this destination? */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("ok now is chain assembled? %p\n", + outchain); + } +#endif + + if (outchain) { + /* We may need to start a control timer or two */ + if (asconf) { + sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, net); + asconf = 0; + } + if (cookie) { + sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, net); + cookie = 0; + } + /* must start a send timer if data is being sent */ + if (bundle_at && (!callout_pending(&net->rxt_timer.timer))) { + /* no timer running on this destination + * restart it. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("ok lets start a send timer .. we will transmit %p\n", + outchain); + } +#endif + sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net); + } + /* Now send it, if there is anything to send :> */ + if ((outchain->m_flags & M_PKTHDR) == 0) { + struct mbuf *t; + + MGETHDR(t, M_DONTWAIT, MT_HEADER); + if (t == NULL) { + sctp_m_freem(outchain); + return (ENOMEM); + } + t->m_next = outchain; + t->m_pkthdr.len = 0; + t->m_pkthdr.rcvif = 0; + t->m_len = 0; + + outchain = t; + while (t) { + outchain->m_pkthdr.len += t->m_len; + t = t->m_next; + } + } + if (outchain->m_len == 0) { + /* Special case for when you get a 0 len + * mbuf at the head due to the lack + * of a MHDR at the beginning. + */ + MH_ALIGN(outchain, sizeof(struct sctphdr)); + outchain->m_len = sizeof(struct sctphdr); + } else { + M_PREPEND(outchain, sizeof(struct sctphdr), M_DONTWAIT); + if (outchain == NULL) { + /* out of mbufs */ + error = ENOBUFS; + goto errored_send; + } + } + shdr = mtod(outchain, struct sctphdr *); + shdr->src_port = inp->sctp_lport; + shdr->dest_port = stcb->rport; + shdr->v_tag = htonl(stcb->asoc.peer_vtag); + shdr->checksum = 0; + if ((error = sctp_lowlevel_chunk_output(inp, stcb, net, + (struct sockaddr *)&net->ro._l_addr, + outchain, + no_fragmentflg, bundle_at, data_list[0], asconf))) { + /* error, we could not output */ + if (error == ENOBUFS) { + asoc->ifp_had_enobuf = 1; + } + sctp_pegs[SCTP_DATA_OUT_ERR]++; + if (from_where == 0) { + sctp_pegs[SCTP_ERROUT_FRM_USR]++; + } + + errored_send: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Gak send error %d\n", error); + } +#endif + if (hbflag) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Update HB time anyway\n"); + } +#endif + if (*now_filled == 0) { + SCTP_GETTIME_TIMEVAL(&net->last_sent_time); + *now_filled = 1; + *now = net->last_sent_time; + } else { + net->last_sent_time = *now; + } + hbflag = 0; + } + if (error == EHOSTUNREACH) { + /* + * Destination went unreachable during + * this send + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Calling the movement routine\n"); + } +#endif + sctp_move_to_an_alt(stcb, asoc, net); + } + sctp_clean_up_ctl (asoc); + return (error); + } else { + asoc->ifp_had_enobuf = 0; + } + if (bundle_at || hbflag) { + /* For data/asconf and hb set time */ + if (*now_filled == 0) { + SCTP_GETTIME_TIMEVAL(&net->last_sent_time); + *now_filled = 1; + *now = net->last_sent_time; + } else { + net->last_sent_time = *now; + } + } + + if (!no_out_cnt) { + *num_out += (ctl_cnt + bundle_at); + } + if (bundle_at) { + if (!net->rto_pending) { + /* setup for a RTO measurement */ + net->rto_pending = 1; + data_list[0]->do_rtt = 1; + } else { + data_list[0]->do_rtt = 0; + } + sctp_pegs[SCTP_PEG_TSNS_SENT] += bundle_at; + sctp_clean_up_datalist(stcb, asoc, data_list, bundle_at, net); + } + if (one_chunk) { + break; + } + } + } + /* At the end there should be no NON timed + * chunks hanging on this queue. + */ + if ((*num_out == 0) && (*reason_code == 0)) { + *reason_code = 3; + } + sctp_clean_up_ctl (asoc); + return (0); +} + +void +sctp_queue_op_err(struct sctp_tcb *stcb, struct mbuf *op_err) +{ + /* Prepend a OPERATIONAL_ERROR chunk header + * and put on the end of the control chunk queue. + */ + /* Sender had better have gotten a MGETHDR or else + * the control chunk will be forever skipped + */ + struct sctp_chunkhdr *hdr; + struct sctp_tmit_chunk *chk; + struct mbuf *mat; + + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* no memory */ + sctp_m_freem(op_err); + return; + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + M_PREPEND(op_err, sizeof(struct sctp_chunkhdr), M_DONTWAIT); + if (op_err == NULL) { + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return; + } + chk->send_size = 0; + mat = op_err; + while (mat != NULL) { + chk->send_size += mat->m_len; + mat = mat->m_next; + } + chk->rec.chunk_id = SCTP_OPERATION_ERROR; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->flags = 0; + chk->asoc = &stcb->asoc; + chk->data = op_err; + chk->whoTo = chk->asoc->primary_destination; + chk->whoTo->ref_count++; + hdr = mtod(op_err, struct sctp_chunkhdr *); + hdr->chunk_type = SCTP_OPERATION_ERROR; + hdr->chunk_flags = 0; + hdr->chunk_length = htons(chk->send_size); + TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, + chk, + sctp_next); + chk->asoc->ctrl_queue_cnt++; +} + +int +sctp_send_cookie_echo(struct mbuf *m, + int offset, + struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + /* + * pull out the cookie and put it at the front of the control + * chunk queue. + */ + int at; + struct mbuf *cookie, *mat; + struct sctp_paramhdr parm, *phdr; + struct sctp_chunkhdr *hdr; + struct sctp_tmit_chunk *chk; + uint16_t ptype, plen; + /* First find the cookie in the param area */ + cookie = NULL; + at = offset + sizeof(struct sctp_init_chunk); + + do { + phdr = sctp_get_next_param(m, at, &parm, sizeof(parm)); + if (phdr == NULL) { + return (-3); + } + ptype = ntohs(phdr->param_type); + plen = ntohs(phdr->param_length); + if (ptype == SCTP_STATE_COOKIE) { + int pad; + /* found the cookie */ + if ((pad = (plen % 4))) { + plen += 4 - pad; + } + cookie = sctp_m_copym(m, at, plen, M_DONTWAIT); + if (cookie == NULL) { + /* No memory */ + return (-2); + } + break; + } + at += SCTP_SIZE32(plen); + } while (phdr); + if (cookie == NULL) { + /* Did not find the cookie */ + return (-3); + } + /* ok, we got the cookie lets change it into a cookie echo chunk */ + + /* first the change from param to cookie */ + hdr = mtod(cookie, struct sctp_chunkhdr *); + hdr->chunk_type = SCTP_COOKIE_ECHO; + hdr->chunk_flags = 0; + /* now we MUST have a PKTHDR on it */ + if ((cookie->m_flags & M_PKTHDR) != M_PKTHDR) { + /* we hope this happens rarely */ + MGETHDR(mat, M_DONTWAIT, MT_HEADER); + if (mat == NULL) { + sctp_m_freem(cookie); + return (-4); + } + mat->m_len = 0; + mat->m_pkthdr.rcvif = 0; + mat->m_next = cookie; + cookie = mat; + } + cookie->m_pkthdr.len = plen; + /* get the chunk stuff now and place it in the FRONT of the queue */ + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* no memory */ + sctp_m_freem(cookie); + return (-5); + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + chk->send_size = cookie->m_pkthdr.len; + chk->rec.chunk_id = SCTP_COOKIE_ECHO; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->flags = 0; + chk->asoc = &stcb->asoc; + chk->data = cookie; + chk->whoTo = chk->asoc->primary_destination; + chk->whoTo->ref_count++; + TAILQ_INSERT_HEAD(&chk->asoc->control_send_queue, chk, sctp_next); + chk->asoc->ctrl_queue_cnt++; + return (0); +} + +void +sctp_send_heartbeat_ack(struct sctp_tcb *stcb, + struct mbuf *m, + int offset, + int chk_length, + struct sctp_nets *net) +{ + /* take a HB request and make it into a + * HB ack and send it. + */ + struct mbuf *outchain; + struct sctp_chunkhdr *chdr; + struct sctp_tmit_chunk *chk; + + + if (net == NULL) + /* must have a net pointer */ + return; + + outchain = sctp_m_copym(m, offset, chk_length, M_DONTWAIT); + if (outchain == NULL) { + /* gak out of memory */ + return; + } + chdr = mtod(outchain, struct sctp_chunkhdr *); + chdr->chunk_type = SCTP_HEARTBEAT_ACK; + chdr->chunk_flags = 0; + if ((outchain->m_flags & M_PKTHDR) != M_PKTHDR) { + /* should not happen but we are cautious. */ + struct mbuf *tmp; + MGETHDR(tmp, M_DONTWAIT, MT_HEADER); + if (tmp == NULL) { + return; + } + tmp->m_len = 0; + tmp->m_pkthdr.rcvif = 0; + tmp->m_next = outchain; + outchain = tmp; + } + outchain->m_pkthdr.len = chk_length; + if (chk_length % 4) { + /* need pad */ + u_int32_t cpthis=0; + int padlen; + padlen = 4 - (outchain->m_pkthdr.len % 4); + m_copyback(outchain, outchain->m_pkthdr.len, padlen, (caddr_t)&cpthis); + } + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* no memory */ + sctp_m_freem(outchain); + return ; + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + + chk->send_size = chk_length; + chk->rec.chunk_id = SCTP_HEARTBEAT_ACK; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->flags = 0; + chk->asoc = &stcb->asoc; + chk->data = outchain; + chk->whoTo = net; + chk->whoTo->ref_count++; + TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); + chk->asoc->ctrl_queue_cnt++; +} + +int +sctp_send_cookie_ack(struct sctp_tcb *stcb) { + /* formulate and queue a cookie-ack back to sender */ + struct mbuf *cookie_ack; + struct sctp_chunkhdr *hdr; + struct sctp_tmit_chunk *chk; + + cookie_ack = NULL; + MGETHDR(cookie_ack, M_DONTWAIT, MT_HEADER); + if (cookie_ack == NULL) { + /* no mbuf's */ + return (-1); + } + cookie_ack->m_data += SCTP_MIN_OVERHEAD; + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* no memory */ + sctp_m_freem(cookie_ack); + return (-1); + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + + chk->send_size = sizeof(struct sctp_chunkhdr); + chk->rec.chunk_id = SCTP_COOKIE_ACK; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->flags = 0; + chk->asoc = &stcb->asoc; + chk->data = cookie_ack; + if (chk->asoc->last_control_chunk_from != NULL) { + chk->whoTo = chk->asoc->last_control_chunk_from; + } else { + chk->whoTo = chk->asoc->primary_destination; + } + chk->whoTo->ref_count++; + hdr = mtod(cookie_ack, struct sctp_chunkhdr *); + hdr->chunk_type = SCTP_COOKIE_ACK; + hdr->chunk_flags = 0; + hdr->chunk_length = htons(chk->send_size); + cookie_ack->m_pkthdr.len = cookie_ack->m_len = chk->send_size; + cookie_ack->m_pkthdr.rcvif = 0; + TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); + chk->asoc->ctrl_queue_cnt++; + return (0); +} + + +int +sctp_send_shutdown_ack(struct sctp_tcb *stcb, struct sctp_nets *net) +{ + /* formulate and queue a SHUTDOWN-ACK back to the sender */ + struct mbuf *m_shutdown_ack; + struct sctp_shutdown_ack_chunk *ack_cp; + struct sctp_tmit_chunk *chk; + + m_shutdown_ack = NULL; + MGETHDR(m_shutdown_ack, M_DONTWAIT, MT_HEADER); + if (m_shutdown_ack == NULL) { + /* no mbuf's */ + return (-1); + } + m_shutdown_ack->m_data += SCTP_MIN_OVERHEAD; + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* no memory */ + sctp_m_freem(m_shutdown_ack); + return (-1); + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + + chk->send_size = sizeof(struct sctp_chunkhdr); + chk->rec.chunk_id = SCTP_SHUTDOWN_ACK; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->flags = 0; + chk->asoc = &stcb->asoc; + chk->data = m_shutdown_ack; + chk->whoTo = net; + net->ref_count++; + + ack_cp = mtod(m_shutdown_ack, struct sctp_shutdown_ack_chunk *); + ack_cp->ch.chunk_type = SCTP_SHUTDOWN_ACK; + ack_cp->ch.chunk_flags = 0; + ack_cp->ch.chunk_length = htons(chk->send_size); + m_shutdown_ack->m_pkthdr.len = m_shutdown_ack->m_len = chk->send_size; + m_shutdown_ack->m_pkthdr.rcvif = 0; + TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); + chk->asoc->ctrl_queue_cnt++; + return (0); +} + +int +sctp_send_shutdown(struct sctp_tcb *stcb, struct sctp_nets *net) +{ + /* formulate and queue a SHUTDOWN to the sender */ + struct mbuf *m_shutdown; + struct sctp_shutdown_chunk *shutdown_cp; + struct sctp_tmit_chunk *chk; + + m_shutdown = NULL; + MGETHDR(m_shutdown, M_DONTWAIT, MT_HEADER); + if (m_shutdown == NULL) { + /* no mbuf's */ + return (-1); + } + m_shutdown->m_data += SCTP_MIN_OVERHEAD; + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* no memory */ + sctp_m_freem(m_shutdown); + return (-1); + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + + chk->send_size = sizeof(struct sctp_shutdown_chunk); + chk->rec.chunk_id = SCTP_SHUTDOWN; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->flags = 0; + chk->asoc = &stcb->asoc; + chk->data = m_shutdown; + chk->whoTo = net; + net->ref_count++; + + shutdown_cp = mtod(m_shutdown, struct sctp_shutdown_chunk *); + shutdown_cp->ch.chunk_type = SCTP_SHUTDOWN; + shutdown_cp->ch.chunk_flags = 0; + shutdown_cp->ch.chunk_length = htons(chk->send_size); + shutdown_cp->cumulative_tsn_ack = htonl(stcb->asoc.cumulative_tsn); + m_shutdown->m_pkthdr.len = m_shutdown->m_len = chk->send_size; + m_shutdown->m_pkthdr.rcvif = 0; + TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); + chk->asoc->ctrl_queue_cnt++; + + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + stcb->sctp_ep->sctp_socket->so_snd.sb_cc = 0; + soisdisconnecting(stcb->sctp_ep->sctp_socket); + } + return (0); +} + +int +sctp_send_asconf(struct sctp_tcb *stcb, struct sctp_nets *net) +{ + /* + * formulate and queue an ASCONF to the peer + * ASCONF parameters should be queued on the assoc queue + */ + struct sctp_tmit_chunk *chk; + struct mbuf *m_asconf; + struct sctp_asconf_chunk *acp; + + + /* compose an ASCONF chunk, maximum length is PMTU */ + m_asconf = sctp_compose_asconf(stcb); + if (m_asconf == NULL) { + return (-1); + } + acp = mtod(m_asconf, struct sctp_asconf_chunk *); + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* no memory */ + sctp_m_freem(m_asconf); + return (-1); + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + + chk->data = m_asconf; + chk->send_size = m_asconf->m_pkthdr.len; + chk->rec.chunk_id = SCTP_ASCONF; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->flags = 0; + chk->asoc = &stcb->asoc; + chk->whoTo = chk->asoc->primary_destination; + chk->whoTo->ref_count++; + TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); + chk->asoc->ctrl_queue_cnt++; + return (0); +} + +int +sctp_send_asconf_ack(struct sctp_tcb *stcb, uint32_t retrans) +{ + /* + * formulate and queue a asconf-ack back to sender + * the asconf-ack must be stored in the tcb + */ + struct sctp_tmit_chunk *chk; + struct mbuf *m_ack; + + /* is there a asconf-ack mbuf chain to send? */ + if (stcb->asoc.last_asconf_ack_sent == NULL) { + return (-1); + } + + /* copy the asconf_ack */ +#if defined(__FreeBSD__) || defined(__NetBSD__) + /* Supposedly the m_copypacket is a optimzation, + * use it if we can. + */ + if (stcb->asoc.last_asconf_ack_sent->m_flags & M_PKTHDR) { + m_ack = m_copypacket(stcb->asoc.last_asconf_ack_sent, M_DONTWAIT); + sctp_pegs[SCTP_CACHED_SRC]++; + } else + m_ack = m_copy(stcb->asoc.last_asconf_ack_sent, 0, M_COPYALL); +#else + m_ack = m_copy(stcb->asoc.last_asconf_ack_sent, 0, M_COPYALL); +#endif + if (m_ack == NULL) { + /* couldn't copy it */ + + return (-1); + } + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* no memory */ + if (m_ack) + sctp_m_freem(m_ack); + return (-1); + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + + /* figure out where it goes to */ + if (retrans) { + /* we're doing a retransmission */ + if (stcb->asoc.used_alt_asconfack > 2) { + /* tried alternate nets already, go back */ + chk->whoTo = NULL; + } else { + /* need to try and alternate net */ + chk->whoTo = sctp_find_alternate_net(stcb, stcb->asoc.last_control_chunk_from); + stcb->asoc.used_alt_asconfack++; + } + if (chk->whoTo == NULL) { + /* no alternate */ + if (stcb->asoc.last_control_chunk_from == NULL) + chk->whoTo = stcb->asoc.primary_destination; + else + chk->whoTo = stcb->asoc.last_control_chunk_from; + stcb->asoc.used_alt_asconfack = 0; + } + } else { + /* normal case */ + if (stcb->asoc.last_control_chunk_from == NULL) + chk->whoTo = stcb->asoc.primary_destination; + else + chk->whoTo = stcb->asoc.last_control_chunk_from; + stcb->asoc.used_alt_asconfack = 0; + } + chk->data = m_ack; + chk->send_size = m_ack->m_pkthdr.len; + chk->rec.chunk_id = SCTP_ASCONF_ACK; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->flags = 0; + chk->asoc = &stcb->asoc; + chk->whoTo->ref_count++; + TAILQ_INSERT_TAIL(&chk->asoc->control_send_queue, chk, sctp_next); + chk->asoc->ctrl_queue_cnt++; + return (0); +} + + +static int +sctp_chunk_retransmission(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_association *asoc, + int *cnt_out, struct timeval *now, int *now_filled) +{ + /* + * send out one MTU of retransmission. + * If fast_retransmit is happening we ignore the cwnd. + * Otherwise we obey the cwnd and rwnd. + * For a Cookie or Asconf in the control chunk queue we retransmit + * them by themselves. + * + * For data chunks we will pick out the lowest TSN's in the + * sent_queue marked for resend and bundle them all together + * (up to a MTU of destination). The address to send to should + * have been selected/changed where the retransmission was + * marked (i.e. in FR or t3-timeout routines). + */ + struct sctp_tmit_chunk *data_list[SCTP_MAX_DATA_BUNDLING]; + struct sctp_tmit_chunk *chk, *fwd; + struct mbuf *m; + struct sctphdr *shdr; + int asconf; + struct sctp_nets *net; + int no_fragmentflg, bundle_at, cnt_thru; + unsigned int mtu; + int error, i, one_chunk, fwd_tsn, ctl_cnt, tmr_started; + + tmr_started = ctl_cnt = bundle_at = error = 0; + no_fragmentflg = 1; + asconf = 0; + fwd_tsn = 0; + *cnt_out = 0; + fwd = NULL; + m = NULL; +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xC3, 1); +#endif + if (TAILQ_EMPTY(&asoc->sent_queue)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("SCTP hits empty queue with cnt set to %d?\n", + asoc->sent_queue_retran_cnt); + } +#endif + asoc->sent_queue_cnt = 0; + asoc->sent_queue_cnt_removeable = 0; + } + TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { + if (chk->sent != SCTP_DATAGRAM_RESEND) { + /* we only worry about things marked for resend */ + continue; + } + if ((chk->rec.chunk_id == SCTP_COOKIE_ECHO) || + (chk->rec.chunk_id == SCTP_ASCONF) || + (chk->rec.chunk_id == SCTP_STREAM_RESET) || + (chk->rec.chunk_id == SCTP_FORWARD_CUM_TSN)) { + if (chk->rec.chunk_id == SCTP_STREAM_RESET) { + /* For stream reset we only retran the request + * not the response. + */ + struct sctp_stream_reset_req *strreq; + strreq = mtod(chk->data, struct sctp_stream_reset_req *); + if (strreq->sr_req.ph.param_type != ntohs(SCTP_STR_RESET_REQUEST)) { + continue; + } + } + ctl_cnt++; + if (chk->rec.chunk_id == SCTP_ASCONF) { + no_fragmentflg = 1; + asconf = 1; + } + if (chk->rec.chunk_id == SCTP_FORWARD_CUM_TSN) { + fwd_tsn = 1; + fwd = chk; + } + m = sctp_copy_mbufchain(chk->data, m); + break; + } + } + one_chunk = 0; + cnt_thru = 0; + /* do we have control chunks to retransmit? */ + if (m != NULL) { + /* Start a timer no matter if we suceed or fail */ + if (chk->rec.chunk_id == SCTP_COOKIE_ECHO) { + sctp_timer_start(SCTP_TIMER_TYPE_COOKIE, inp, stcb, chk->whoTo); + } else if (chk->rec.chunk_id == SCTP_ASCONF) + sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp, stcb, chk->whoTo); + + if (m->m_len == 0) { + /* Special case for when you get a 0 len + * mbuf at the head due to the lack + * of a MHDR at the beginning. + */ + m->m_len = sizeof(struct sctphdr); + } else { + M_PREPEND(m, sizeof(struct sctphdr), M_DONTWAIT); + if (m == NULL) { + return (ENOBUFS); + } + } + shdr = mtod(m, struct sctphdr *); + shdr->src_port = inp->sctp_lport; + shdr->dest_port = stcb->rport; + shdr->v_tag = htonl(stcb->asoc.peer_vtag); + shdr->checksum = 0; + chk->snd_count++; /* update our count */ + + if ((error = sctp_lowlevel_chunk_output(inp, stcb, chk->whoTo, + (struct sockaddr *)&chk->whoTo->ro._l_addr, m, + no_fragmentflg, 0, NULL, asconf))) { + sctp_pegs[SCTP_DATA_OUT_ERR]++; + return (error); + } + /* + *We don't want to mark the net->sent time here since this + * we use this for HB and retrans cannot measure RTT + */ + /* SCTP_GETTIME_TIMEVAL(&chk->whoTo->last_sent_time);*/ + *cnt_out += 1; + chk->sent = SCTP_DATAGRAM_SENT; + asoc->sent_queue_retran_cnt--; + if (asoc->sent_queue_retran_cnt < 0) { + asoc->sent_queue_retran_cnt = 0; + } + if (fwd_tsn == 0) { + return (0); + } else { + /* Clean up the fwd-tsn list */ + sctp_clean_up_ctl (asoc); + return (0); + } + } + /* Ok, it is just data retransmission we need to do or + * that and a fwd-tsn with it all. + */ + if (TAILQ_EMPTY(&asoc->sent_queue)) { + return (-1); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Normal chunk retransmission cnt:%d\n", + asoc->sent_queue_retran_cnt); + } +#endif + if ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT)) { + /* not yet open, resend the cookie and that is it */ + return (1); + } + + +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(20, inp, stcb, NULL); +#endif + TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { + if (chk->sent != SCTP_DATAGRAM_RESEND) { + /* No, not sent to this net or not ready for rtx */ + continue; + + } + /* pick up the net */ + net = chk->whoTo; + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + mtu = (net->mtu - SCTP_MIN_OVERHEAD); + } else { + mtu = net->mtu- SCTP_MIN_V4_OVERHEAD; + } + + if ((asoc->peers_rwnd < mtu) && (asoc->total_flight > 0)) { + /* No room in peers rwnd */ + uint32_t tsn; + tsn = asoc->last_acked_seq + 1; + if (tsn == chk->rec.data.TSN_seq) { + /* we make a special exception for this case. + * The peer has no rwnd but is missing the + * lowest chunk.. which is probably what is + * holding up the rwnd. + */ + goto one_chunk_around; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("blocked-peers_rwnd:%d tf:%d\n", + (int)asoc->peers_rwnd, + (int)asoc->total_flight); + } +#endif + sctp_pegs[SCTP_RWND_BLOCKED]++; + return (1); + } + one_chunk_around: + if (asoc->peers_rwnd < mtu) { + one_chunk = 1; + } +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xC3, 2); +#endif + bundle_at = 0; + m = NULL; + net->fast_retran_ip = 0; + if (chk->rec.data.doing_fast_retransmit == 0) { + /* if no FR in progress skip destination that + * have flight_size > cwnd. + */ + if (net->flight_size >= net->cwnd) { + sctp_pegs[SCTP_CWND_BLOCKED]++; + continue; + } + } else { + /* Mark the destination net to have FR recovery + * limits put on it. + */ + net->fast_retran_ip = 1; + } + + if ((chk->send_size <= mtu) || (chk->flags & CHUNK_FLAGS_FRAGMENT_OK)) { + /* ok we will add this one */ + m = sctp_copy_mbufchain(chk->data, m); + if (m == NULL) { + return (ENOMEM); + } + /* upate our MTU size */ + /* Do clear IP_DF ? */ + if (chk->flags & CHUNK_FLAGS_FRAGMENT_OK) { + no_fragmentflg = 0; + } + mtu -= chk->send_size; + data_list[bundle_at++] = chk; + if (one_chunk && (asoc->total_flight <= 0)) { + sctp_pegs[SCTP_WINDOW_PROBES]++; + chk->rec.data.state_flags |= SCTP_WINDOW_PROBE; + } + } + if (one_chunk == 0) { + /* now are there anymore forward from chk to pick up?*/ + fwd = TAILQ_NEXT(chk, sctp_next); + while (fwd) { + if (fwd->sent != SCTP_DATAGRAM_RESEND) { + /* Nope, not for retran */ + fwd = TAILQ_NEXT(fwd, sctp_next); + continue; + } + if (fwd->whoTo != net) { + /* Nope, not the net in question */ + fwd = TAILQ_NEXT(fwd, sctp_next); + continue; + } + if (fwd->send_size <= mtu) { + m = sctp_copy_mbufchain(fwd->data, m); + if (m == NULL) { + return (ENOMEM); + } + /* upate our MTU size */ + /* Do clear IP_DF ? */ + if (fwd->flags & CHUNK_FLAGS_FRAGMENT_OK) { + no_fragmentflg = 0; + } + mtu -= fwd->send_size; + data_list[bundle_at++] = fwd; + if (bundle_at >= SCTP_MAX_DATA_BUNDLING) { + break; + } + fwd = TAILQ_NEXT(fwd, sctp_next); + } else { + /* can't fit so we are done */ + break; + } + } + } + /* Is there something to send for this destination? */ + if (m) { + /* No matter if we fail/or suceed we should + * start a timer. A failure is like a lost + * IP packet :-) + */ + if (!callout_pending(&net->rxt_timer.timer)) { + /* no timer running on this destination + * restart it. + */ + sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net); + tmr_started = 1; + } + if (m->m_len == 0) { + /* Special case for when you get a 0 len + * mbuf at the head due to the lack + * of a MHDR at the beginning. + */ + m->m_len = sizeof(struct sctphdr); + } else { + M_PREPEND(m, sizeof(struct sctphdr), M_DONTWAIT); + if (m == NULL) { + return (ENOBUFS); + } + } + shdr = mtod(m, struct sctphdr *); + shdr->src_port = inp->sctp_lport; + shdr->dest_port = stcb->rport; + shdr->v_tag = htonl(stcb->asoc.peer_vtag); + shdr->checksum = 0; + + /* Now lets send it, if there is anything to send :> */ + if ((error = sctp_lowlevel_chunk_output(inp, stcb, net, + (struct sockaddr *)&net->ro._l_addr, + m, + no_fragmentflg, 0, NULL, asconf))) { + /* error, we could not output */ + sctp_pegs[SCTP_DATA_OUT_ERR]++; + return (error); + } + /* For HB's */ + /* + * We don't want to mark the net->sent time here since + * this we use this for HB and retrans cannot measure + * RTT + */ + /* SCTP_GETTIME_TIMEVAL(&net->last_sent_time);*/ + + /* For auto-close */ + cnt_thru++; + if (*now_filled == 0) { + SCTP_GETTIME_TIMEVAL(&asoc->time_last_sent); + *now = asoc->time_last_sent; + *now_filled = 1; + } else { + asoc->time_last_sent = *now; + } + *cnt_out += bundle_at; +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xC4, bundle_at); +#endif + for (i = 0; i < bundle_at; i++) { + sctp_pegs[SCTP_RETRANTSN_SENT]++; + data_list[i]->sent = SCTP_DATAGRAM_SENT; + data_list[i]->snd_count++; + asoc->sent_queue_retran_cnt--; + /* record the time */ + data_list[i]->sent_rcv_time = asoc->time_last_sent; + if (asoc->sent_queue_retran_cnt < 0) { + asoc->sent_queue_retran_cnt = 0; + } + net->flight_size += data_list[i]->book_size; + asoc->total_flight += data_list[i]->book_size; + asoc->total_flight_count++; + +#ifdef SCTP_LOG_RWND + sctp_log_rwnd(SCTP_DECREASE_PEER_RWND, + asoc->peers_rwnd , data_list[i]->send_size, sctp_peer_chunk_oh); +#endif + asoc->peers_rwnd = sctp_sbspace_sub(asoc->peers_rwnd, + (u_int32_t)(data_list[i]->send_size + sctp_peer_chunk_oh)); + if (asoc->peers_rwnd < stcb->sctp_ep->sctp_ep.sctp_sws_sender) { + /* SWS sender side engages */ + asoc->peers_rwnd = 0; + } + + if ((i == 0) && + (data_list[i]->rec.data.doing_fast_retransmit)) { + sctp_pegs[SCTP_FAST_RETRAN]++; + if ((data_list[i] == TAILQ_FIRST(&asoc->sent_queue)) && + (tmr_started == 0)) { + /* + * ok we just fast-retrans'd + * the lowest TSN, i.e the + * first on the list. In this + * case we want to give some + * more time to get a SACK + * back without a t3-expiring. + */ + sctp_timer_stop(SCTP_TIMER_TYPE_SEND, inp, stcb, net); + sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net); + } + } + } +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(21, inp, stcb, NULL); +#endif + } else { + /* None will fit */ + return (1); + } + if (asoc->sent_queue_retran_cnt <= 0) { + /* all done we have no more to retran */ + asoc->sent_queue_retran_cnt = 0; + break; + } + if (one_chunk) { + /* No more room in rwnd */ + return (1); + } + /* stop the for loop here. we sent out a packet */ + break; + } + return (0); +} + + +static int +sctp_timer_validation(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_association *asoc, + int ret) +{ + struct sctp_nets *net; + /* Validate that a timer is running somewhere */ + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + if (callout_pending(&net->rxt_timer.timer)) { + /* Here is a timer */ + return (ret); + } + } + /* Gak, we did not have a timer somewhere */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Deadlock avoided starting timer on a dest at retran\n"); + } +#endif + sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, asoc->primary_destination); + return (ret); +} + +int +sctp_chunk_output(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + int from_where) +{ + /* Ok this is the generic chunk service queue. + * we must do the following: + * - See if there are retransmits pending, if so we + * must do these first and return. + * - Service the stream queue that is next, + * moving any message (note I must get a complete + * message i.e. FIRST/MIDDLE and LAST to the out + * queue in one pass) and assigning TSN's + * - Check to see if the cwnd/rwnd allows any output, if + * so we go ahead and fomulate and send the low level + * chunks. Making sure to combine any control in the + * control chunk queue also. + */ + struct sctp_association *asoc; + struct sctp_nets *net; + int error, num_out, tot_out, ret, reason_code, burst_cnt, burst_limit; + struct timeval now; + int now_filled=0; + int cwnd_full=0; + asoc = &stcb->asoc; + tot_out = 0; + num_out = 0; + reason_code = 0; + sctp_pegs[SCTP_CALLS_TO_CO]++; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("in co - retran count:%d\n", asoc->sent_queue_retran_cnt); + } +#endif + while (asoc->sent_queue_retran_cnt) { + /* Ok, it is retransmission time only, we send out only ONE + * packet with a single call off to the retran code. + */ + ret = sctp_chunk_retransmission(inp, stcb, asoc, &num_out, &now, &now_filled); + if (ret > 0) { + /* Can't send anymore */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("retransmission ret:%d -- full\n", ret); + } +#endif + /* + * now lets push out control by calling med-level + * output once. this assures that we WILL send HB's + * if queued too. + */ + (void)sctp_med_chunk_output(inp, stcb, asoc, &num_out, &reason_code, 1, + &cwnd_full, from_where, + &now, &now_filled); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Control send outputs:%d@full\n", num_out); + } +#endif +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(8, inp, stcb, NULL); +#endif + return (sctp_timer_validation(inp, stcb, asoc, ret)); + } + if (ret < 0) { + /* + * The count was off.. retran is not happening so do + * the normal retransmission. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Done with retrans, none left fill up window\n"); + } +#endif +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(9, inp, stcb, NULL); +#endif + break; + } + if (from_where == 1) { + /* Only one transmission allowed out of a timeout */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Only one packet allowed out\n"); + } +#endif +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(10, inp, stcb, NULL); +#endif + /* Push out any control */ + (void)sctp_med_chunk_output(inp, stcb, asoc, &num_out, &reason_code, 1, &cwnd_full, from_where, + &now, &now_filled); + return (ret); + } + if ((num_out == 0) && (ret == 0)) { + /* No more retrans to send */ + break; + } + } +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(12, inp, stcb, NULL); +#endif + /* Check for bad destinations, if they exist move chunks around. */ + burst_limit = asoc->max_burst; + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + if ((net->dest_state & SCTP_ADDR_NOT_REACHABLE) == + SCTP_ADDR_NOT_REACHABLE) { + /* + * if possible move things off of this address + * we still may send below due to the dormant state + * but we try to find an alternate address to send + * to and if we have one we move all queued data on + * the out wheel to this alternate address. + */ + sctp_move_to_an_alt(stcb, asoc, net); + } else { + /* + if ((asoc->sat_network) || (net->addr_is_local)) { + burst_limit = asoc->max_burst * SCTP_SAT_NETWORK_BURST_INCR; + } + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("examined net:%p burst limit:%d\n", net, asoc->max_burst); + } +#endif + +#ifdef SCTP_USE_ALLMAN_BURST + if ((net->flight_size+(burst_limit*net->mtu)) < net->cwnd) { + if (net->ssthresh < net->cwnd) + net->ssthresh = net->cwnd; + net->cwnd = (net->flight_size+(burst_limit*net->mtu)); +#ifdef SCTP_LOG_MAXBURST + sctp_log_maxburst(net, 0, burst_limit, SCTP_MAX_BURST_APPLIED); +#endif + sctp_pegs[SCTP_MAX_BURST_APL]++; + } + net->fast_retran_ip = 0; +#endif + } + + } + /* Fill up what we can to the destination */ + burst_cnt = 0; + cwnd_full = 0; + do { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("Burst count:%d - call m-c-o\n", burst_cnt); + } +#endif + error = sctp_med_chunk_output(inp, stcb, asoc, &num_out, + &reason_code, 0, &cwnd_full, from_where, + &now, &now_filled); + if (error) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Error %d was returned from med-c-op\n", error); + } +#endif +#ifdef SCTP_LOG_MAXBURST + sctp_log_maxburst(asoc->primary_destination, error , burst_cnt, SCTP_MAX_BURST_ERROR_STOP); +#endif + break; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT3) { + printf("m-c-o put out %d\n", num_out); + } +#endif + tot_out += num_out; + burst_cnt++; + } while (num_out +#ifndef SCTP_USE_ALLMAN_BURST + && (burst_cnt < burst_limit) +#endif + ); +#ifndef SCTP_USE_ALLMAN_BURST + if (burst_cnt >= burst_limit) { + sctp_pegs[SCTP_MAX_BURST_APL]++; + asoc->burst_limit_applied = 1; +#ifdef SCTP_LOG_MAXBURST + sctp_log_maxburst(asoc->primary_destination, 0 , burst_cnt, SCTP_MAX_BURST_APPLIED); +#endif + } else { + asoc->burst_limit_applied = 0; + } +#endif + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Ok, we have put out %d chunks\n", tot_out); + } +#endif + if (tot_out == 0) { + sctp_pegs[SCTP_CO_NODATASNT]++; + if (asoc->stream_queue_cnt > 0) { + sctp_pegs[SCTP_SOS_NOSNT]++; + } else { + sctp_pegs[SCTP_NOS_NOSNT]++; + } + if (asoc->send_queue_cnt > 0) { + sctp_pegs[SCTP_SOSE_NOSNT]++; + } else { + sctp_pegs[SCTP_NOSE_NOSNT]++; + } + } + /* Now we need to clean up the control chunk chain if + * a ECNE is on it. It must be marked as UNSENT again + * so next call will continue to send it until + * such time that we get a CWR, to remove it. + */ + sctp_fix_ecn_echo(asoc); + return (error); +} + + +int +sctp_output(inp, m, addr, control, p, flags) + struct sctp_inpcb *inp; + struct mbuf *m; + struct sockaddr *addr; + struct mbuf *control; +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + struct thread *p; +#else + struct proc *p; +#endif + int flags; +{ + struct inpcb *ip_inp; + struct sctp_inpcb *t_inp; + struct sctp_tcb *stcb; + struct sctp_nets *net; + struct sctp_association *asoc; + int create_lock_applied = 0; + int queue_only, error = 0; + int s; + struct sctp_sndrcvinfo srcv; + int un_sent = 0; + int use_rcvinfo = 0; + t_inp = inp; + /* struct route ro;*/ + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + queue_only = 0; + ip_inp = (struct inpcb *)inp; + stcb = NULL; + asoc = NULL; + net = NULL; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("USR Send BEGINS\n"); + } +#endif + + if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && + (inp->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING)) { + /* The listner can NOT send */ + if (control) { + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + control = NULL; + } + sctp_m_freem(m); + splx(s); + return (EFAULT); + } + /* Can't allow a V6 address on a non-v6 socket */ + if (addr) { + SCTP_ASOC_CREATE_LOCK(inp); + if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || + (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { + /* Should I really unlock ? */ + SCTP_ASOC_CREATE_UNLOCK(inp); + if (control) { + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + control = NULL; + } + sctp_m_freem(m); + splx(s); + return (EFAULT); + } + create_lock_applied = 1; + if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) && + (addr->sa_family == AF_INET6)) { + SCTP_ASOC_CREATE_UNLOCK(inp); + if (control) { + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + control = NULL; + } + sctp_m_freem(m); + splx(s); + return (EINVAL); + } + } + if (control) { + sctppcbinfo.mbuf_track++; + if (sctp_find_cmsg(SCTP_SNDRCV, (void *)&srcv, control, + sizeof(srcv))) { + if (srcv.sinfo_flags & MSG_SENDALL) { + /* its a sendall */ + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + splx(s); + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + return (sctp_sendall(inp, NULL, m, &srcv)); + } + if (srcv.sinfo_assoc_id) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + + if (stcb == NULL) { + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + sctp_m_freem(m); + splx(s); + return (ENOTCONN); + } + net = stcb->asoc.primary_destination; + } else { + stcb = sctp_findassociation_ep_asocid(inp, srcv.sinfo_assoc_id); + } + /* + * Question: Should I error here if the + + * assoc_id is no longer valid? + * i.e. I can't find it? + */ + if ((stcb) && + (addr != NULL)) { + /* Must locate the net structure */ + if (addr) + net = sctp_findnet(stcb, addr); + } + if (net == NULL) + net = stcb->asoc.primary_destination; + } + use_rcvinfo = 1; + } + } + if (stcb == NULL) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + if (stcb == NULL) { + splx(s); + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + if (control) { + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + control = NULL; + } + sctp_m_freem(m); + return (ENOTCONN); + } + if (addr == NULL) { + net = stcb->asoc.primary_destination; + } else { + net = sctp_findnet(stcb, addr); + if (net == NULL) { + net = stcb->asoc.primary_destination; + } + } + } else { + if (addr != NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + stcb = sctp_findassociation_ep_addr(&t_inp, addr, &net, NULL, NULL); + if (stcb == NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + } + } + } + if ((stcb == NULL) && + (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE)) { + if (control) { + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + control = NULL; + } + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + sctp_m_freem(m); + splx(s); + return (ENOTCONN); + } else if ((stcb == NULL) && + (addr == NULL)) { + if (control) { + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + control = NULL; + } + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + sctp_m_freem(m); + splx(s); + return (ENOENT); + } else if (stcb == NULL) { + /* UDP mode, we must go ahead and start the INIT process */ + if ((use_rcvinfo) && (srcv.sinfo_flags & MSG_ABORT)) { + /* Strange user to do this */ + if (control) { + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + control = NULL; + } + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + sctp_m_freem(m); + splx(s); + return (ENOENT); + } + stcb = sctp_aloc_assoc(inp, addr, 1, &error, 0); + if (stcb == NULL) { + if (control) { + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + control = NULL; + } + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + sctp_m_freem(m); + splx(s); + return (error); + } + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } else { + printf("Huh-1, create lock should have been applied!\n"); + } + queue_only = 1; + asoc = &stcb->asoc; + asoc->state = SCTP_STATE_COOKIE_WAIT; + SCTP_GETTIME_TIMEVAL(&asoc->time_entered); + if (control) { + /* see if a init structure exists in cmsg headers */ + struct sctp_initmsg initm; + int i; + if (sctp_find_cmsg(SCTP_INIT, (void *)&initm, control, + sizeof(initm))) { + /* we have an INIT override of the default */ + if (initm.sinit_max_attempts) + asoc->max_init_times = initm.sinit_max_attempts; + if (initm.sinit_num_ostreams) + asoc->pre_open_streams = initm.sinit_num_ostreams; + if (initm.sinit_max_instreams) + asoc->max_inbound_streams = initm.sinit_max_instreams; + if (initm.sinit_max_init_timeo) + asoc->initial_init_rto_max = initm.sinit_max_init_timeo; + } + if (asoc->streamoutcnt < asoc->pre_open_streams) { + /* Default is NOT correct */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Ok, defout:%d pre_open:%d\n", + asoc->streamoutcnt, asoc->pre_open_streams); + } +#endif + FREE(asoc->strmout, M_PCB); + asoc->strmout = NULL; + asoc->streamoutcnt = asoc->pre_open_streams; + MALLOC(asoc->strmout, struct sctp_stream_out *, + asoc->streamoutcnt * + sizeof(struct sctp_stream_out), M_PCB, + M_WAIT); + for (i = 0; i < asoc->streamoutcnt; i++) { + /* + * inbound side must be set to 0xffff, + * also NOTE when we get the INIT-ACK + * back (for INIT sender) we MUST + * reduce the count (streamoutcnt) but + * first check if we sent to any of the + * upper streams that were dropped (if + * some were). Those that were dropped + * must be notified to the upper layer + * as failed to send. + */ + asoc->strmout[i].next_sequence_sent = 0x0; + TAILQ_INIT(&asoc->strmout[i].outqueue); + asoc->strmout[i].stream_no = i; + asoc->strmout[i].next_spoke.tqe_next = 0; + asoc->strmout[i].next_spoke.tqe_prev = 0; + } + } + } + sctp_send_initiate(inp, stcb); + /* + * we may want to dig in after this call and adjust the MTU + * value. It defaulted to 1500 (constant) but the ro structure + * may now have an update and thus we may need to change it + * BEFORE we append the message. + */ + net = stcb->asoc.primary_destination; + } else { + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + asoc = &stcb->asoc; + if ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED)) { + queue_only = 1; + } + if ((SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) || + (asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) { + if (control) { + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + control = NULL; + } + if ((use_rcvinfo) && + (srcv.sinfo_flags & MSG_ABORT)) { + sctp_msg_append(stcb, net, m, &srcv, flags); + error = 0; + } else { + if (m) + sctp_m_freem(m); + error = ECONNRESET; + } + splx(s); + SCTP_TCB_UNLOCK(stcb); + return (error); + } + } + if (create_lock_applied) { + /* we should never hit here with the create lock applied + * + */ + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + + + if (use_rcvinfo == 0) { + srcv = stcb->asoc.def_send; + } +#ifdef SCTP_DEBUG + else { + if (sctp_debug_on & SCTP_DEBUG_OUTPUT5) { + printf("stream:%d\n", srcv.sinfo_stream); + printf("flags:%x\n", (u_int)srcv.sinfo_flags); + printf("ppid:%d\n", srcv.sinfo_ppid); + printf("context:%d\n", srcv.sinfo_context); + } + } +#endif + if (control) { + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + control = NULL; + } + if (net && ((srcv.sinfo_flags & MSG_ADDR_OVER))) { + /* we take the override or the unconfirmed */ + ; + } else { + net = stcb->asoc.primary_destination; + } + if ((error = sctp_msg_append(stcb, net, m, &srcv, flags))) { + SCTP_TCB_UNLOCK(stcb); + splx(s); + return (error); + } + if (net->flight_size > net->cwnd) { + sctp_pegs[SCTP_SENDTO_FULL_CWND]++; + queue_only = 1; + } else if (asoc->ifp_had_enobuf) { + sctp_pegs[SCTP_QUEONLY_BURSTLMT]++; + queue_only = 1; + } else { + un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) + + ((stcb->asoc.chunks_on_out_queue - stcb->asoc.total_flight_count) * sizeof(struct sctp_data_chunk)) + + SCTP_MED_OVERHEAD); + + if (((inp->sctp_flags & SCTP_PCB_FLAGS_NODELAY) == 0) && + (stcb->asoc.total_flight > 0) && + (un_sent < (int)stcb->asoc.smallest_mtu) + ) { + + /* Ok, Nagle is set on and we have + * data outstanding. Don't send anything + * and let the SACK drive out the data. + */ + sctp_pegs[SCTP_NAGLE_NOQ]++; + queue_only = 1; + } else { + sctp_pegs[SCTP_NAGLE_OFF]++; + } + } + if ((queue_only == 0) && stcb->asoc.peers_rwnd) { + /* we can attempt to send too.*/ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("USR Send calls sctp_chunk_output\n"); + } +#endif +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xC0, 1); + sctp_auditing(6, inp, stcb, net); +#endif + sctp_pegs[SCTP_OUTPUT_FRM_SND]++; + sctp_chunk_output(inp, stcb, 0); +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xC0, 2); + sctp_auditing(7, inp, stcb, net); +#endif + + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("USR Send complete qo:%d prw:%d\n", queue_only, stcb->asoc.peers_rwnd); + } +#endif + SCTP_TCB_UNLOCK(stcb); + splx(s); + return (0); +} + +void +send_forward_tsn(struct sctp_tcb *stcb, + struct sctp_association *asoc) +{ + struct sctp_tmit_chunk *chk; + struct sctp_forward_tsn_chunk *fwdtsn; + + TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { + if (chk->rec.chunk_id == SCTP_FORWARD_CUM_TSN) { + /* mark it to unsent */ + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + /* Do we correct its output location? */ + if (chk->whoTo != asoc->primary_destination) { + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = asoc->primary_destination; + chk->whoTo->ref_count++; + } + goto sctp_fill_in_rest; + } + } + /* Ok if we reach here we must build one */ + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + return; + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + chk->rec.chunk_id = SCTP_FORWARD_CUM_TSN; + chk->asoc = asoc; + MGETHDR(chk->data, M_DONTWAIT, MT_DATA); + if (chk->data == NULL) { + chk->whoTo->ref_count--; + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return; + } + chk->data->m_data += SCTP_MIN_OVERHEAD; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->whoTo = asoc->primary_destination; + chk->whoTo->ref_count++; + TAILQ_INSERT_TAIL(&asoc->control_send_queue, chk, sctp_next); + asoc->ctrl_queue_cnt++; + sctp_fill_in_rest: + /* Here we go through and fill out the part that + * deals with stream/seq of the ones we skip. + */ + chk->data->m_pkthdr.len = chk->data->m_len = 0; + { + struct sctp_tmit_chunk *at, *tp1, *last; + struct sctp_strseq *strseq; + unsigned int cnt_of_space, i, ovh; + unsigned int space_needed; + unsigned int cnt_of_skipped = 0; + TAILQ_FOREACH(at, &asoc->sent_queue, sctp_next) { + if (at->sent != SCTP_FORWARD_TSN_SKIP) { + /* no more to look at */ + break; + } + if (at->rec.data.rcv_flags & SCTP_DATA_UNORDERED) { + /* We don't report these */ + continue; + } + cnt_of_skipped++; + } + space_needed = (sizeof(struct sctp_forward_tsn_chunk) + + (cnt_of_skipped * sizeof(struct sctp_strseq))); + if ((M_TRAILINGSPACE(chk->data) < (int)space_needed) && + ((chk->data->m_flags & M_EXT) == 0)) { + /* Need a M_EXT, get one and move + * fwdtsn to data area. + */ + MCLGET(chk->data, M_DONTWAIT); + } + cnt_of_space = M_TRAILINGSPACE(chk->data); + + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + ovh = SCTP_MIN_OVERHEAD; + } else { + ovh = SCTP_MIN_V4_OVERHEAD; + } + if (cnt_of_space > (asoc->smallest_mtu-ovh)) { + /* trim to a mtu size */ + cnt_of_space = asoc->smallest_mtu - ovh; + } + if (cnt_of_space < space_needed) { + /* ok we must trim down the chunk by lowering + * the advance peer ack point. + */ + cnt_of_skipped = (cnt_of_space- + ((sizeof(struct sctp_forward_tsn_chunk))/ + sizeof(struct sctp_strseq))); + /* Go through and find the TSN that + * will be the one we report. + */ + at = TAILQ_FIRST(&asoc->sent_queue); + for (i = 0; i < cnt_of_skipped; i++) { + tp1 = TAILQ_NEXT(at, sctp_next); + at = tp1; + } + last = at; + /* last now points to last one I can report, update peer ack point */ + asoc->advanced_peer_ack_point = last->rec.data.TSN_seq; + space_needed -= (cnt_of_skipped * sizeof(struct sctp_strseq)); + } + chk->send_size = space_needed; + /* Setup the chunk */ + fwdtsn = mtod(chk->data, struct sctp_forward_tsn_chunk *); + fwdtsn->ch.chunk_length = htons(chk->send_size); + fwdtsn->ch.chunk_flags = 0; + fwdtsn->ch.chunk_type = SCTP_FORWARD_CUM_TSN; + fwdtsn->new_cumulative_tsn = htonl(asoc->advanced_peer_ack_point); + chk->send_size = (sizeof(struct sctp_forward_tsn_chunk) + + (cnt_of_skipped * sizeof(struct sctp_strseq))); + chk->data->m_pkthdr.len = chk->data->m_len = chk->send_size; + fwdtsn++; + /* Move pointer to after the fwdtsn and transfer to + * the strseq pointer. + */ + strseq = (struct sctp_strseq *)fwdtsn; + /* + * Now populate the strseq list. This is done blindly + * without pulling out duplicate stream info. This is + * inefficent but won't harm the process since the peer + * will look at these in sequence and will thus release + * anything. It could mean we exceed the PMTU and chop + * off some that we could have included.. but this is + * unlikely (aka 1432/4 would mean 300+ stream seq's would + * have to be reported in one FWD-TSN. With a bit of work + * we can later FIX this to optimize and pull out duplcates.. + * but it does add more overhead. So for now... not! + */ + at = TAILQ_FIRST(&asoc->sent_queue); + for (i = 0; i < cnt_of_skipped; i++) { + tp1 = TAILQ_NEXT(at, sctp_next); + if (at->rec.data.rcv_flags & SCTP_DATA_UNORDERED) { + /* We don't report these */ + i--; + at = tp1; + continue; + } + strseq->stream = ntohs(at->rec.data.stream_number); + strseq->sequence = ntohs(at->rec.data.stream_seq); + strseq++; + at = tp1; + } + } + return; + +} + +void +sctp_send_sack(struct sctp_tcb *stcb) +{ + /* + * Queue up a SACK in the control queue. We must first check to + * see if a SACK is somehow on the control queue. If so, we will + * take and and remove the old one. + */ + struct sctp_association *asoc; + struct sctp_tmit_chunk *chk, *a_chk; + struct sctp_sack_chunk *sack; + struct sctp_gap_ack_block *gap_descriptor; + uint32_t *dup; + int start; + unsigned int i, maxi, seeing_ones, m_size; + unsigned int num_gap_blocks, space; + + start = maxi = 0; + seeing_ones = 1; + a_chk = NULL; + asoc = &stcb->asoc; + if (asoc->last_data_chunk_from == NULL) { + /* Hmm we never received anything */ + return; + } + sctp_set_rwnd(stcb, asoc); + TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { + if (chk->rec.chunk_id == SCTP_SELECTIVE_ACK) { + /* Hmm, found a sack already on queue, remove it */ + TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); + asoc->ctrl_queue_cnt++; + a_chk = chk; + if (a_chk->data) + sctp_m_freem(a_chk->data); + a_chk->data = NULL; + sctp_free_remote_addr(a_chk->whoTo); + a_chk->whoTo = NULL; + break; + } + } + if (a_chk == NULL) { + a_chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (a_chk == NULL) { + /* No memory so we drop the idea, and set a timer */ + sctp_timer_stop(SCTP_TIMER_TYPE_RECV, + stcb->sctp_ep, stcb, NULL); + sctp_timer_start(SCTP_TIMER_TYPE_RECV, + stcb->sctp_ep, stcb, NULL); + return; + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + a_chk->rec.chunk_id = SCTP_SELECTIVE_ACK; + } + a_chk->asoc = asoc; + a_chk->snd_count = 0; + a_chk->send_size = 0; /* fill in later */ + a_chk->sent = SCTP_DATAGRAM_UNSENT; + m_size = (asoc->mapping_array_size << 3); + + if ((asoc->numduptsns) || + (asoc->last_data_chunk_from->dest_state & SCTP_ADDR_NOT_REACHABLE) + ) { + /* Ok, we have some duplicates or the destination for the + * sack is unreachable, lets see if we can select an alternate + * than asoc->last_data_chunk_from + */ + if ((!(asoc->last_data_chunk_from->dest_state & + SCTP_ADDR_NOT_REACHABLE)) && + (asoc->used_alt_onsack > 2)) { + /* We used an alt last time, don't this time */ + a_chk->whoTo = NULL; + } else { + asoc->used_alt_onsack++; + a_chk->whoTo = sctp_find_alternate_net(stcb, asoc->last_data_chunk_from); + } + if (a_chk->whoTo == NULL) { + /* Nope, no alternate */ + a_chk->whoTo = asoc->last_data_chunk_from; + asoc->used_alt_onsack = 0; + } + } else { + /* No duplicates so we use the last + * place we received data from. + */ +#ifdef SCTP_DEBUG + if (asoc->last_data_chunk_from == NULL) { + printf("Huh, last_data_chunk_from is null when we want to sack??\n"); + } +#endif + asoc->used_alt_onsack = 0; + a_chk->whoTo = asoc->last_data_chunk_from; + } + if (a_chk->whoTo) + a_chk->whoTo->ref_count++; + + /* Ok now lets formulate a MBUF with our sack */ + MGETHDR(a_chk->data, M_DONTWAIT, MT_DATA); + if ((a_chk->data == NULL) || + (a_chk->whoTo == NULL)) { + /* rats, no mbuf memory */ + if (a_chk->data) { + /* was a problem with the destination */ + sctp_m_freem(a_chk->data); + a_chk->data = NULL; + } + a_chk->whoTo->ref_count--; + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, a_chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + sctp_timer_stop(SCTP_TIMER_TYPE_RECV, + stcb->sctp_ep, stcb, NULL); + sctp_timer_start(SCTP_TIMER_TYPE_RECV, + stcb->sctp_ep, stcb, NULL); + return; + } + /* First count the number of gap ack blocks we need */ + if (asoc->highest_tsn_inside_map == asoc->cumulative_tsn) { + /* We know if there are none above the cum-ack we + * have everything with NO gaps + */ + num_gap_blocks = 0; + } else { + /* Ok we must count how many gaps we + * have. + */ + num_gap_blocks = 0; + if (asoc->highest_tsn_inside_map >= asoc->mapping_array_base_tsn) { + maxi = (asoc->highest_tsn_inside_map - asoc->mapping_array_base_tsn); + } else { + maxi = (asoc->highest_tsn_inside_map + (MAX_TSN - asoc->mapping_array_base_tsn) + 1); + } + if (maxi > m_size) { + /* impossible but who knows, someone is playing with us :> */ +#ifdef SCTP_DEBUG + printf("GAK maxi:%d > m_size:%d came out higher than allowed htsn:%u base:%u cumack:%u\n", + maxi, + m_size, + asoc->highest_tsn_inside_map, + asoc->mapping_array_base_tsn, + asoc->cumulative_tsn + ); +#endif + num_gap_blocks = 0; + goto no_gaps_now; + } + if (asoc->cumulative_tsn >= asoc->mapping_array_base_tsn) { + start = (asoc->cumulative_tsn - asoc->mapping_array_base_tsn); + } else { + /* Set it so we start at 0 */ + start = -1; + } + /* Ok move start up one to look at the NEXT past the cum-ack */ + start++; + for (i = start; i <= maxi; i++) { + if (seeing_ones) { + /* while seeing ones I must + * transition back to 0 before + * finding the next gap and + * counting the segment. + */ + if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, i) == 0) { + seeing_ones = 0; + } + } else { + if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, i)) { + seeing_ones = 1; + num_gap_blocks++; + } + } + } + no_gaps_now: + if (num_gap_blocks == 0) { + /* + * Traveled all of the bits and NO one, + * must have reneged + */ + if (compare_with_wrap(asoc->cumulative_tsn, asoc->highest_tsn_inside_map, MAX_TSN)) { + asoc->highest_tsn_inside_map = asoc->cumulative_tsn; +#ifdef SCTP_MAP_LOGGING + sctp_log_map(0, 4, asoc->highest_tsn_inside_map, SCTP_MAP_SLIDE_RESULT); +#endif + } + } + } + + /* Now calculate the space needed */ + space = (sizeof(struct sctp_sack_chunk) + + (num_gap_blocks * sizeof(struct sctp_gap_ack_block)) + + (asoc->numduptsns * sizeof(int32_t)) + ); + if (space > (asoc->smallest_mtu-SCTP_MAX_OVERHEAD)) { + /* Reduce the size of the sack to fit */ + int calc, fit; + calc = (asoc->smallest_mtu - SCTP_MAX_OVERHEAD); + calc -= sizeof(struct sctp_gap_ack_block); + fit = calc/sizeof(struct sctp_gap_ack_block); + if (fit > (int)num_gap_blocks) { + /* discard some dups */ + asoc->numduptsns = (fit - num_gap_blocks); + } else { + /* discard all dups and some gaps */ + num_gap_blocks = fit; + asoc->numduptsns = 0; + } + /* recalc space */ + space = (sizeof(struct sctp_sack_chunk) + + (num_gap_blocks * sizeof(struct sctp_gap_ack_block)) + + (asoc->numduptsns * sizeof(int32_t)) + ); + + } + + if ((space+SCTP_MIN_OVERHEAD) > MHLEN) { + /* We need a cluster */ + MCLGET(a_chk->data, M_DONTWAIT); + if ((a_chk->data->m_flags & M_EXT) != M_EXT) { + /* can't get a cluster + * give up and try later. + */ + if (a_chk->data) + sctp_m_freem(a_chk->data); + a_chk->data = NULL; + a_chk->whoTo->ref_count--; + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, a_chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + sctp_timer_stop(SCTP_TIMER_TYPE_RECV, + stcb->sctp_ep, stcb, NULL); + sctp_timer_start(SCTP_TIMER_TYPE_RECV, + stcb->sctp_ep, stcb, NULL); + return; + } + } + + /* ok, lets go through and fill it in */ + a_chk->data->m_data += SCTP_MIN_OVERHEAD; + sack = mtod(a_chk->data, struct sctp_sack_chunk *); + sack->ch.chunk_type = SCTP_SELECTIVE_ACK; + sack->ch.chunk_flags = asoc->receiver_nonce_sum & SCTP_SACK_NONCE_SUM; + sack->sack.cum_tsn_ack = htonl(asoc->cumulative_tsn); + sack->sack.a_rwnd = htonl(asoc->my_rwnd); + asoc->my_last_reported_rwnd = asoc->my_rwnd; + sack->sack.num_gap_ack_blks = htons(num_gap_blocks); + sack->sack.num_dup_tsns = htons(asoc->numduptsns); + + a_chk->send_size = (sizeof(struct sctp_sack_chunk) + + (num_gap_blocks * sizeof(struct sctp_gap_ack_block)) + + (asoc->numduptsns * sizeof(int32_t))); + a_chk->data->m_pkthdr.len = a_chk->data->m_len = a_chk->send_size; + sack->ch.chunk_length = htons(a_chk->send_size); + + gap_descriptor = (struct sctp_gap_ack_block *)((caddr_t)sack + sizeof(struct sctp_sack_chunk)); + seeing_ones = 0; + for (i = start; i <= maxi; i++) { + if (num_gap_blocks == 0) { + break; + } + if (seeing_ones) { + /* while seeing Ones I must + * transition back to 0 before + * finding the next gap + */ + if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, i) == 0) { + gap_descriptor->end = htons(((uint16_t)(i-start))); + gap_descriptor++; + seeing_ones = 0; + num_gap_blocks--; + } + } else { + if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, i)) { + gap_descriptor->start = htons(((uint16_t)(i+1-start))); + /* advance struct to next pointer */ + seeing_ones = 1; + } + } + } + if (num_gap_blocks) { + /* special case where the array is all 1's + * to the end of the array. + */ + gap_descriptor->end = htons(((uint16_t)((i-start)))); + gap_descriptor++; + } + /* now we must add any dups we are going to report. */ + if (asoc->numduptsns) { + dup = (uint32_t *)gap_descriptor; + for (i = 0; i < asoc->numduptsns; i++) { + *dup = htonl(asoc->dup_tsns[i]); + dup++; + } + asoc->numduptsns = 0; + } + /* now that the chunk is prepared queue it to the control + * chunk queue. + */ + TAILQ_INSERT_TAIL(&asoc->control_send_queue, a_chk, sctp_next); + asoc->ctrl_queue_cnt++; + sctp_pegs[SCTP_PEG_SACKS_SENT]++; + return; +} + +void +sctp_send_abort_tcb(struct sctp_tcb *stcb, struct mbuf *operr) +{ + struct mbuf *m_abort; + struct sctp_abort_msg *abort_m; + int sz; + abort_m = NULL; + MGETHDR(m_abort, M_DONTWAIT, MT_HEADER); + if (m_abort == NULL) { + /* no mbuf's */ + return; + } + m_abort->m_data += SCTP_MIN_OVERHEAD; + abort_m = mtod(m_abort, struct sctp_abort_msg *); + m_abort->m_len = sizeof(struct sctp_abort_msg); + m_abort->m_next = operr; + sz = 0; + if (operr) { + struct mbuf *n; + n = operr; + while (n) { + sz += n->m_len; + n = n->m_next; + } + } + abort_m->msg.ch.chunk_type = SCTP_ABORT_ASSOCIATION; + abort_m->msg.ch.chunk_flags = 0; + abort_m->msg.ch.chunk_length = htons(sizeof(struct sctp_abort_chunk) + + sz); + abort_m->sh.src_port = stcb->sctp_ep->sctp_lport; + abort_m->sh.dest_port = stcb->rport; + abort_m->sh.v_tag = htonl(stcb->asoc.peer_vtag); + abort_m->sh.checksum = 0; + m_abort->m_pkthdr.len = m_abort->m_len + sz; + m_abort->m_pkthdr.rcvif = 0; + sctp_lowlevel_chunk_output(stcb->sctp_ep, stcb, + stcb->asoc.primary_destination, + (struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr, + m_abort, 1, 0, NULL, 0); +} + +int +sctp_send_shutdown_complete(struct sctp_tcb *stcb, + struct sctp_nets *net) + +{ + /* formulate and SEND a SHUTDOWN-COMPLETE */ + struct mbuf *m_shutdown_comp; + struct sctp_shutdown_complete_msg *comp_cp; + + m_shutdown_comp = NULL; + MGETHDR(m_shutdown_comp, M_DONTWAIT, MT_HEADER); + if (m_shutdown_comp == NULL) { + /* no mbuf's */ + return (-1); + } + m_shutdown_comp->m_data += sizeof(struct ip6_hdr); + comp_cp = mtod(m_shutdown_comp, struct sctp_shutdown_complete_msg *); + comp_cp->shut_cmp.ch.chunk_type = SCTP_SHUTDOWN_COMPLETE; + comp_cp->shut_cmp.ch.chunk_flags = 0; + comp_cp->shut_cmp.ch.chunk_length = htons(sizeof(struct sctp_shutdown_complete_chunk)); + comp_cp->sh.src_port = stcb->sctp_ep->sctp_lport; + comp_cp->sh.dest_port = stcb->rport; + comp_cp->sh.v_tag = htonl(stcb->asoc.peer_vtag); + comp_cp->sh.checksum = 0; + + m_shutdown_comp->m_pkthdr.len = m_shutdown_comp->m_len = sizeof(struct sctp_shutdown_complete_msg); + m_shutdown_comp->m_pkthdr.rcvif = 0; + sctp_lowlevel_chunk_output(stcb->sctp_ep, stcb, net, + (struct sockaddr *)&net->ro._l_addr, m_shutdown_comp, + 1, 0, NULL, 0); + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + stcb->sctp_ep->sctp_flags &= ~SCTP_PCB_FLAGS_CONNECTED; + stcb->sctp_ep->sctp_socket->so_snd.sb_cc = 0; + soisdisconnected(stcb->sctp_ep->sctp_socket); + } + return (0); +} + +int +sctp_send_shutdown_complete2(struct mbuf *m, int iphlen, struct sctphdr *sh) +{ + /* formulate and SEND a SHUTDOWN-COMPLETE */ + struct mbuf *mout; + struct ip *iph, *iph_out; + struct ip6_hdr *ip6, *ip6_out; + int offset_out; + struct sctp_shutdown_complete_msg *comp_cp; + + MGETHDR(mout, M_DONTWAIT, MT_HEADER); + if (mout == NULL) { + /* no mbuf's */ + return (-1); + } + iph = mtod(m, struct ip *); + iph_out = NULL; + ip6_out = NULL; + offset_out = 0; + if (iph->ip_v == IPVERSION) { + mout->m_len = sizeof(struct ip) + + sizeof(struct sctp_shutdown_complete_msg); + mout->m_next = NULL; + iph_out = mtod(mout, struct ip *); + + /* Fill in the IP header for the ABORT */ + iph_out->ip_v = IPVERSION; + iph_out->ip_hl = (sizeof(struct ip)/4); + iph_out->ip_tos = (u_char)0; + iph_out->ip_id = 0; + iph_out->ip_off = 0; + iph_out->ip_ttl = MAXTTL; + iph_out->ip_p = IPPROTO_SCTP; + iph_out->ip_src.s_addr = iph->ip_dst.s_addr; + iph_out->ip_dst.s_addr = iph->ip_src.s_addr; + + /* let IP layer calculate this */ + iph_out->ip_sum = 0; + offset_out += sizeof(*iph_out); + comp_cp = (struct sctp_shutdown_complete_msg *)( + (caddr_t)iph_out + offset_out); + } else if (iph->ip_v == (IPV6_VERSION >> 4)) { + ip6 = (struct ip6_hdr *)iph; + mout->m_len = sizeof(struct ip6_hdr) + + sizeof(struct sctp_shutdown_complete_msg); + mout->m_next = NULL; + ip6_out = mtod(mout, struct ip6_hdr *); + + /* Fill in the IPv6 header for the ABORT */ + ip6_out->ip6_flow = ip6->ip6_flow; + ip6_out->ip6_hlim = ip6_defhlim; + ip6_out->ip6_nxt = IPPROTO_SCTP; + ip6_out->ip6_src = ip6->ip6_dst; + ip6_out->ip6_dst = ip6->ip6_src; + ip6_out->ip6_plen = mout->m_len; + offset_out += sizeof(*ip6_out); + comp_cp = (struct sctp_shutdown_complete_msg *)( + (caddr_t)ip6_out + offset_out); + } else { + /* Currently not supported. */ + return (-1); + } + + /* Now copy in and fill in the ABORT tags etc. */ + comp_cp->sh.src_port = sh->dest_port; + comp_cp->sh.dest_port = sh->src_port; + comp_cp->sh.checksum = 0; + comp_cp->sh.v_tag = sh->v_tag; + comp_cp->shut_cmp.ch.chunk_flags = SCTP_HAD_NO_TCB; + comp_cp->shut_cmp.ch.chunk_type = SCTP_SHUTDOWN_COMPLETE; + comp_cp->shut_cmp.ch.chunk_length = htons(sizeof(struct sctp_shutdown_complete_chunk)); + + mout->m_pkthdr.len = mout->m_len; + /* add checksum */ + if ((sctp_no_csum_on_loopback) && + (m->m_pkthdr.rcvif) && + (m->m_pkthdr.rcvif->if_type == IFT_LOOP)) { + comp_cp->sh.checksum = 0; + } else { + comp_cp->sh.checksum = sctp_calculate_sum(mout, NULL, offset_out); + } + + /* zap the rcvif, it should be null */ + mout->m_pkthdr.rcvif = 0; + /* zap the stack pointer to the route */ + if (iph_out != NULL) { + struct route ro; + + bzero(&ro, sizeof ro); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { + printf("sctp_shutdown_complete2 calling ip_output:\n"); + sctp_print_address_pkt(iph_out, &comp_cp->sh); + } +#endif + /* set IPv4 length */ +#if defined(__FreeBSD__) + iph_out->ip_len = mout->m_pkthdr.len; +#else + iph_out->ip_len = htons(mout->m_pkthdr.len); +#endif + /* out it goes */ + ip_output(mout, 0, &ro, IP_RAWOUTPUT, NULL +#if defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD_version >= 480000) || defined(__NetBSD__) + , NULL +#endif + ); + /* Free the route if we got one back */ + if (ro.ro_rt) + RTFREE(ro.ro_rt); + } else if (ip6_out != NULL) { +#ifdef NEW_STRUCT_ROUTE + struct route ro; +#else + struct route_in6 ro; +#endif + + bzero(&ro, sizeof(ro)); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { + printf("sctp_shutdown_complete2 calling ip6_output:\n"); + sctp_print_address_pkt((struct ip *)ip6_out, + &comp_cp->sh); + } +#endif + ip6_output(mout, NULL, &ro, 0, NULL, NULL +#if defined(__NetBSD__) + , NULL +#endif +#if (defined(__FreeBSD__) && __FreeBSD_version >= 480000) + , NULL +#endif + ); + /* Free the route if we got one back */ + if (ro.ro_rt) + RTFREE(ro.ro_rt); + } + sctp_pegs[SCTP_DATAGRAMS_SENT]++; + return (0); +} + +static struct sctp_nets * +sctp_select_hb_destination(struct sctp_tcb *stcb, struct timeval *now) +{ + struct sctp_nets *net, *hnet; + int ms_goneby, highest_ms, state_overide=0; + + SCTP_GETTIME_TIMEVAL(now); + highest_ms = 0; + hnet = NULL; + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + if ( + ((net->dest_state & SCTP_ADDR_NOHB) && ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0)) || + (net->dest_state & SCTP_ADDR_OUT_OF_SCOPE) + ) { + /* Skip this guy from consideration if HB is off AND its confirmed*/ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Skipping net:%p state:%d nohb/out-of-scope\n", + net, net->dest_state); + } +#endif + continue; + } + if (sctp_destination_is_reachable(stcb, (struct sockaddr *)&net->ro._l_addr) == 0) { + /* skip this dest net from consideration */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Skipping net:%p reachable NOT\n", + net); + } +#endif + continue; + } + if (net->last_sent_time.tv_sec) { + /* Sent to so we subtract */ + ms_goneby = (now->tv_sec - net->last_sent_time.tv_sec) * 1000; + } else + /* Never been sent to */ + ms_goneby = 0x7fffffff; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("net:%p ms_goneby:%d\n", + net, ms_goneby); + } +#endif + /* When the address state is unconfirmed but still considered reachable, we + * HB at a higher rate. Once it goes confirmed OR reaches the "unreachable" + * state, thenw we cut it back to HB at a more normal pace. + */ + if ((net->dest_state & (SCTP_ADDR_UNCONFIRMED|SCTP_ADDR_NOT_REACHABLE)) == SCTP_ADDR_UNCONFIRMED) { + state_overide = 1; + } else { + state_overide = 0; + } + + if ((((unsigned int)ms_goneby >= net->RTO) || (state_overide)) && + (ms_goneby > highest_ms)) { + highest_ms = ms_goneby; + hnet = net; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("net:%p is the new high\n", + net); + } +#endif + } + } + if (hnet && + ((hnet->dest_state & (SCTP_ADDR_UNCONFIRMED|SCTP_ADDR_NOT_REACHABLE)) == SCTP_ADDR_UNCONFIRMED)) { + state_overide = 1; + } else { + state_overide = 0; + } + + if (highest_ms && (((unsigned int)highest_ms >= hnet->RTO) || state_overide)) { + /* Found the one with longest delay bounds + * OR it is unconfirmed and still not marked + * unreachable. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("net:%p is the hb winner -", + hnet); + if (hnet) + sctp_print_address((struct sockaddr *)&hnet->ro._l_addr); + else + printf(" none\n"); + } +#endif + /* update the timer now */ + hnet->last_sent_time = *now; + return (hnet); + } + /* Nothing to HB */ + return (NULL); +} + +int +sctp_send_hb(struct sctp_tcb *stcb, int user_req, struct sctp_nets *u_net) +{ + struct sctp_tmit_chunk *chk; + struct sctp_nets *net; + struct sctp_heartbeat_chunk *hb; + struct timeval now; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + + if (user_req == 0) { + net = sctp_select_hb_destination(stcb, &now); + if (net == NULL) { + /* All our busy none to send to, just + * start the timer again. + */ + if (stcb->asoc.state == 0) { + return (0); + } + sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, + stcb->sctp_ep, + stcb, + net); + return (0); + } +#ifndef SCTP_USE_ALLMAN_BURST + else { + /* found one idle.. decay cwnd on this one + * by 1/2 if none outstanding. + */ + + if (net->flight_size == 0) { + net->cwnd /= 2; + if (net->addr_is_local) { + if (net->cwnd < (net->mtu *4)) { + net->cwnd = net->mtu * 4; + } + } else { + if (net->cwnd < (net->mtu * 2)) { + net->cwnd = net->mtu * 2; + } + } + + } + + } +#endif + } else { + net = u_net; + if (net == NULL) { + return (0); + } + SCTP_GETTIME_TIMEVAL(&now); + } + sin = (struct sockaddr_in *)&net->ro._l_addr; + if (sin->sin_family != AF_INET) { + if (sin->sin_family != AF_INET6) { + /* huh */ + return (0); + } + } + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Gak, can't get a chunk for hb\n"); + } +#endif + return (0); + } + sctppcbinfo.ipi_gencnt_chunk++; + sctppcbinfo.ipi_count_chunk++; + chk->rec.chunk_id = SCTP_HEARTBEAT_REQUEST; + chk->asoc = &stcb->asoc; + chk->send_size = sizeof(struct sctp_heartbeat_chunk); + MGETHDR(chk->data, M_DONTWAIT, MT_DATA); + if (chk->data == NULL) { + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return (0); + } + chk->data->m_data += SCTP_MIN_OVERHEAD; + chk->data->m_pkthdr.len = chk->data->m_len = chk->send_size; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->whoTo = net; + chk->whoTo->ref_count++; + /* Now we have a mbuf that we can fill in with the details */ + hb = mtod(chk->data, struct sctp_heartbeat_chunk *); + + /* fill out chunk header */ + hb->ch.chunk_type = SCTP_HEARTBEAT_REQUEST; + hb->ch.chunk_flags = 0; + hb->ch.chunk_length = htons(chk->send_size); + /* Fill out hb parameter */ + hb->heartbeat.hb_info.ph.param_type = htons(SCTP_HEARTBEAT_INFO); + hb->heartbeat.hb_info.ph.param_length = htons(sizeof(struct sctp_heartbeat_info_param)); + hb->heartbeat.hb_info.time_value_1 = now.tv_sec; + hb->heartbeat.hb_info.time_value_2 = now.tv_usec; + /* Did our user request this one, put it in */ + hb->heartbeat.hb_info.user_req = user_req; + hb->heartbeat.hb_info.addr_family = sin->sin_family; + hb->heartbeat.hb_info.addr_len = sin->sin_len; + if (net->dest_state & SCTP_ADDR_UNCONFIRMED) { + /* we only take from the entropy pool if the address is + * not confirmed. + */ + net->heartbeat_random1 = hb->heartbeat.hb_info.random_value1 = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep); + net->heartbeat_random2 = hb->heartbeat.hb_info.random_value2 = sctp_select_initial_TSN(&stcb->sctp_ep->sctp_ep); + } else { + net->heartbeat_random1 = hb->heartbeat.hb_info.random_value1 = 0; + net->heartbeat_random2 = hb->heartbeat.hb_info.random_value2 = 0; + } + if (sin->sin_family == AF_INET) { + memcpy(hb->heartbeat.hb_info.address, &sin->sin_addr, sizeof(sin->sin_addr)); + } else if (sin->sin_family == AF_INET6) { + /* We leave the scope the way it is in our lookup table. */ + sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; + memcpy(hb->heartbeat.hb_info.address, &sin6->sin6_addr, sizeof(sin6->sin6_addr)); + } else { + /* huh compiler bug */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Compiler bug bleeds a mbuf and a chunk\n"); + } +#endif + return (0); + } + /* ok we have a destination that needs a beat */ + /* lets do the theshold management Qiaobing style */ + if (user_req == 0) { + if (sctp_threshold_management(stcb->sctp_ep, stcb, net, + stcb->asoc.max_send_times)) { + /* we have lost the association, in a way this + * is quite bad since we really are one less time + * since we really did not send yet. This is the + * down side to the Q's style as defined in the RFC + * and not my alternate style defined in the RFC. + */ + if (chk->data != NULL) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return (-1); + } + } + net->hb_responded = 0; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("Inserting chunk for HB\n"); + } +#endif + TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next); + stcb->asoc.ctrl_queue_cnt++; + sctp_pegs[SCTP_HB_SENT]++; + /* + * Call directly med level routine to put out the chunk. It will + * always tumble out control chunks aka HB but it may even tumble + * out data too. + */ + if (user_req == 0) { + /* Ok now lets start the HB timer if it is NOT a user req */ + sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep, + stcb, net); + } + return (1); +} + +void +sctp_send_ecn_echo(struct sctp_tcb *stcb, struct sctp_nets *net, + uint32_t high_tsn) +{ + struct sctp_association *asoc; + struct sctp_ecne_chunk *ecne; + struct sctp_tmit_chunk *chk; + asoc = &stcb->asoc; + TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { + if (chk->rec.chunk_id == SCTP_ECN_ECHO) { + /* found a previous ECN_ECHO update it if needed */ + ecne = mtod(chk->data, struct sctp_ecne_chunk *); + ecne->tsn = htonl(high_tsn); + return; + } + } + /* nope could not find one to update so we must build one */ + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + return; + } + sctp_pegs[SCTP_ECNE_SENT]++; + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + chk->rec.chunk_id = SCTP_ECN_ECHO; + chk->asoc = &stcb->asoc; + chk->send_size = sizeof(struct sctp_ecne_chunk); + MGETHDR(chk->data, M_DONTWAIT, MT_DATA); + if (chk->data == NULL) { + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return; + } + chk->data->m_data += SCTP_MIN_OVERHEAD; + chk->data->m_pkthdr.len = chk->data->m_len = chk->send_size; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->whoTo = net; + chk->whoTo->ref_count++; + ecne = mtod(chk->data, struct sctp_ecne_chunk *); + ecne->ch.chunk_type = SCTP_ECN_ECHO; + ecne->ch.chunk_flags = 0; + ecne->ch.chunk_length = htons(sizeof(struct sctp_ecne_chunk)); + ecne->tsn = htonl(high_tsn); + TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next); + asoc->ctrl_queue_cnt++; +} + +void +sctp_send_packet_dropped(struct sctp_tcb *stcb, struct sctp_nets *net, + struct mbuf *m, int iphlen, int bad_crc) +{ + struct sctp_association *asoc; + struct sctp_pktdrop_chunk *drp; + struct sctp_tmit_chunk *chk; + uint8_t *datap; + int len; + unsigned int small_one; + struct ip *iph; + + long spc; + asoc = &stcb->asoc; + if (asoc->peer_supports_pktdrop == 0) { + /* peer must declare support before I + * send one. + */ + return; + } + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + return; + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + + iph = mtod(m, struct ip *); + if (iph == NULL) { + return; + } + if (iph->ip_v == IPVERSION) { + /* IPv4 */ +#if defined(__FreeBSD__) + len = chk->send_size = iph->ip_len; +#else + len = chk->send_size = (iph->ip_len - iphlen); +#endif + } else { + struct ip6_hdr *ip6h; + /* IPv6 */ + ip6h = mtod(m, struct ip6_hdr *); + len = chk->send_size = htons(ip6h->ip6_plen); + } + if ((len+iphlen) > m->m_pkthdr.len) { + /* huh */ + chk->send_size = len = m->m_pkthdr.len - iphlen; + } + chk->asoc = &stcb->asoc; + MGETHDR(chk->data, M_DONTWAIT, MT_DATA); + if (chk->data == NULL) { + jump_out: + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return; + } + if ((chk->send_size+sizeof(struct sctp_pktdrop_chunk)+SCTP_MIN_OVERHEAD) > MHLEN) { + MCLGET(chk->data, M_DONTWAIT); + if ((chk->data->m_flags & M_EXT) == 0) { + /* Give up */ + sctp_m_freem(chk->data); + chk->data = NULL; + goto jump_out; + } + } + chk->data->m_data += SCTP_MIN_OVERHEAD; + drp = mtod(chk->data, struct sctp_pktdrop_chunk *); + if (drp == NULL) { + sctp_m_freem(chk->data); + chk->data = NULL; + goto jump_out; + } + small_one = asoc->smallest_mtu; + if (small_one > MCLBYTES) { + /* Only one cluster worth of data MAX */ + small_one = MCLBYTES; + } + chk->book_size = (chk->send_size + sizeof(struct sctp_pktdrop_chunk) + + sizeof(struct sctphdr) + SCTP_MED_OVERHEAD); + if (chk->book_size > small_one) { + drp->ch.chunk_flags = SCTP_PACKET_TRUNCATED; + drp->trunc_len = htons(chk->send_size); + chk->send_size = small_one - (SCTP_MED_OVERHEAD + + sizeof(struct sctp_pktdrop_chunk) + + sizeof(struct sctphdr)); + len = chk->send_size; + } else { + /* no truncation needed */ + drp->ch.chunk_flags = 0; + drp->trunc_len = htons(0); + } + if (bad_crc) { + drp->ch.chunk_flags |= SCTP_BADCRC; + } + chk->send_size += sizeof(struct sctp_pktdrop_chunk); + chk->data->m_pkthdr.len = chk->data->m_len = chk->send_size; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + if (net) { + /* we should hit here */ + chk->whoTo = net; + } else { + chk->whoTo = asoc->primary_destination; + } + chk->whoTo->ref_count++; + chk->rec.chunk_id = SCTP_PACKET_DROPPED; + drp->ch.chunk_type = SCTP_PACKET_DROPPED; + drp->ch.chunk_length = htons(chk->send_size); + spc = stcb->sctp_socket->so_rcv.sb_hiwat; + if (spc < 0) { + spc = 0; + } + drp->bottle_bw = htonl(spc); + drp->current_onq = htonl(asoc->size_on_delivery_queue + + asoc->size_on_reasm_queue + + asoc->size_on_all_streams + + asoc->my_rwnd_control_len + + stcb->sctp_socket->so_rcv.sb_cc); + drp->reserved = 0; + datap = drp->data; + m_copydata(m, iphlen, len, datap); + TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next); + asoc->ctrl_queue_cnt++; +} + +void +sctp_send_cwr(struct sctp_tcb *stcb, struct sctp_nets *net, uint32_t high_tsn) +{ + struct sctp_association *asoc; + struct sctp_cwr_chunk *cwr; + struct sctp_tmit_chunk *chk; + + asoc = &stcb->asoc; + TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { + if (chk->rec.chunk_id == SCTP_ECN_CWR) { + /* found a previous ECN_CWR update it if needed */ + cwr = mtod(chk->data, struct sctp_cwr_chunk *); + if (compare_with_wrap(high_tsn, ntohl(cwr->tsn), + MAX_TSN)) { + cwr->tsn = htonl(high_tsn); + } + return; + } + } + /* nope could not find one to update so we must build one */ + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + return; + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + chk->rec.chunk_id = SCTP_ECN_CWR; + chk->asoc = &stcb->asoc; + chk->send_size = sizeof(struct sctp_cwr_chunk); + MGETHDR(chk->data, M_DONTWAIT, MT_DATA); + if (chk->data == NULL) { + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return; + } + chk->data->m_data += SCTP_MIN_OVERHEAD; + chk->data->m_pkthdr.len = chk->data->m_len = chk->send_size; + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->whoTo = net; + chk->whoTo->ref_count++; + cwr = mtod(chk->data, struct sctp_cwr_chunk *); + cwr->ch.chunk_type = SCTP_ECN_CWR; + cwr->ch.chunk_flags = 0; + cwr->ch.chunk_length = htons(sizeof(struct sctp_cwr_chunk)); + cwr->tsn = htonl(high_tsn); + TAILQ_INSERT_TAIL(&stcb->asoc.control_send_queue, chk, sctp_next); + asoc->ctrl_queue_cnt++; +} +static void +sctp_reset_the_streams(struct sctp_tcb *stcb, + struct sctp_stream_reset_request *req, int number_entries, uint16_t *list) +{ + int i; + + if (req->reset_flags & SCTP_RESET_ALL) { + for (i=0; iasoc.streamoutcnt; i++) { + stcb->asoc.strmout[i].next_sequence_sent = 0; + } + } else if (number_entries) { + for (i=0; i= stcb->asoc.streamoutcnt) { + /* no such stream */ + continue; + } + stcb->asoc.strmout[(list[i])].next_sequence_sent = 0; + } + } + sctp_ulp_notify(SCTP_NOTIFY_STR_RESET_SEND, stcb, number_entries, (void *)list); +} + +void +sctp_send_str_reset_ack(struct sctp_tcb *stcb, + struct sctp_stream_reset_request *req) +{ + struct sctp_association *asoc; + struct sctp_stream_reset_resp *strack; + struct sctp_tmit_chunk *chk; + uint32_t seq; + int number_entries, i; + uint8_t two_way=0, not_peer=0; + uint16_t *list=NULL; + + asoc = &stcb->asoc; + if (req->reset_flags & SCTP_RESET_ALL) + number_entries = 0; + else + number_entries = (ntohs(req->ph.param_length) - sizeof(struct sctp_stream_reset_request)) / sizeof(uint16_t); + + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + return; + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + chk->rec.chunk_id = SCTP_STREAM_RESET; + chk->asoc = &stcb->asoc; + chk->send_size = sizeof(struct sctp_stream_reset_resp) + (number_entries * sizeof(uint16_t)); + MGETHDR(chk->data, M_DONTWAIT, MT_DATA); + if (chk->data == NULL) { + strresp_jump_out: + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return; + } + chk->data->m_data += SCTP_MIN_OVERHEAD; + chk->data->m_pkthdr.len = chk->data->m_len = SCTP_SIZE32(chk->send_size); + if (M_TRAILINGSPACE(chk->data) < (int)SCTP_SIZE32(chk->send_size)) { + MCLGET(chk->data, M_DONTWAIT); + if ((chk->data->m_flags & M_EXT) == 0) { + /* Give up */ + sctp_m_freem(chk->data); + chk->data = NULL; + goto strresp_jump_out; + } + chk->data->m_data += SCTP_MIN_OVERHEAD; + } + if (M_TRAILINGSPACE(chk->data) < (int)SCTP_SIZE32(chk->send_size)) { + /* can't do it, no room */ + /* Give up */ + sctp_m_freem(chk->data); + chk->data = NULL; + goto strresp_jump_out; + + } + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->whoTo = asoc->primary_destination; + chk->whoTo->ref_count++; + strack = mtod(chk->data, struct sctp_stream_reset_resp *); + + strack->ch.chunk_type = SCTP_STREAM_RESET; + strack->ch.chunk_flags = 0; + strack->ch.chunk_length = htons(chk->send_size); + + memset(strack->sr_resp.reset_pad, 0, sizeof(strack->sr_resp.reset_pad)); + + strack->sr_resp.ph.param_type = ntohs(SCTP_STR_RESET_RESPONSE); + strack->sr_resp.ph.param_length = htons((chk->send_size - sizeof(struct sctp_chunkhdr))); + + + + if (chk->send_size % 4) { + /* need a padding for the end */ + int pad; + uint8_t *end; + end = (uint8_t *)((caddr_t)strack + chk->send_size); + pad = chk->send_size % 4; + for (i = 0; i < pad; i++) { + end[i] = 0; + } + chk->send_size += pad; + } + + /* actual response */ + if (req->reset_flags & SCTP_RESET_YOUR) { + strack->sr_resp.reset_flags = SCTP_RESET_PERFORMED; + } else { + strack->sr_resp.reset_flags = 0; + } + + /* copied from reset request */ + strack->sr_resp.reset_req_seq_resp = req->reset_req_seq; + seq = ntohl(req->reset_req_seq); + + list = req->list_of_streams; + /* copy the un-converted network byte order streams */ + for (i=0; isr_resp.list_of_streams[i] = list[i]; + } + if (asoc->str_reset_seq_in == seq) { + /* is it the next expected? */ + asoc->str_reset_seq_in++; + strack->sr_resp.reset_at_tsn = htonl(asoc->sending_seq); + asoc->str_reset_sending_seq = asoc->sending_seq; + if (number_entries) { + int i; + uint16_t temp; + /* convert them to host byte order */ + for (i=0 ; ireset_flags & SCTP_RESET_YOUR) { + /* reset my outbound streams */ + sctp_reset_the_streams(stcb, req , number_entries, list); + } + if (req->reset_flags & SCTP_RECIPRICAL) { + /* reset peer too */ + sctp_send_str_reset_req(stcb, number_entries, list, two_way, not_peer); + } + + } else { + /* no its a retran so I must just ack and do nothing */ + strack->sr_resp.reset_at_tsn = htonl(asoc->str_reset_sending_seq); + } + strack->sr_resp.cumulative_tsn = htonl(asoc->cumulative_tsn); + TAILQ_INSERT_TAIL(&asoc->control_send_queue, + chk, + sctp_next); + asoc->ctrl_queue_cnt++; +} + + +void +sctp_send_str_reset_req(struct sctp_tcb *stcb, + int number_entrys, uint16_t *list, uint8_t two_way, uint8_t not_peer) +{ + /* Send a stream reset request. The number_entrys may be 0 and list NULL + * if the request is to reset all streams. If two_way is true then we + * not only request a RESET of the received streams but we also + * request the peer to send a reset req to us too. + * Flag combinations in table: + * + * two_way | not_peer | = | Flags + * ------------------------------ + * 0 | 0 | = | SCTP_RESET_YOUR (just the peer) + * 1 | 0 | = | SCTP_RESET_YOUR | SCTP_RECIPRICAL (both sides) + * 0 | 1 | = | Not a Valid Request (not anyone) + * 1 | 1 | = | SCTP_RESET_RECIPRICAL (Just local host) + */ + struct sctp_association *asoc; + struct sctp_stream_reset_req *strreq; + struct sctp_tmit_chunk *chk; + + + asoc = &stcb->asoc; + if (asoc->stream_reset_outstanding) { + /* Already one pending, must get ACK back + * to clear the flag. + */ + return; + } + + if ((two_way == 0) && (not_peer == 1)) { + /* not a valid request */ + return; + } + + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + return; + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + chk->rec.chunk_id = SCTP_STREAM_RESET; + chk->asoc = &stcb->asoc; + chk->send_size = sizeof(struct sctp_stream_reset_req) + (number_entrys * sizeof(uint16_t)); + MGETHDR(chk->data, M_DONTWAIT, MT_DATA); + if (chk->data == NULL) { + strreq_jump_out: + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return; + } + chk->data->m_data += SCTP_MIN_OVERHEAD; + chk->data->m_pkthdr.len = chk->data->m_len = SCTP_SIZE32(chk->send_size); + if (M_TRAILINGSPACE(chk->data) < (int)SCTP_SIZE32(chk->send_size)) { + MCLGET(chk->data, M_DONTWAIT); + if ((chk->data->m_flags & M_EXT) == 0) { + /* Give up */ + sctp_m_freem(chk->data); + chk->data = NULL; + goto strreq_jump_out; + } + chk->data->m_data += SCTP_MIN_OVERHEAD; + } + if (M_TRAILINGSPACE(chk->data) < (int)SCTP_SIZE32(chk->send_size)) { + /* can't do it, no room */ + /* Give up */ + sctp_m_freem(chk->data); + chk->data = NULL; + goto strreq_jump_out; + } + chk->sent = SCTP_DATAGRAM_UNSENT; + chk->snd_count = 0; + chk->whoTo = asoc->primary_destination; + chk->whoTo->ref_count++; + + strreq = mtod(chk->data, struct sctp_stream_reset_req *); + strreq->ch.chunk_type = SCTP_STREAM_RESET; + strreq->ch.chunk_flags = 0; + strreq->ch.chunk_length = htons(chk->send_size); + + strreq->sr_req.ph.param_type = ntohs(SCTP_STR_RESET_REQUEST); + strreq->sr_req.ph.param_length = htons((chk->send_size - sizeof(struct sctp_chunkhdr))); + + if (chk->send_size % 4) { + /* need a padding for the end */ + int pad, i; + uint8_t *end; + end = (uint8_t *)((caddr_t)strreq + chk->send_size); + pad = chk->send_size % 4; + for (i=0; isend_size += pad; + } + + strreq->sr_req.reset_flags = 0; + if (number_entrys == 0) { + strreq->sr_req.reset_flags |= SCTP_RESET_ALL; + } + if (two_way == 0) { + strreq->sr_req.reset_flags |= SCTP_RESET_YOUR; + } else { + if (not_peer == 0) { + strreq->sr_req.reset_flags |= SCTP_RECIPRICAL | SCTP_RESET_YOUR; + } else { + strreq->sr_req.reset_flags |= SCTP_RECIPRICAL; + } + } + memset(strreq->sr_req.reset_pad, 0, sizeof(strreq->sr_req.reset_pad)); + strreq->sr_req.reset_req_seq = htonl(asoc->str_reset_seq_out); + if (number_entrys) { + /* populate the specific entry's */ + int i; + for (i=0; i < number_entrys; i++) { + strreq->sr_req.list_of_streams[i] = htons(list[i]); + } + } + TAILQ_INSERT_TAIL(&asoc->control_send_queue, + chk, + sctp_next); + asoc->ctrl_queue_cnt++; + sctp_timer_start(SCTP_TIMER_TYPE_STRRESET, stcb->sctp_ep, stcb, chk->whoTo); + asoc->stream_reset_outstanding = 1; +} + +void +sctp_send_abort(struct mbuf *m, int iphlen, struct sctphdr *sh, uint32_t vtag, + struct mbuf *err_cause) +{ + /* + * Formulate the abort message, and send it back down. + */ + struct mbuf *mout; + struct sctp_abort_msg *abm; + struct ip *iph, *iph_out; + struct ip6_hdr *ip6, *ip6_out; + int iphlen_out; + + /* don't respond to ABORT with ABORT */ + if (sctp_is_there_an_abort_here(m, iphlen, &vtag)) { + if (err_cause) + sctp_m_freem(err_cause); + return; + } + MGETHDR(mout, M_DONTWAIT, MT_HEADER); + if (mout == NULL) { + if (err_cause) + sctp_m_freem(err_cause); + return; + } + iph = mtod(m, struct ip *); + iph_out = NULL; + ip6_out = NULL; + if (iph->ip_v == IPVERSION) { + iph_out = mtod(mout, struct ip *); + mout->m_len = sizeof(*iph_out) + sizeof(*abm); + mout->m_next = err_cause; + + /* Fill in the IP header for the ABORT */ + iph_out->ip_v = IPVERSION; + iph_out->ip_hl = (sizeof(struct ip) / 4); + iph_out->ip_tos = (u_char)0; + iph_out->ip_id = 0; + iph_out->ip_off = 0; + iph_out->ip_ttl = MAXTTL; + iph_out->ip_p = IPPROTO_SCTP; + iph_out->ip_src.s_addr = iph->ip_dst.s_addr; + iph_out->ip_dst.s_addr = iph->ip_src.s_addr; + /* let IP layer calculate this */ + iph_out->ip_sum = 0; + + iphlen_out = sizeof(*iph_out); + abm = (struct sctp_abort_msg *)((caddr_t)iph_out + iphlen_out); + } else if (iph->ip_v == (IPV6_VERSION >> 4)) { + ip6 = (struct ip6_hdr *)iph; + ip6_out = mtod(mout, struct ip6_hdr *); + mout->m_len = sizeof(*ip6_out) + sizeof(*abm); + mout->m_next = err_cause; + + /* Fill in the IP6 header for the ABORT */ + ip6_out->ip6_flow = ip6->ip6_flow; + ip6_out->ip6_hlim = ip6_defhlim; + ip6_out->ip6_nxt = IPPROTO_SCTP; + ip6_out->ip6_src = ip6->ip6_dst; + ip6_out->ip6_dst = ip6->ip6_src; + + iphlen_out = sizeof(*ip6_out); + abm = (struct sctp_abort_msg *)((caddr_t)ip6_out + iphlen_out); + } else { + /* Currently not supported */ + return; + } + + abm->sh.src_port = sh->dest_port; + abm->sh.dest_port = sh->src_port; + abm->sh.checksum = 0; + if (vtag == 0) { + abm->sh.v_tag = sh->v_tag; + abm->msg.ch.chunk_flags = SCTP_HAD_NO_TCB; + } else { + abm->sh.v_tag = htonl(vtag); + abm->msg.ch.chunk_flags = 0; + } + abm->msg.ch.chunk_type = SCTP_ABORT_ASSOCIATION; + + if (err_cause) { + struct mbuf *m_tmp = err_cause; + int err_len = 0; + /* get length of the err_cause chain */ + while (m_tmp != NULL) { + err_len += m_tmp->m_len; + m_tmp = m_tmp->m_next; + } + mout->m_pkthdr.len = mout->m_len + err_len; + if (err_len % 4) { + /* need pad at end of chunk */ + u_int32_t cpthis=0; + int padlen; + padlen = 4 - (mout->m_pkthdr.len % 4); + m_copyback(mout, mout->m_pkthdr.len, padlen, (caddr_t)&cpthis); + } + abm->msg.ch.chunk_length = htons(sizeof(abm->msg.ch) + err_len); + } else { + mout->m_pkthdr.len = mout->m_len; + abm->msg.ch.chunk_length = htons(sizeof(abm->msg.ch)); + } + + /* add checksum */ + if ((sctp_no_csum_on_loopback) && + (m->m_pkthdr.rcvif) && + (m->m_pkthdr.rcvif->if_type == IFT_LOOP)) { + abm->sh.checksum = 0; + } else { + abm->sh.checksum = sctp_calculate_sum(mout, NULL, iphlen_out); + } + + /* zap the rcvif, it should be null */ + mout->m_pkthdr.rcvif = 0; + if (iph_out != NULL) { + struct route ro; + + /* zap the stack pointer to the route */ + bzero(&ro, sizeof ro); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { + printf("sctp_send_abort calling ip_output:\n"); + sctp_print_address_pkt(iph_out, &abm->sh); + } +#endif + /* set IPv4 length */ +#if defined(__FreeBSD__) + iph_out->ip_len = mout->m_pkthdr.len; +#else + iph_out->ip_len = htons(mout->m_pkthdr.len); +#endif + /* out it goes */ + (void)ip_output(mout, 0, &ro, IP_RAWOUTPUT, NULL +#if defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD_version >= 480000) || defined(__NetBSD__) + , NULL +#endif + ); + /* Free the route if we got one back */ + if (ro.ro_rt) + RTFREE(ro.ro_rt); + } else if (ip6_out != NULL) { +#ifdef NEW_STRUCT_ROUTE + struct route ro; +#else + struct route_in6 ro; +#endif + + /* zap the stack pointer to the route */ + bzero(&ro, sizeof(ro)); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { + printf("sctp_send_abort calling ip6_output:\n"); + sctp_print_address_pkt((struct ip *)ip6_out, &abm->sh); + } +#endif + ip6_output(mout, NULL, &ro, 0, NULL, NULL +#if defined(__NetBSD__) + , NULL +#endif +#if (defined(__FreeBSD__) && __FreeBSD_version >= 480000) + , NULL +#endif + ); + /* Free the route if we got one back */ + if (ro.ro_rt) + RTFREE(ro.ro_rt); + } + sctp_pegs[SCTP_DATAGRAMS_SENT]++; +} + +void +sctp_send_operr_to(struct mbuf *m, int iphlen, + struct mbuf *scm, + uint32_t vtag) +{ + struct sctphdr *ihdr; + int retcode; + struct sctphdr *ohdr; + struct sctp_chunkhdr *ophdr; + + struct ip *iph; +#ifdef SCTP_DEBUG + struct sockaddr_in6 lsa6, fsa6; +#endif + uint32_t val; + iph = mtod(m, struct ip *); + ihdr = (struct sctphdr *)((caddr_t)iph + iphlen); + if (!(scm->m_flags & M_PKTHDR)) { + /* must be a pkthdr */ + printf("Huh, not a packet header in send_operr\n"); + m_freem(scm); + return; + } + M_PREPEND(scm, (sizeof(struct sctphdr) + sizeof(struct sctp_chunkhdr)), M_DONTWAIT); + if (scm == NULL) { + /* can't send because we can't add a mbuf */ + return; + } + ohdr = mtod(scm, struct sctphdr *); + ohdr->src_port = ihdr->dest_port; + ohdr->dest_port = ihdr->src_port; + ohdr->v_tag = vtag; + ohdr->checksum = 0; + ophdr = (struct sctp_chunkhdr *)(ohdr + 1); + ophdr->chunk_type = SCTP_OPERATION_ERROR; + ophdr->chunk_flags = 0; + ophdr->chunk_length = htons(scm->m_pkthdr.len - sizeof(struct sctphdr)); + if (scm->m_pkthdr.len % 4) { + /* need padding */ + u_int32_t cpthis=0; + int padlen; + padlen = 4 - (scm->m_pkthdr.len % 4); + m_copyback(scm, scm->m_pkthdr.len, padlen, (caddr_t)&cpthis); + } + if ((sctp_no_csum_on_loopback) && + (m->m_pkthdr.rcvif) && + (m->m_pkthdr.rcvif->if_type == IFT_LOOP)) { + val = 0; + } else { + val = sctp_calculate_sum(scm, NULL, 0); + } + ohdr->checksum = val; + if (iph->ip_v == IPVERSION) { + /* V4 */ + struct ip *out; + struct route ro; + M_PREPEND(scm, sizeof(struct ip), M_DONTWAIT); + if (scm == NULL) + return; + bzero(&ro, sizeof ro); + out = mtod(scm, struct ip *); + out->ip_v = iph->ip_v; + out->ip_hl = (sizeof(struct ip)/4); + out->ip_tos = iph->ip_tos; + out->ip_id = iph->ip_id; + out->ip_off = 0; + out->ip_ttl = MAXTTL; + out->ip_p = IPPROTO_SCTP; + out->ip_sum = 0; + out->ip_src = iph->ip_dst; + out->ip_dst = iph->ip_src; +#if defined(__FreeBSD__) + out->ip_len = scm->m_pkthdr.len; +#else + out->ip_len = htons(scm->m_pkthdr.len); +#endif + retcode = ip_output(scm, 0, &ro, IP_RAWOUTPUT, NULL +#if defined(__OpenBSD__) || (defined(__FreeBSD__) && __FreeBSD_version >= 480000) || defined(__NetBSD__) + , NULL +#endif + ); + sctp_pegs[SCTP_DATAGRAMS_SENT]++; + /* Free the route if we got one back */ + if (ro.ro_rt) + RTFREE(ro.ro_rt); + } else { + /* V6 */ +#ifdef NEW_STRUCT_ROUTE + struct route ro; +#else + struct route_in6 ro; +#endif + struct ip6_hdr *out6, *in6; + + M_PREPEND(scm, sizeof(struct ip6_hdr), M_DONTWAIT); + if (scm == NULL) + return; + bzero(&ro, sizeof ro); + in6 = mtod(m, struct ip6_hdr *); + out6 = mtod(scm, struct ip6_hdr *); + out6->ip6_flow = in6->ip6_flow; + out6->ip6_hlim = ip6_defhlim; + out6->ip6_nxt = IPPROTO_SCTP; + out6->ip6_src = in6->ip6_dst; + out6->ip6_dst = in6->ip6_src; + +#ifdef SCTP_DEBUG + bzero(&lsa6, sizeof(lsa6)); + lsa6.sin6_len = sizeof(lsa6); + lsa6.sin6_family = AF_INET6; + lsa6.sin6_addr = out6->ip6_src; + bzero(&fsa6, sizeof(fsa6)); + fsa6.sin6_len = sizeof(fsa6); + fsa6.sin6_family = AF_INET6; + fsa6.sin6_addr = out6->ip6_dst; + if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { + printf("sctp_operr_to calling ipv6 output:\n"); + printf("src: "); + sctp_print_address((struct sockaddr *)&lsa6); + printf("dst "); + sctp_print_address((struct sockaddr *)&fsa6); + } +#endif /* SCTP_DEBUG */ + ip6_output(scm, NULL, &ro, 0, NULL, NULL +#if defined(__NetBSD__) + , NULL +#endif +#if (defined(__FreeBSD__) && __FreeBSD_version >= 480000) + , NULL +#endif + ); + sctp_pegs[SCTP_DATAGRAMS_SENT]++; + /* Free the route if we got one back */ + if (ro.ro_rt) + RTFREE(ro.ro_rt); + } +} + +static int +sctp_copy_one(struct mbuf *m, struct uio *uio, int cpsz, int resv_upfront, int *mbcnt) +{ + int left, cancpy, willcpy, error; + left = cpsz; + + if (m == NULL) { + /* TSNH */ + *mbcnt = 0; + return (ENOMEM); + } + m->m_len = 0; + if ((left+resv_upfront) > (int)MHLEN) { + MCLGET(m, M_WAIT); + if (m == NULL) { + *mbcnt = 0; + return (ENOMEM); + } + if ((m->m_flags & M_EXT) == 0) { + *mbcnt = 0; + return (ENOMEM); + } + *mbcnt += m->m_ext.ext_size; + } + *mbcnt += MSIZE; + cancpy = M_TRAILINGSPACE(m); + willcpy = min(cancpy, left); + if ((willcpy + resv_upfront) > cancpy) { + willcpy -= resv_upfront; + } + while (left > 0) { + /* Align data to the end */ + if ((m->m_flags & M_EXT) == 0) { + if (m->m_flags & M_PKTHDR) { + MH_ALIGN(m, willcpy); + } else { + M_ALIGN(m, willcpy); + } + } else { + MC_ALIGN(m, willcpy); + } + error = uiomove(mtod(m, caddr_t), willcpy, uio); + if (error) { + return (error); + } + m->m_len = willcpy; + m->m_nextpkt = 0; + left -= willcpy; + if (left > 0) { + MGET(m->m_next, M_WAIT, MT_DATA); + if (m->m_next == NULL) { + *mbcnt = 0; + return (ENOMEM); + } + m = m->m_next; + m->m_len = 0; + *mbcnt += MSIZE; + if (left > (int)MHLEN) { + MCLGET(m, M_WAIT); + if (m == NULL) { + *mbcnt = 0; + return (ENOMEM); + } + if ((m->m_flags & M_EXT) == 0) { + *mbcnt = 0; + return (ENOMEM); + } + *mbcnt += m->m_ext.ext_size; + } + cancpy = M_TRAILINGSPACE(m); + willcpy = min(cancpy, left); + } + } + return (0); +} + +static int +sctp_copy_it_in(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_association *asoc, + struct sctp_nets *net, + struct sctp_sndrcvinfo *srcv, + struct uio *uio, + int flags) +{ + /* This routine must be very careful in + * its work. Protocol processing is + * up and running so care must be taken to + * spl...() when you need to do something + * that may effect the stcb/asoc. The sb is + * locked however. When data is copied the + * protocol processing should be enabled since + * this is a slower operation... + */ + struct socket *so; + int error = 0; + int s; + int frag_size, mbcnt = 0, mbcnt_e = 0; + unsigned int sndlen; + unsigned int tot_demand; + int tot_out, dataout; + struct sctp_tmit_chunk *chk; + struct mbuf *mm; + struct sctp_stream_out *strq; + uint32_t my_vtag; + int resv_in_first; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + so = stcb->sctp_socket; + chk = NULL; + mm = NULL; + + sndlen = uio->uio_resid; + /* lock the socket buf */ + SOCKBUF_LOCK(&so->so_snd); + error = sblock(&so->so_snd, SBLOCKWAIT(flags)); + if (error) + goto out_locked; + + /* will it ever fit ? */ + if (sndlen > so->so_snd.sb_hiwat) { + /* It will NEVER fit */ + error = EMSGSIZE; + splx(s); + goto release; + } + /* Do I need to block? */ + if ((so->so_snd.sb_hiwat < + (sndlen + asoc->total_output_queue_size)) || + (asoc->chunks_on_out_queue > sctp_max_chunks_on_queue) || + (asoc->total_output_mbuf_queue_size > + so->so_snd.sb_mbmax) + ) { + /* prune any prsctp bufs out */ + if (asoc->peer_supports_prsctp) { + sctp_prune_prsctp(stcb, asoc, srcv, sndlen); + } + /* + * We store off a pointer to the endpoint. + * Since on return from this we must check to + * see if an so_error is set. If so we may have + * been reset and our stcb destroyed. Returning + * an error will flow back to the user... + */ + while ((so->so_snd.sb_hiwat < + (sndlen + asoc->total_output_queue_size)) || + (asoc->chunks_on_out_queue > + sctp_max_chunks_on_queue) || + (asoc->total_output_mbuf_queue_size > + so->so_snd.sb_mbmax) + ) { + if ((so->so_state & SS_NBIO) +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + || (flags & MSG_NBIO) +#endif + ) { + /* Non-blocking io in place */ + error = EWOULDBLOCK; + goto release; + } + inp->sctp_tcb_at_block = (void *)stcb; + inp->error_on_block = 0; +#ifdef SCTP_BLK_LOGGING + sctp_log_block(SCTP_BLOCK_LOG_INTO_BLK, + so, asoc); +#endif + sbunlock(&so->so_snd); + SCTP_TCB_UNLOCK(stcb); + error = sbwait(&so->so_snd); + SCTP_INP_RLOCK(inp); + if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || + (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { + /* Should I really unlock ? */ + SCTP_INP_RUNLOCK(inp); + error = EFAULT; + goto out_locked; + } + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + + inp->sctp_tcb_at_block = 0; +#ifdef SCTP_BLK_LOGGING + sctp_log_block(SCTP_BLOCK_LOG_OUTOF_BLK, + so, asoc); +#endif + if (inp->error_on_block) { + /* + * if our asoc was killed, the free code + * (in sctp_pcb.c) will save a error in + * here for us + */ + error = inp->error_on_block; + splx(s); + goto out_locked; + } + if (error) { + splx(s); + goto out_locked; + } + /* did we encounter a socket error? */ + if (so->so_error) { + error = so->so_error; + splx(s); + goto out_locked; + } + error = sblock(&so->so_snd, M_WAITOK); + if (error) { + /* Can't aquire the lock */ + splx(s); + goto out_locked; + } +#if defined(__FreeBSD__) && __FreeBSD_version >= 502115 + if (so->so_rcv.sb_state & SBS_CANTSENDMORE) { +#else + if (so->so_state & SS_CANTSENDMORE) { +#endif + /* The socket is now set not to sendmore.. its gone */ + error = EPIPE; + splx(s); + goto release; + } + if (so->so_error) { + error = so->so_error; + splx(s); + goto release; + } + if (asoc->peer_supports_prsctp) { + sctp_prune_prsctp(stcb, asoc, srcv, sndlen); + } + } + } + dataout = tot_out = uio->uio_resid; + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + resv_in_first = SCTP_MED_OVERHEAD; + } else { + resv_in_first = SCTP_MED_V4_OVERHEAD; + } + + /* Are we aborting? */ + if (srcv->sinfo_flags & MSG_ABORT) { + if ((SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_WAIT) && + (SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_ECHOED)) { + /* It has to be up before we abort */ + /* how big is the user initiated abort? */ + + /* I wonder about doing a MGET without a splnet set. + * it is done that way in the sosend code so I guess + * it is ok :-0 + */ + MGETHDR(mm, M_WAIT, MT_DATA); + if (mm) { + struct sctp_paramhdr *ph; + + tot_demand = (tot_out + sizeof(struct sctp_paramhdr)); + if (tot_demand > MHLEN) { + if (tot_demand > MCLBYTES) { + /* truncate user data */ + tot_demand = MCLBYTES; + tot_out = tot_demand - sizeof(struct sctp_paramhdr); + } + MCLGET(mm, M_WAIT); + if ((mm->m_flags & M_EXT) == 0) { + /* truncate further */ + tot_demand = MHLEN; + tot_out = tot_demand - sizeof(struct sctp_paramhdr); + } + } + /* now move forward the data pointer */ + ph = mtod(mm, struct sctp_paramhdr *); + ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT); + ph->param_length = htons((sizeof(struct sctp_paramhdr) + tot_out)); + ph++; + mm->m_pkthdr.len = tot_out + sizeof(struct sctp_paramhdr); + mm->m_len = mm->m_pkthdr.len; + error = uiomove((caddr_t)ph, (int)tot_out, uio); + if (error) { + /* + * Here if we can't get his data we + * still abort we just don't get to + * send the users note :-0 + */ + sctp_m_freem(mm); + mm = NULL; + } + } + sbunlock(&so->so_snd); + SOCKBUF_UNLOCK(&so->so_snd); + sctp_abort_an_association(stcb->sctp_ep, stcb, + SCTP_RESPONSE_TO_USER_REQ, + mm); + mm = NULL; + splx(s); + goto out_notlocked; + } + splx(s); + goto release; + } + + /* Now can we send this? */ + if ((SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) || + (asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) { + /* got data while shutting down */ + error = ECONNRESET; + splx(s); + goto release; + } + /* Is the stream no. valid? */ + if (srcv->sinfo_stream >= asoc->streamoutcnt) { + /* Invalid stream number */ + error = EINVAL; + splx(s); + goto release; + } + if (asoc->strmout == NULL) { + /* huh? software error */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("software error in sctp_copy_it_in\n"); + } +#endif + error = EFAULT; + splx(s); + goto release; + } + if ((srcv->sinfo_flags & MSG_EOF) && + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) && + (tot_out == 0)) { + splx(s); + goto zap_by_it_now; + } + if (tot_out == 0) { + /* not allowed */ + error = EMSGSIZE; + splx(s); + goto release; + } + /* save off the tag */ + my_vtag = asoc->my_vtag; + strq = &asoc->strmout[srcv->sinfo_stream]; + /* First lets figure out the "chunking" point */ + frag_size = sctp_get_frag_point(stcb, asoc); + + /* two choices here, it all fits in one chunk or + * we need multiple chunks. + */ + splx(s); + SOCKBUF_UNLOCK(&so->so_snd); + if (tot_out <= frag_size) { + /* no need to setup a template */ + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + error = ENOMEM; + SOCKBUF_LOCK(&so->so_snd); + goto release; + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + asoc->chunks_on_out_queue++; + MGETHDR(mm, M_WAIT, MT_DATA); + if (mm == NULL) { + error = ENOMEM; + goto clean_up; + } + error = sctp_copy_one(mm, uio, tot_out, resv_in_first, &mbcnt_e); + if (error) + goto clean_up; + sctp_prepare_chunk(chk, stcb, srcv, strq, net); + chk->mbcnt = mbcnt_e; + mbcnt += mbcnt_e; + mbcnt_e = 0; + mm->m_pkthdr.len = tot_out; + chk->data = mm; + mm = NULL; + + /* the actual chunk flags */ + chk->rec.data.rcv_flags |= SCTP_DATA_NOT_FRAG; + chk->whoTo->ref_count++; + + /* fix up the send_size if it is not present */ + chk->send_size = tot_out; + chk->book_size = chk->send_size; + /* ok, we are commited */ + if ((srcv->sinfo_flags & MSG_UNORDERED) == 0) { + /* bump the ssn if we are unordered. */ + strq->next_sequence_sent++; + } + if (chk->flags & SCTP_PR_SCTP_BUFFER) { + asoc->sent_queue_cnt_removeable++; + } +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + if ((asoc->state == 0) || + (my_vtag != asoc->my_vtag) || + (so != inp->sctp_socket) || + (inp->sctp_socket == 0)) { + /* connection was aborted */ + splx(s); + error = ECONNRESET; + goto clean_up; + } + asoc->stream_queue_cnt++; + TAILQ_INSERT_TAIL(&strq->outqueue, chk, sctp_next); + /* now check if this stream is on the wheel */ + if ((strq->next_spoke.tqe_next == NULL) && + (strq->next_spoke.tqe_prev == NULL)) { + /* Insert it on the wheel since it is not + * on it currently + */ + sctp_insert_on_wheel(asoc, strq); + } + splx(s); +clean_up: + if (error) { + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + SOCKBUF_LOCK(&so->so_snd); + goto release; + } + } else { + /* we need to setup a template */ + struct sctp_tmit_chunk template; + struct sctpchunk_listhead tmp; + + /* setup the template */ + sctp_prepare_chunk(&template, stcb, srcv, strq, net); + + /* Prepare the temp list */ + TAILQ_INIT(&tmp); + + /* Template is complete, now time for the work */ + while (tot_out > 0) { + /* Get a chunk */ + chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (chk == NULL) { + /* + * ok we must spin through and dump anything + * we have allocated and then jump to the + * no_membad + */ + error = ENOMEM; + } + sctppcbinfo.ipi_count_chunk++; + asoc->chunks_on_out_queue++; + + sctppcbinfo.ipi_gencnt_chunk++; + *chk = template; + chk->whoTo->ref_count++; + MGETHDR(chk->data, M_WAIT, MT_DATA); + if (chk->data == NULL) { + error = ENOMEM; + goto temp_clean_up; + } + tot_demand = min(tot_out, frag_size); + error = sctp_copy_one(chk->data, uio, tot_demand , resv_in_first, &mbcnt_e); + if (error) + goto temp_clean_up; + /* now fix the chk->send_size */ + chk->mbcnt = mbcnt_e; + mbcnt += mbcnt_e; + mbcnt_e = 0; + chk->send_size = tot_demand; + chk->data->m_pkthdr.len = tot_demand; + chk->book_size = chk->send_size; + if (chk->flags & SCTP_PR_SCTP_BUFFER) { + asoc->sent_queue_cnt_removeable++; + } + TAILQ_INSERT_TAIL(&tmp, chk, sctp_next); + tot_out -= tot_demand; + } + /* Now the tmp list holds all chunks and data */ + if ((srcv->sinfo_flags & MSG_UNORDERED) == 0) { + /* bump the ssn if we are unordered. */ + strq->next_sequence_sent++; + } + /* Mark the first/last flags. This will + * result int a 3 for a single item on the list + */ + chk = TAILQ_FIRST(&tmp); + chk->rec.data.rcv_flags |= SCTP_DATA_FIRST_FRAG; + chk = TAILQ_LAST(&tmp, sctpchunk_listhead); + chk->rec.data.rcv_flags |= SCTP_DATA_LAST_FRAG; + + /* now move it to the streams actual queue */ + /* first stop protocol processing */ +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + if ((asoc->state == 0) || + (my_vtag != asoc->my_vtag) || + (so != inp->sctp_socket) || + (inp->sctp_socket == 0)) { + /* connection was aborted */ + splx(s); + error = ECONNRESET; + goto temp_clean_up; + } + chk = TAILQ_FIRST(&tmp); + while (chk) { + chk->data->m_nextpkt = 0; + TAILQ_REMOVE(&tmp, chk, sctp_next); + asoc->stream_queue_cnt++; + TAILQ_INSERT_TAIL(&strq->outqueue, chk, sctp_next); + chk = TAILQ_FIRST(&tmp); + } + /* now check if this stream is on the wheel */ + if ((strq->next_spoke.tqe_next == NULL) && + (strq->next_spoke.tqe_prev == NULL)) { + /* Insert it on the wheel since it is not + * on it currently + */ + sctp_insert_on_wheel(asoc, strq); + } + /* Ok now we can allow pping */ + splx(s); +temp_clean_up: + if (error) { + SOCKBUF_LOCK(&so->so_snd); + chk = TAILQ_FIRST(&tmp); + while (chk) { + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + TAILQ_REMOVE(&tmp, chk, sctp_next); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + asoc->chunks_on_out_queue--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&tmp); + } + goto release; + } + } +zap_by_it_now: +#ifdef SCTP_MBCNT_LOGGING + sctp_log_mbcnt(SCTP_LOG_MBCNT_INCREASE, + asoc->total_output_queue_size, + dataout, + asoc->total_output_mbuf_queue_size, + mbcnt); +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + SOCKBUF_LOCK(&so->so_snd); + asoc->total_output_queue_size += dataout; + asoc->total_output_mbuf_queue_size += mbcnt; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + so->so_snd.sb_cc += dataout; + so->so_snd.sb_mbcnt += mbcnt; + } + if ((srcv->sinfo_flags & MSG_EOF) && + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) + ) { + int some_on_streamwheel = 0; + error = 0; + if (!TAILQ_EMPTY(&asoc->out_wheel)) { + /* Check to see if some data queued */ + struct sctp_stream_out *outs; + TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) { + if (!TAILQ_EMPTY(&outs->outqueue)) { + some_on_streamwheel = 1; + break; + } + } + } + if (TAILQ_EMPTY(&asoc->send_queue) && + TAILQ_EMPTY(&asoc->sent_queue) && + (some_on_streamwheel == 0)) { + /* there is nothing queued to send, so I'm done... */ + if ((SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) && + (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { + /* only send SHUTDOWN the first time through */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("%s:%d sends a shutdown\n", + __FILE__, + __LINE__ + ); + } +#endif + sctp_send_shutdown(stcb, stcb->asoc.primary_destination); + asoc->state = SCTP_STATE_SHUTDOWN_SENT; + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb, + asoc->primary_destination); + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb, + asoc->primary_destination); + } + } else { + /* + * we still got (or just got) data to send, so set + * SHUTDOWN_PENDING + */ + /* + * XXX sockets draft says that MSG_EOF should be sent + * with no data. currently, we will allow user data + * to be sent first and move to SHUTDOWN-PENDING + */ + asoc->state |= SCTP_STATE_SHUTDOWN_PENDING; + } + } + splx(s); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT2) { + printf("++total out:%d total_mbuf_out:%d\n", + (int)asoc->total_output_queue_size, + (int)asoc->total_output_mbuf_queue_size); + } +#endif + +release: + sbunlock(&so->so_snd); +out_locked: + SOCKBUF_UNLOCK(&so->so_snd); +out_notlocked: + if (mm) + sctp_m_freem(mm); + return (error); +} + + +int +sctp_sosend(struct socket *so, +#ifdef __NetBSD__ + struct mbuf *addr_mbuf, +#else + struct sockaddr *addr, +#endif + struct uio *uio, + struct mbuf *top, + struct mbuf *control, +#if defined(__NetBSD__) || defined(__APPLE__) + int flags +#else + int flags, +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + struct thread *p +#else + struct proc *p +#endif +#endif +) +{ + unsigned int sndlen; + int error, use_rcvinfo; + int s, queue_only = 0, queue_only_for_init=0; + int un_sent = 0; + int now_filled=0; + struct sctp_inpcb *inp; + struct sctp_tcb *stcb=NULL; + struct sctp_sndrcvinfo srcv; + struct timeval now; + struct sctp_nets *net; + struct sctp_association *asoc; + struct sctp_inpcb *t_inp; + int create_lock_applied = 0; +#if defined(__APPLE__) + struct proc *p = current_proc(); +#elif defined(__NetBSD__) + struct proc *p = curproc; /* XXX */ + struct sockaddr *addr = NULL; + if (addr_mbuf) + addr = mtod(addr_mbuf, struct sockaddr *); +#endif + + error = use_rcvinfo = 0; + net = NULL; + stcb = NULL; + asoc = NULL; + t_inp = inp = (struct sctp_inpcb *)so->so_pcb; + if (uio) + sndlen = uio->uio_resid; + else + sndlen = top->m_pkthdr.len; + + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + + if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && + (inp->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING)) { + /* The listner can NOT send */ + error = EFAULT; + splx(s); + goto out; + } + if (addr) { + SCTP_ASOC_CREATE_LOCK(inp); + if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || + (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { + /* Should I really unlock ? */ + error = EFAULT; + splx(s); + goto out; + + } + create_lock_applied = 1; + if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) && + (addr->sa_family == AF_INET6)) { + error = EINVAL; + splx(s); + goto out; + } + } + /* now we must find the assoc */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { + SCTP_INP_RUNLOCK(inp); + error = ENOTCONN; + splx(s); + goto out; + } + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + net = stcb->asoc.primary_destination; + } + /* get control */ + if (control) { + /* process cmsg snd/rcv info (maybe a assoc-id) */ + if (sctp_find_cmsg(SCTP_SNDRCV, (void *)&srcv, control, + sizeof(srcv))) { + /* got one */ + if (srcv.sinfo_flags & MSG_SENDALL) { + /* its a sendall */ + sctppcbinfo.mbuf_track--; + sctp_m_freem(control); + + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + return (sctp_sendall(inp, uio, top, &srcv)); + } + use_rcvinfo = 1; + } + } + if (stcb == NULL) { + /* Need to do a lookup */ + if (use_rcvinfo && srcv.sinfo_assoc_id) { + stcb = sctp_findassociation_ep_asocid(inp, srcv.sinfo_assoc_id); + /* + * Question: Should I error here if the assoc_id is + * no longer valid? i.e. I can't find it? + */ + if ((stcb) && + (addr != NULL)) { + /* Must locate the net structure */ + net = sctp_findnet(stcb, addr); + } + } + if (stcb == NULL) { + if (addr != NULL) { + /* Since we did not use findep we must + * increment it, and if we don't find a + * tcb decrement it. + */ + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + stcb = sctp_findassociation_ep_addr(&t_inp, addr, &net, NULL, NULL); + if (stcb == NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + } + } + } + if ((stcb == NULL) && + (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE)) { + error = ENOTCONN; + splx(s); + goto out; + } else if ((stcb == NULL) && (addr == NULL)) { + error = ENOENT; + splx(s); + goto out; + } else if (stcb == NULL) { + /* UDP style, we must go ahead and start the INIT process */ + if ((use_rcvinfo) && + (srcv.sinfo_flags & MSG_ABORT)) { + /* User asks to abort a non-existant asoc */ + error = ENOENT; + splx(s); + goto out; + } + /* get an asoc/stcb struct */ + stcb = sctp_aloc_assoc(inp, addr, 1, &error, 0); + if (stcb == NULL) { + /* Error is setup for us in the call */ + splx(s); + goto out; + } + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } else { + printf("Huh-3? create lock should have been on??\n"); + } + /* Turn on queue only flag to prevent data from being sent */ + queue_only = 1; + asoc = &stcb->asoc; + asoc->state = SCTP_STATE_COOKIE_WAIT; + SCTP_GETTIME_TIMEVAL(&asoc->time_entered); + if (control) { + /* see if a init structure exists in cmsg headers */ + struct sctp_initmsg initm; + int i; + if (sctp_find_cmsg(SCTP_INIT, (void *)&initm, control, sizeof(initm))) { + /* we have an INIT override of the default */ + if (initm.sinit_max_attempts) + asoc->max_init_times = initm.sinit_max_attempts; + if (initm.sinit_num_ostreams) + asoc->pre_open_streams = initm.sinit_num_ostreams; + if (initm.sinit_max_instreams) + asoc->max_inbound_streams = initm.sinit_max_instreams; + if (initm.sinit_max_init_timeo) + asoc->initial_init_rto_max = initm.sinit_max_init_timeo; + if (asoc->streamoutcnt < asoc->pre_open_streams) { + /* Default is NOT correct */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("Ok, defout:%d pre_open:%d\n", + asoc->streamoutcnt, asoc->pre_open_streams); + } +#endif + FREE(asoc->strmout, M_PCB); + asoc->strmout = NULL; + asoc->streamoutcnt = asoc->pre_open_streams; + + /* What happesn if this fails? .. we panic ...*/ + MALLOC(asoc->strmout, + struct sctp_stream_out *, + asoc->streamoutcnt * + sizeof(struct sctp_stream_out), + M_PCB, M_WAIT); + for (i = 0; i < asoc->streamoutcnt; i++) { + /* + * inbound side must be set to 0xffff, + * also NOTE when we get the INIT-ACK + * back (for INIT sender) we MUST + * reduce the count (streamoutcnt) but + * first check if we sent to any of the + * upper streams that were dropped (if + * some were). Those that were dropped + * must be notified to the upper layer + * as failed to send. + */ + asoc->strmout[i].next_sequence_sent = 0x0; + TAILQ_INIT(&asoc->strmout[i].outqueue); + asoc->strmout[i].stream_no = i; + asoc->strmout[i].next_spoke.tqe_next = 0; + asoc->strmout[i].next_spoke.tqe_prev = 0; + } + } + } + + } + /* out with the INIT */ + queue_only_for_init = 1; + sctp_send_initiate(inp, stcb); + /* + * we may want to dig in after this call and adjust the MTU + * value. It defaulted to 1500 (constant) but the ro structure + * may now have an update and thus we may need to change it + * BEFORE we append the message. + */ + net = stcb->asoc.primary_destination; + asoc = &stcb->asoc; + } else { + asoc = &stcb->asoc; + } + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + if ((SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_WAIT) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_COOKIE_ECHOED)) { + queue_only = 1; + } + if (use_rcvinfo == 0) { + /* Grab the default stuff from the asoc */ + srcv = stcb->asoc.def_send; + } + /* we are now done with all control */ + if (control) { + sctp_m_freem(control); + control = NULL; + } + + if ((SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_SENT) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_RECEIVED) || + (SCTP_GET_STATE(asoc) == SCTP_STATE_SHUTDOWN_ACK_SENT) || + (asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) { + if ((use_rcvinfo) && + (srcv.sinfo_flags & MSG_ABORT)) { + ; + } else { + error = ECONNRESET; + splx(s); + goto out; + } + } + /* Ok, we will attempt a msgsnd :> */ + if (p) +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + p->td_proc->p_stats->p_ru.ru_msgsnd++; +#else + p->p_stats->p_ru.ru_msgsnd++; +#endif + + if (stcb) { + if (net && ((srcv.sinfo_flags & MSG_ADDR_OVER))) { + /* we take the override or the unconfirmed */ + ; + } else { + net = stcb->asoc.primary_destination; + } + } + + if (top == NULL) { + /* Must copy it all in from user land. The + * socket buf is locked but we don't suspend + * protocol processing until we are ready to + * send/queue it. + */ + splx(s); + error = sctp_copy_it_in(inp, stcb, asoc, net, &srcv, uio, flags); + if (error) + goto out; + } else { + /* Here we must either pull in the user data to chunk + * buffers, or use top to do a msg_append. + */ + error = sctp_msg_append(stcb, net, top, &srcv, flags); + splx(s); + if (error) + goto out; + /* zap the top since it is now being used */ + top = 0; + } + + if (net->flight_size > net->cwnd) { + sctp_pegs[SCTP_SENDTO_FULL_CWND]++; + queue_only = 1; + + } else if (asoc->ifp_had_enobuf) { + sctp_pegs[SCTP_QUEONLY_BURSTLMT]++; + queue_only = 1; + } else { + un_sent = ((stcb->asoc.total_output_queue_size - stcb->asoc.total_flight) + + ((stcb->asoc.chunks_on_out_queue - stcb->asoc.total_flight_count) * sizeof(struct sctp_data_chunk)) + + SCTP_MED_OVERHEAD); + + if (((inp->sctp_flags & SCTP_PCB_FLAGS_NODELAY) == 0) && + (stcb->asoc.total_flight > 0) && + (un_sent < (int)stcb->asoc.smallest_mtu)) { + + /* Ok, Nagle is set on and we have data outstanding. Don't + * send anything and let SACKs drive out the data unless we + * have a "full" segment to send. + */ + sctp_pegs[SCTP_NAGLE_NOQ]++; + queue_only = 1; + } else { + sctp_pegs[SCTP_NAGLE_OFF]++; + } + } + if (queue_only_for_init) { + /* It is possible to have a turn around of the + * INIT/INIT-ACK/COOKIE before I have a chance to + * copy in the data. In such a case I DO want to + * send it out by reversing the queue only flag. + */ + if ((SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_WAIT) || + (SCTP_GET_STATE(asoc) != SCTP_STATE_COOKIE_ECHOED)) { + /* yep, reverse it */ + queue_only = 0; + } + } + + if ((queue_only == 0) && (stcb->asoc.peers_rwnd && un_sent)) { + /* we can attempt to send too.*/ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("USR Send calls sctp_chunk_output\n"); + } +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + sctp_pegs[SCTP_OUTPUT_FRM_SND]++; + sctp_chunk_output(inp, stcb, 0); + splx(s); + } else if ((queue_only == 0) && + (stcb->asoc.peers_rwnd == 0) && + (stcb->asoc.total_flight == 0)) { + /* We get to have a probe outstanding */ +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + sctp_from_user_send = 1; + sctp_chunk_output(inp, stcb, 0); + sctp_from_user_send = 0; + splx(s); + + } else if (!TAILQ_EMPTY(&stcb->asoc.control_send_queue)) { + int num_out, reason, cwnd_full; + /* Here we do control only */ +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + sctp_med_chunk_output(inp, stcb, &stcb->asoc, &num_out, + &reason, 1, &cwnd_full, 1, &now, &now_filled); + splx(s); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT1) { + printf("USR Send complete qo:%d prw:%d unsent:%d tf:%d cooq:%d toqs:%d \n", + queue_only, stcb->asoc.peers_rwnd, un_sent, + stcb->asoc.total_flight, stcb->asoc.chunks_on_out_queue, + stcb->asoc.total_output_queue_size); + } +#endif + out: + if (create_lock_applied) { + SCTP_ASOC_CREATE_UNLOCK(inp); + create_lock_applied = 0; + } + if (stcb) + SCTP_TCB_UNLOCK(stcb); + if (top) + sctp_m_freem(top); + if (control) + sctp_m_freem(control); + return (error); +} diff --git a/sys/netinet/sctp_output.h b/sys/netinet/sctp_output.h new file mode 100644 index 0000000000..b3c19c5593 --- /dev/null +++ b/sys/netinet/sctp_output.h @@ -0,0 +1,153 @@ +/* $KAME: sctp_output.h,v 1.13 2004/08/17 04:06:18 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_output.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +#ifndef __sctp_output_h__ +#define __sctp_output_h__ + +/* + * Copyright (C) 2002, 2003, 2004 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + + + +#include +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) +void sctp_send_initiate(struct sctp_inpcb *, struct sctp_tcb *); + +void sctp_send_initiate_ack(struct sctp_inpcb *, struct sctp_tcb *, + struct mbuf *, int, int, struct sctphdr *, struct sctp_init_chunk *); + +struct mbuf *sctp_arethere_unrecognized_parameters(struct mbuf *, int, int *, + struct sctp_chunkhdr *); +void sctp_queue_op_err(struct sctp_tcb *, struct mbuf *); + +int sctp_send_cookie_echo(struct mbuf *, int, struct sctp_tcb *, + struct sctp_nets *); +int sctp_send_cookie_ack(struct sctp_tcb *); + +void sctp_send_heartbeat_ack(struct sctp_tcb *, struct mbuf *, int, int, + struct sctp_nets *); + +int sctp_is_addr_restricted(struct sctp_tcb *, struct sockaddr *); + +struct in_addr sctp_ipv4_source_address_selection(struct sctp_inpcb *, + struct sctp_tcb *, struct route *, struct sctp_nets *, int); + + +struct in6_addr sctp_ipv6_source_address_selection(struct sctp_inpcb *, + struct sctp_tcb *, struct route *, struct sctp_nets *, int); + + +int sctp_send_shutdown(struct sctp_tcb *, struct sctp_nets *); + +int sctp_send_shutdown_ack(struct sctp_tcb *, struct sctp_nets *); + +int sctp_send_shutdown_complete(struct sctp_tcb *, struct sctp_nets *); + +int sctp_send_shutdown_complete2(struct mbuf *, int, struct sctphdr *); + +int sctp_send_asconf(struct sctp_tcb *, struct sctp_nets *); + +int sctp_send_asconf_ack(struct sctp_tcb *, uint32_t); + +int sctp_get_frag_point(struct sctp_tcb *, struct sctp_association *); + +void sctp_toss_old_cookies(struct sctp_association *); + +void sctp_toss_old_asconf(struct sctp_tcb *); + +void sctp_fix_ecn_echo(struct sctp_association *); + +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +int sctp_output(struct sctp_inpcb *, struct mbuf *, struct sockaddr *, + struct mbuf *, struct thread *, int); +#else +int sctp_output(struct sctp_inpcb *, struct mbuf *, struct sockaddr *, + struct mbuf *, struct proc *, int); +#endif + +int sctp_chunk_output(struct sctp_inpcb *, struct sctp_tcb *, int); +void sctp_send_abort_tcb(struct sctp_tcb *, struct mbuf *); + +void send_forward_tsn(struct sctp_tcb *, struct sctp_association *); + +void sctp_send_sack(struct sctp_tcb *); + +int sctp_send_hb(struct sctp_tcb *, int, struct sctp_nets *); + +void sctp_send_ecn_echo(struct sctp_tcb *, struct sctp_nets *, uint32_t); + + +void +sctp_send_packet_dropped(struct sctp_tcb *, struct sctp_nets *, struct mbuf *, + int, int); + + + +void sctp_send_cwr(struct sctp_tcb *, struct sctp_nets *, uint32_t); + + +void +sctp_send_str_reset_ack(struct sctp_tcb *stcb, + struct sctp_stream_reset_request *req); + + +void +sctp_send_str_reset_req(struct sctp_tcb *stcb, + int number_entrys, uint16_t *list, uint8_t two_way, uint8_t not_peer); + + +void sctp_send_abort(struct mbuf *, int, struct sctphdr *, uint32_t, + struct mbuf *); + +void sctp_send_operr_to(struct mbuf *, int, struct mbuf *, uint32_t); + +int +sctp_sosend(struct socket *so, +#ifdef __NetBSD__ + struct mbuf *addr_mbuf, +#else + struct sockaddr *addr, +#endif + struct uio *uio, + struct mbuf *top, + struct mbuf *control, +#if defined(__NetBSD__) || defined(__APPLE__) + int flags +#else + int flags, +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + struct thread *p +#else + struct proc *p +#endif +#endif + ); + +#endif +#endif diff --git a/sys/netinet/sctp_pcb.c b/sys/netinet/sctp_pcb.c new file mode 100644 index 0000000000..4785e22342 --- /dev/null +++ b/sys/netinet/sctp_pcb.c @@ -0,0 +1,5207 @@ +/* $KAME: sctp_pcb.c,v 1.37 2004/08/17 06:28:02 t-momose Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_pcb.c,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#if !(defined(__OpenBSD__) || defined(__APPLE__)) +#include "opt_ipsec.h" +#endif +#if defined(__FreeBSD__) +#include "opt_compat.h" +#include "opt_inet6.h" +#include "opt_inet.h" +#endif +#if defined(__NetBSD__) +#include "opt_inet.h" +#endif +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__FreeBSD__) || defined(__APPLE__) +#include +#endif +#if defined(__NetBSD__) +#include +#endif +#if defined(__OpenBSD__) +#include +#endif + +#if defined(__APPLE__) +#include +#elif defined(__OpenBSD__) +#include +#else +#include +#endif + +#if (defined(__FreeBSD__) && __FreeBSD_version >= 500000) +#include +#else +#include +#endif +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef INET6 +#include +#include +#include +#if defined(__FreeBSD__) || (__NetBSD__) +#include +#elif defined(__OpenBSD__) +#include +#endif +#endif /* INET6 */ + +#ifdef IPSEC +#ifndef __OpenBSD__ +#include +#include +#else +#undef IPSEC +#endif +#endif /* IPSEC */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef SCTP_PCBHASHSIZE +/* default number of association hash buckets in each endpoint */ +#define SCTP_PCBHASHSIZE 256 +#endif + +#ifdef SCTP_DEBUG +u_int32_t sctp_debug_on = 0; +#endif /* SCTP_DEBUG */ + +u_int32_t sctp_pegs[SCTP_NUMBER_OF_PEGS]; + +int sctp_pcbtblsize = SCTP_PCBHASHSIZE; + +struct sctp_epinfo sctppcbinfo; + +/* FIX: we don't handle multiple link local scopes */ +/* "scopeless" replacement IN6_ARE_ADDR_EQUAL */ +int +SCTP6_ARE_ADDR_EQUAL(struct in6_addr *a, struct in6_addr *b) +{ + struct in6_addr tmp_a, tmp_b; + /* use a copy of a and b */ + tmp_a = *a; + tmp_b = *b; + in6_clearscope(&tmp_a); + in6_clearscope(&tmp_b); + return (IN6_ARE_ADDR_EQUAL(&tmp_a, &tmp_b)); +} + +#ifdef __OpenBSD__ +extern int ipport_firstauto; +extern int ipport_lastauto; +extern int ipport_hifirstauto; +extern int ipport_hilastauto; +#endif + +#if defined(__FreeBSD__) && __FreeBSD_version > 500000 + +#ifndef xyzzy +void sctp_validate_no_locks(void); + +void +SCTP_INP_RLOCK(struct sctp_inpcb *inp) +{ + struct sctp_tcb *stcb; + LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { + if (mtx_owned(&(stcb)->tcb_mtx)) + panic("I own TCB lock?"); + } + if (mtx_owned(&(inp)->inp_mtx)) + panic("INP Recursive Lock-R"); + mtx_lock(&(inp)->inp_mtx); +} + +void +SCTP_INP_WLOCK(struct sctp_inpcb *inp) +{ + SCTP_INP_RLOCK(inp); +} + +void +SCTP_INP_INFO_RLOCK() +{ + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + LIST_FOREACH(inp, &sctppcbinfo.listhead, sctp_list) { + if (mtx_owned(&(inp)->inp_mtx)) + panic("info-lock and own inp lock?"); + LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { + if (mtx_owned(&(stcb)->tcb_mtx)) + panic("Info lock and own a tcb lock?"); + } + } + if (mtx_owned(&sctppcbinfo.ipi_ep_mtx)) + panic("INP INFO Recursive Lock-R"); + mtx_lock(&sctppcbinfo.ipi_ep_mtx); +} + +void +SCTP_INP_INFO_WLOCK() +{ + SCTP_INP_INFO_RLOCK(); +} + + +void sctp_validate_no_locks() +{ + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + + if (mtx_owned(&sctppcbinfo.ipi_ep_mtx)) + panic("INP INFO lock is owned?"); + + LIST_FOREACH(inp, &sctppcbinfo.listhead, sctp_list) { + if (mtx_owned(&(inp)->inp_mtx)) + panic("You own an INP lock?"); + LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { + if (mtx_owned(&(stcb)->tcb_mtx)) + panic("You own a TCB lock?"); + } + } +} + +#endif +#endif + +void +sctp_fill_pcbinfo(struct sctp_pcbinfo *spcb) +{ + /* We really don't need + * to lock this, but I will + * just because it does not hurt. + */ + SCTP_INP_INFO_RLOCK(); + spcb->ep_count = sctppcbinfo.ipi_count_ep; + spcb->asoc_count = sctppcbinfo.ipi_count_asoc; + spcb->laddr_count = sctppcbinfo.ipi_count_laddr; + spcb->raddr_count = sctppcbinfo.ipi_count_raddr; + spcb->chk_count = sctppcbinfo.ipi_count_chunk; + spcb->sockq_count = sctppcbinfo.ipi_count_sockq; + spcb->mbuf_track = sctppcbinfo.mbuf_track; + SCTP_INP_INFO_RUNLOCK(); +} + + +/* + * Notes on locks for FreeBSD 5 and up. All association + * lookups that have a definte ep, the INP structure is + * assumed to be locked for reading. If we need to go + * find the INP (ususally when a **inp is passed) then + * we must lock the INFO structure first and if needed + * lock the INP too. Note that if we lock it we must + * + */ + + +/* + * Given a endpoint, look and find in its association list any association + * with the "to" address given. This can be a "from" address, too, for + * inbound packets. For outbound packets it is a true "to" address. + */ +static struct sctp_tcb * +sctp_tcb_special_locate(struct sctp_inpcb **inp_p, struct sockaddr *from, + struct sockaddr *to, struct sctp_nets **netp) +{ + /**** ASSUMSES THE CALLER holds the INP_INFO_RLOCK */ + + /* + * Note for this module care must be taken when observing what to is + * for. In most of the rest of the code the TO field represents my + * peer and the FROM field represents my address. For this module it + * is reversed of that. + */ + /* + * If we support the TCP model, then we must now dig through to + * see if we can find our endpoint in the list of tcp ep's. + */ + uint16_t lport, rport; + struct sctppcbhead *ephead; + struct sctp_inpcb *inp; + struct sctp_laddr *laddr; + struct sctp_tcb *stcb; + struct sctp_nets *net; + + if ((to == NULL) || (from == NULL)) { + return (NULL); + } + + if (to->sa_family == AF_INET && from->sa_family == AF_INET) { + lport = ((struct sockaddr_in *)to)->sin_port; + rport = ((struct sockaddr_in *)from)->sin_port; + } else if (to->sa_family == AF_INET6 && from->sa_family == AF_INET6) { + lport = ((struct sockaddr_in6 *)to)->sin6_port; + rport = ((struct sockaddr_in6 *)from)->sin6_port; + } else { + return NULL; + } + ephead = &sctppcbinfo.sctp_tcpephash[SCTP_PCBHASH_ALLADDR( + (lport + rport), sctppcbinfo.hashtcpmark)]; + /* + * Ok now for each of the guys in this bucket we must look + * and see: + * - Does the remote port match. + * - Does there single association's addresses match this + * address (to). + * If so we update p_ep to point to this ep and return the + * tcb from it. + */ + LIST_FOREACH(inp, ephead, sctp_hash) { + if (lport != inp->sctp_lport) { + continue; + } + SCTP_INP_RLOCK(inp); + /* check to see if the ep has one of the addresses */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) { + /* We are NOT bound all, so look further */ + int match = 0; + + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("An ounce of prevention is worth a pound of cure\n"); + } +#endif + continue; + } + if (laddr->ifa->ifa_addr == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("ifa with a NULL address\n"); + } +#endif + continue; + } + if (laddr->ifa->ifa_addr->sa_family == + to->sa_family) { + /* see if it matches */ + struct sockaddr_in *intf_addr, *sin; + intf_addr = (struct sockaddr_in *) + laddr->ifa->ifa_addr; + sin = (struct sockaddr_in *)to; + if (from->sa_family == AF_INET) { + if (sin->sin_addr.s_addr == + intf_addr->sin_addr.s_addr) { + match = 1; + SCTP_INP_RUNLOCK(inp); + break; + } + } else { + struct sockaddr_in6 *intf_addr6; + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *) + to; + intf_addr6 = (struct sockaddr_in6 *) + laddr->ifa->ifa_addr; + + if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &intf_addr6->sin6_addr)) { + match = 1; + SCTP_INP_RUNLOCK(inp); + break; + } + } + } + } + if (match == 0) { + /* This endpoint does not have this address */ + SCTP_INP_RUNLOCK(inp); + continue; + } + } + /* + * Ok if we hit here the ep has the address, does it hold the + * tcb? + */ + + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { + SCTP_INP_RUNLOCK(inp); + continue; + } + SCTP_TCB_LOCK(stcb); + if (stcb->rport != rport) { + /* remote port does not match. */ + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_RUNLOCK(inp); + continue; + } + /* Does this TCB have a matching address? */ + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + if (net->ro._l_addr.sa.sa_family != from->sa_family) { + /* not the same family, can't be a match */ + continue; + } + if (from->sa_family == AF_INET) { + struct sockaddr_in *sin, *rsin; + sin = (struct sockaddr_in *)&net->ro._l_addr; + rsin = (struct sockaddr_in *)from; + if (sin->sin_addr.s_addr == + rsin->sin_addr.s_addr) { + /* found it */ + if (netp != NULL) { + *netp = net; + } + /* Update the endpoint pointer */ + *inp_p = inp; + SCTP_INP_RUNLOCK(inp); + return (stcb); + } + } else { + struct sockaddr_in6 *sin6, *rsin6; + sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; + rsin6 = (struct sockaddr_in6 *)from; + if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &rsin6->sin6_addr)) { + /* found it */ + if (netp != NULL) { + *netp = net; + } + /* Update the endpoint pointer */ + *inp_p = inp; + SCTP_INP_RUNLOCK(inp); + return (stcb); + } + } + } + SCTP_TCB_UNLOCK(stcb); + + SCTP_INP_RUNLOCK(inp); + } + return (NULL); +} + +struct sctp_tcb * +sctp_findassociation_ep_asconf(struct mbuf *m, int iphlen, int offset, + struct sctphdr *sh, struct sctp_inpcb **inp_p, struct sctp_nets **netp) +{ + struct sctp_tcb *stcb; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct sockaddr_storage local_store, remote_store; + struct ip *iph; + struct sctp_paramhdr parm_buf, *phdr; + int ptype; + + memset(&local_store, 0, sizeof(local_store)); + memset(&remote_store, 0, sizeof(remote_store)); + + /* First get the destination address setup too. */ + iph = mtod(m, struct ip *); + if (iph->ip_v == IPVERSION) { + /* its IPv4 */ + sin = (struct sockaddr_in *)&local_store; + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_port = sh->dest_port; + sin->sin_addr.s_addr = iph->ip_dst.s_addr ; + } else if (iph->ip_v == (IPV6_VERSION >> 4)) { + /* its IPv6 */ + struct ip6_hdr *ip6; + ip6 = mtod(m, struct ip6_hdr *); + sin6 = (struct sockaddr_in6 *)&local_store; + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_port = sh->dest_port; + sin6->sin6_addr = ip6->ip6_dst; + } else { + return NULL; + } + + phdr = sctp_get_next_param(m, offset + sizeof(struct sctp_asconf_chunk), + &parm_buf, sizeof(struct sctp_paramhdr)); + if (phdr == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("sctp_process_control: failed to get asconf lookup addr\n"); + } +#endif /* SCTP_DEBUG */ + return NULL; + } + ptype = (int)((u_int)ntohs(phdr->param_type)); + /* get the correlation address */ + if (ptype == SCTP_IPV6_ADDRESS) { + /* ipv6 address param */ + struct sctp_ipv6addr_param *p6, p6_buf; + if (ntohs(phdr->param_length) != sizeof(struct sctp_ipv6addr_param)) { + return NULL; + } + + p6 = (struct sctp_ipv6addr_param *)sctp_get_next_param(m, + offset + sizeof(struct sctp_asconf_chunk), + &p6_buf.ph, sizeof(*p6)); + if (p6 == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("sctp_process_control: failed to get asconf v6 lookup addr\n"); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + sin6 = (struct sockaddr_in6 *)&remote_store; + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_port = sh->src_port; + memcpy(&sin6->sin6_addr, &p6->addr, sizeof(struct in6_addr)); + } else if (ptype == SCTP_IPV4_ADDRESS) { + /* ipv4 address param */ + struct sctp_ipv4addr_param *p4, p4_buf; + if (ntohs(phdr->param_length) != sizeof(struct sctp_ipv4addr_param)) { + return NULL; + } + + p4 = (struct sctp_ipv4addr_param *)sctp_get_next_param(m, + offset + sizeof(struct sctp_asconf_chunk), + &p4_buf.ph, sizeof(*p4)); + if (p4 == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT3) { + printf("sctp_process_control: failed to get asconf v4 lookup addr\n"); + } +#endif /* SCTP_DEBUG */ + return (NULL); + } + sin = (struct sockaddr_in *)&remote_store; + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_port = sh->src_port; + memcpy(&sin->sin_addr, &p4->addr, sizeof(struct in_addr)); + } else { + /* invalid address param type */ + return NULL; + } + + stcb = sctp_findassociation_ep_addr(inp_p, + (struct sockaddr *)&remote_store, netp, + (struct sockaddr *)&local_store, NULL); + return (stcb); +} + +struct sctp_tcb * +sctp_findassociation_ep_addr(struct sctp_inpcb **inp_p, struct sockaddr *remote, + struct sctp_nets **netp, struct sockaddr *local, struct sctp_tcb *locked_tcb) +{ + struct sctpasochead *head; + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + struct sctp_nets *net; + uint16_t rport; + + inp = *inp_p; + if (remote->sa_family == AF_INET) { + rport = (((struct sockaddr_in *)remote)->sin_port); + } else if (remote->sa_family == AF_INET6) { + rport = (((struct sockaddr_in6 *)remote)->sin6_port); + } else { + return (NULL); + } + if (locked_tcb) { + /* UN-lock so we can do proper locking here + * this occurs when called from load_addresses_from_init. + */ + SCTP_TCB_UNLOCK(locked_tcb); + } + SCTP_INP_INFO_RLOCK(); + if (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { + /* + * Now either this guy is our listner or it's the connector. + * If it is the one that issued the connect, then it's only + * chance is to be the first TCB in the list. If it is the + * acceptor, then do the special_lookup to hash and find the + * real inp. + */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING) { + /* to is peer addr, from is my addr */ + stcb = sctp_tcb_special_locate(inp_p, remote, local, + netp); + if ((stcb != NULL) && (locked_tcb == NULL)){ + /* we have a locked tcb, lower refcount */ + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + if (locked_tcb != NULL) { + SCTP_INP_RLOCK(locked_tcb->sctp_ep); + SCTP_TCB_LOCK(locked_tcb); + SCTP_INP_RUNLOCK(locked_tcb->sctp_ep); + if (stcb != NULL) + SCTP_TCB_UNLOCK(stcb); + } + SCTP_INP_INFO_RUNLOCK(); + return (stcb); + } else { + SCTP_INP_WLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { + goto null_return; + } + SCTP_TCB_LOCK(stcb); + if (stcb->rport != rport) { + /* remote port does not match. */ + SCTP_TCB_UNLOCK(stcb); + goto null_return; + } + /* now look at the list of remote addresses */ + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + if (net->ro._l_addr.sa.sa_family != + remote->sa_family) { + /* not the same family */ + continue; + } + if (remote->sa_family == AF_INET) { + struct sockaddr_in *sin, *rsin; + sin = (struct sockaddr_in *) + &net->ro._l_addr; + rsin = (struct sockaddr_in *)remote; + if (sin->sin_addr.s_addr == + rsin->sin_addr.s_addr) { + /* found it */ + if (netp != NULL) { + *netp = net; + } + if (locked_tcb == NULL) { + SCTP_INP_DECR_REF(inp); + } + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_RUNLOCK(); + return (stcb); + } + } else if (remote->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6, *rsin6; + sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; + rsin6 = (struct sockaddr_in6 *)remote; + if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &rsin6->sin6_addr)) { + /* found it */ + if (netp != NULL) { + *netp = net; + } + if (locked_tcb == NULL) { + SCTP_INP_DECR_REF(inp); + } + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_RUNLOCK(); + return (stcb); + } + } + } + SCTP_TCB_UNLOCK(stcb); + } + } else { + SCTP_INP_WLOCK(inp); + head = &inp->sctp_tcbhash[SCTP_PCBHASH_ALLADDR(rport, + inp->sctp_hashmark)]; + if (head == NULL) { + goto null_return; + } + LIST_FOREACH(stcb, head, sctp_tcbhash) { + if (stcb->rport != rport) { + /* remote port does not match */ + continue; + } + /* now look at the list of remote addresses */ + SCTP_TCB_LOCK(stcb); + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + if (net->ro._l_addr.sa.sa_family != + remote->sa_family) { + /* not the same family */ + continue; + } + if (remote->sa_family == AF_INET) { + struct sockaddr_in *sin, *rsin; + sin = (struct sockaddr_in *) + &net->ro._l_addr; + rsin = (struct sockaddr_in *)remote; + if (sin->sin_addr.s_addr == + rsin->sin_addr.s_addr) { + /* found it */ + if (netp != NULL) { + *netp = net; + } + if (locked_tcb == NULL) { + SCTP_INP_DECR_REF(inp); + } + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_RUNLOCK(); + return (stcb); + } + } else if (remote->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6, *rsin6; + sin6 = (struct sockaddr_in6 *) + &net->ro._l_addr; + rsin6 = (struct sockaddr_in6 *)remote; + if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &rsin6->sin6_addr)) { + /* found it */ + if (netp != NULL) { + *netp = net; + } + if (locked_tcb == NULL) { + SCTP_INP_DECR_REF(inp); + } + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_RUNLOCK(); + return (stcb); + } + } + } + SCTP_TCB_UNLOCK(stcb); + } + } + null_return: + /* clean up for returning null */ + if (locked_tcb){ + if (locked_tcb->sctp_ep != inp) { + SCTP_INP_RLOCK(locked_tcb->sctp_ep); + SCTP_TCB_LOCK(locked_tcb); + SCTP_INP_RUNLOCK(locked_tcb->sctp_ep); + } else + SCTP_TCB_LOCK(locked_tcb); + } + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_RUNLOCK(); + /* not found */ + return (NULL); +} + +/* + * Find an association for a specific endpoint using the association id + * given out in the COMM_UP notification + */ +struct sctp_tcb * +sctp_findassociation_ep_asocid(struct sctp_inpcb *inp, caddr_t asoc_id) +{ + /* + * Use my the assoc_id to find a endpoint + */ + struct sctpasochead *head; + struct sctp_tcb *stcb; + u_int32_t vtag; + + if (asoc_id == 0 || inp == NULL) { + return (NULL); + } + SCTP_INP_INFO_RLOCK(); + vtag = (u_int32_t)asoc_id; + head = &sctppcbinfo.sctp_asochash[SCTP_PCBHASH_ASOC(vtag, + sctppcbinfo.hashasocmark)]; + if (head == NULL) { + /* invalid vtag */ + SCTP_INP_INFO_RUNLOCK(); + return (NULL); + } + LIST_FOREACH(stcb, head, sctp_asocs) { + SCTP_INP_RLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(stcb->sctp_ep); + if (stcb->asoc.my_vtag == vtag) { + /* candidate */ + if (inp != stcb->sctp_ep) { + /* some other guy has the + * same vtag active (vtag collision). + */ + sctp_pegs[SCTP_VTAG_BOGUS]++; + SCTP_TCB_UNLOCK(stcb); + continue; + } + sctp_pegs[SCTP_VTAG_EXPR]++; + SCTP_INP_INFO_RUNLOCK(); + return (stcb); + } + SCTP_TCB_UNLOCK(stcb); + } + SCTP_INP_INFO_RUNLOCK(); + return (NULL); +} + +static struct sctp_inpcb * +sctp_endpoint_probe(struct sockaddr *nam, struct sctppcbhead *head, + uint16_t lport) +{ + struct sctp_inpcb *inp; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + struct sctp_laddr *laddr; + + /* Endpoing probe expects + * that the INP_INFO is locked. + */ + if (nam->sa_family == AF_INET) { + sin = (struct sockaddr_in *)nam; + sin6 = NULL; + } else if (nam->sa_family == AF_INET6) { + sin6 = (struct sockaddr_in6 *)nam; + sin = NULL; + } else { + /* unsupported family */ + return (NULL); + } + if (head == NULL) + return (NULL); + + LIST_FOREACH(inp, head, sctp_hash) { + SCTP_INP_RLOCK(inp); + + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) && + (inp->sctp_lport == lport)) { + /* got it */ + if ((nam->sa_family == AF_INET) && + (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) && +#if defined(__FreeBSD__) || defined(__APPLE__) + (((struct inpcb *)inp)->inp_flags & IN6P_IPV6_V6ONLY) +#else +#if defined(__OpenBSD__) + (0) /* For open bsd we do dual bind only */ +#else + (((struct in6pcb *)inp)->in6p_flags & IN6P_IPV6_V6ONLY) +#endif +#endif + ) { + /* IPv4 on a IPv6 socket with ONLY IPv6 set */ + SCTP_INP_RUNLOCK(inp); + continue; + } + /* A V6 address and the endpoint is NOT bound V6 */ + if (nam->sa_family == AF_INET6 && + (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) { + SCTP_INP_RUNLOCK(inp); + continue; + } + SCTP_INP_RUNLOCK(inp); + return (inp); + } + SCTP_INP_RUNLOCK(inp); + } + + if ((nam->sa_family == AF_INET) && + (sin->sin_addr.s_addr == INADDR_ANY)) { + /* Can't hunt for one that has no address specified */ + return (NULL); + } else if ((nam->sa_family == AF_INET6) && + (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))) { + /* Can't hunt for one that has no address specified */ + return (NULL); + } + /* + * ok, not bound to all so see if we can find a EP bound to this + * address. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Ok, there is NO bound-all available for port:%x\n", ntohs(lport)); + } +#endif + LIST_FOREACH(inp, head, sctp_hash) { + SCTP_INP_RLOCK(inp); + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL)) { + SCTP_INP_RUNLOCK(inp); + continue; + } + /* + * Ok this could be a likely candidate, look at all of + * its addresses + */ + if (inp->sctp_lport != lport) { + SCTP_INP_RUNLOCK(inp); + continue; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Ok, found maching local port\n"); + } +#endif + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("An ounce of prevention is worth a pound of cure\n"); + } +#endif + continue; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Ok laddr->ifa:%p is possible, ", + laddr->ifa); + } +#endif + if (laddr->ifa->ifa_addr == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Huh IFA as an ifa_addr=NULL, "); + } +#endif + continue; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Ok laddr->ifa:%p is possible, ", + laddr->ifa->ifa_addr); + sctp_print_address(laddr->ifa->ifa_addr); + printf("looking for "); + sctp_print_address(nam); + } +#endif + if (laddr->ifa->ifa_addr->sa_family == nam->sa_family) { + /* possible, see if it matches */ + struct sockaddr_in *intf_addr; + intf_addr = (struct sockaddr_in *) + laddr->ifa->ifa_addr; + if (nam->sa_family == AF_INET) { + if (sin->sin_addr.s_addr == + intf_addr->sin_addr.s_addr) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("YES, return ep:%p\n", inp); + } +#endif + SCTP_INP_RUNLOCK(inp); + return (inp); + } + } else if (nam->sa_family == AF_INET6) { + struct sockaddr_in6 *intf_addr6; + intf_addr6 = (struct sockaddr_in6 *) + laddr->ifa->ifa_addr; + if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &intf_addr6->sin6_addr)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("YES, return ep:%p\n", inp); + } +#endif + SCTP_INP_RUNLOCK(inp); + return (inp); + } + } + } + SCTP_INP_RUNLOCK(inp); + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("NO, Falls out to NULL\n"); + } +#endif + return (NULL); +} + + +struct sctp_inpcb * +sctp_pcb_findep(struct sockaddr *nam, int find_tcp_pool, int have_lock) +{ + /* + * First we check the hash table to see if someone has this port + * bound with just the port. + */ + struct sctp_inpcb *inp; + struct sctppcbhead *head; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + int lport; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Looking for endpoint %d :", + ntohs(((struct sockaddr_in *)nam)->sin_port)); + sctp_print_address(nam); + } +#endif + if (nam->sa_family == AF_INET) { + sin = (struct sockaddr_in *)nam; + lport = ((struct sockaddr_in *)nam)->sin_port; + } else if (nam->sa_family == AF_INET6) { + sin6 = (struct sockaddr_in6 *)nam; + lport = ((struct sockaddr_in6 *)nam)->sin6_port; + } else { + /* unsupported family */ + return (NULL); + } + /* + * I could cheat here and just cast to one of the types but we will + * do it right. It also provides the check against an Unsupported + * type too. + */ + /* Find the head of the ALLADDR chain */ + if (have_lock == 0) + SCTP_INP_INFO_RLOCK(); + head = &sctppcbinfo.sctp_ephash[SCTP_PCBHASH_ALLADDR(lport, + sctppcbinfo.hashmark)]; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Main hash to lookup at head:%p\n", head); + } +#endif + inp = sctp_endpoint_probe(nam, head, lport); + + /* + * If the TCP model exists it could be that the main listening + * endpoint is gone but there exists a connected socket for this + * guy yet. If so we can return the first one that we find. This + * may NOT be the correct one but the sctp_findassociation_ep_addr + * has further code to look at all TCP models. + */ + if (inp == NULL && find_tcp_pool) { + unsigned int i; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("EP was NULL and TCP model is supported\n"); + } +#endif + for (i = 0; i < sctppcbinfo.hashtblsize; i++) { + /* + * This is real gross, but we do NOT have a remote + * port at this point depending on who is calling. We + * must therefore look for ANY one that matches our + * local port :/ + */ + head = &sctppcbinfo.sctp_tcpephash[i]; + if (LIST_FIRST(head)) { + inp = sctp_endpoint_probe(nam, head, lport); + if (inp) { + /* Found one */ + break; + } + } + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("EP to return is %p\n", inp); + } +#endif + if (have_lock == 0) { + if (inp) { + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + SCTP_INP_INFO_RUNLOCK(); + } else { + if (inp) { + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + } + return (inp); +} + +/* + * Find an association for an endpoint with the pointer to whom you want + * to send to and the endpoint pointer. The address can be IPv4 or IPv6. + * We may need to change the *to to some other struct like a mbuf... + */ +struct sctp_tcb * +sctp_findassociation_addr_sa(struct sockaddr *to, struct sockaddr *from, + struct sctp_inpcb **inp_p, struct sctp_nets **netp, int find_tcp_pool) +{ + struct sctp_inpcb *inp; + struct sctp_tcb *retval; + + SCTP_INP_INFO_RLOCK(); + if (find_tcp_pool) { + if (inp_p != NULL) { + retval = sctp_tcb_special_locate(inp_p, from, to, netp); + } else { + retval = sctp_tcb_special_locate(&inp, from, to, netp); + } + if (retval != NULL) { + SCTP_INP_INFO_RUNLOCK(); + return (retval); + } + } + inp = sctp_pcb_findep(to, 0, 1); + if (inp_p != NULL) { + *inp_p = inp; + } + SCTP_INP_INFO_RUNLOCK(); + + if (inp == NULL) { + return (NULL); + } + + /* + * ok, we have an endpoint, now lets find the assoc for it (if any) + * we now place the source address or from in the to of the find + * endpoint call. Since in reality this chain is used from the + * inbound packet side. + */ + if (inp_p != NULL) { + return (sctp_findassociation_ep_addr(inp_p, from, netp, to, NULL)); + } else { + return (sctp_findassociation_ep_addr(&inp, from, netp, to, NULL)); + } +} + + +/* + * This routine will grub through the mbuf that is a INIT or INIT-ACK and + * find all addresses that the sender has specified in any address list. + * Each address will be used to lookup the TCB and see if one exits. + */ +static struct sctp_tcb * +sctp_findassociation_special_addr(struct mbuf *m, int iphlen, int offset, + struct sctphdr *sh, struct sctp_inpcb **inp_p, struct sctp_nets **netp, + struct sockaddr *dest) +{ + struct sockaddr_in sin4; + struct sockaddr_in6 sin6; + struct sctp_paramhdr *phdr, parm_buf; + struct sctp_tcb *retval; + u_int32_t ptype, plen; + + memset(&sin4, 0, sizeof(sin4)); + memset(&sin6, 0, sizeof(sin6)); + sin4.sin_len = sizeof(sin4); + sin4.sin_family = AF_INET; + sin4.sin_port = sh->src_port; + sin6.sin6_len = sizeof(sin6); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = sh->src_port; + + retval = NULL; + offset += sizeof(struct sctp_init_chunk); + + phdr = sctp_get_next_param(m, offset, &parm_buf, sizeof(parm_buf)); + while (phdr != NULL) { + /* now we must see if we want the parameter */ + ptype = ntohs(phdr->param_type); + plen = ntohs(phdr->param_length); + if (plen == 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("sctp_findassociation_special_addr: Impossible length in parameter\n"); + } +#endif /* SCTP_DEBUG */ + break; + } + if (ptype == SCTP_IPV4_ADDRESS && + plen == sizeof(struct sctp_ipv4addr_param)) { + /* Get the rest of the address */ + struct sctp_ipv4addr_param ip4_parm, *p4; + + phdr = sctp_get_next_param(m, offset, + (struct sctp_paramhdr *)&ip4_parm, plen); + if (phdr == NULL) { + return (NULL); + } + p4 = (struct sctp_ipv4addr_param *)phdr; + memcpy(&sin4.sin_addr, &p4->addr, sizeof(p4->addr)); + /* look it up */ + retval = sctp_findassociation_ep_addr(inp_p, + (struct sockaddr *)&sin4, netp, dest, NULL); + if (retval != NULL) { + return (retval); + } + } else if (ptype == SCTP_IPV6_ADDRESS && + plen == sizeof(struct sctp_ipv6addr_param)) { + /* Get the rest of the address */ + struct sctp_ipv6addr_param ip6_parm, *p6; + + phdr = sctp_get_next_param(m, offset, + (struct sctp_paramhdr *)&ip6_parm, plen); + if (phdr == NULL) { + return (NULL); + } + p6 = (struct sctp_ipv6addr_param *)phdr; + memcpy(&sin6.sin6_addr, &p6->addr, sizeof(p6->addr)); + /* look it up */ + retval = sctp_findassociation_ep_addr(inp_p, + (struct sockaddr *)&sin6, netp, dest, NULL); + if (retval != NULL) { + return (retval); + } + } + offset += SCTP_SIZE32(plen); + phdr = sctp_get_next_param(m, offset, &parm_buf, + sizeof(parm_buf)); + } + return (NULL); +} + +static struct sctp_tcb * +sctp_findassoc_by_vtag(struct sockaddr *from, uint32_t vtag, + struct sctp_inpcb **inp_p, struct sctp_nets **netp, uint16_t rport, + uint16_t lport) +{ + /* + * Use my vtag to hash. If we find it we then verify the source addr + * is in the assoc. If all goes well we save a bit on rec of a packet. + */ + struct sctpasochead *head; + struct sctp_nets *net; + struct sctp_tcb *stcb; + + SCTP_INP_INFO_RLOCK(); + head = &sctppcbinfo.sctp_asochash[SCTP_PCBHASH_ASOC(vtag, + sctppcbinfo.hashasocmark)]; + if (head == NULL) { + /* invalid vtag */ + SCTP_INP_INFO_RUNLOCK(); + return (NULL); + } + LIST_FOREACH(stcb, head, sctp_asocs) { + SCTP_INP_RLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(stcb->sctp_ep); + if (stcb->asoc.my_vtag == vtag) { + /* candidate */ + if (stcb->rport != rport) { + /* + * we could remove this if vtags are unique + * across the system. + */ + SCTP_TCB_UNLOCK(stcb); + continue; + } + if (stcb->sctp_ep->sctp_lport != lport) { + /* + * we could remove this if vtags are unique + * across the system. + */ + SCTP_TCB_UNLOCK(stcb); + continue; + } + net = sctp_findnet(stcb, from); + if (net) { + /* yep its him. */ + *netp = net; + sctp_pegs[SCTP_VTAG_EXPR]++; + *inp_p = stcb->sctp_ep; + SCTP_INP_INFO_RUNLOCK(); + return (stcb); + } else { + /* not him, this should only + * happen in rare cases so + * I peg it. + */ + sctp_pegs[SCTP_VTAG_BOGUS]++; + } + } + SCTP_TCB_UNLOCK(stcb); + } + SCTP_INP_INFO_RUNLOCK(); + return (NULL); +} + +/* + * Find an association with the pointer to the inbound IP packet. This + * can be a IPv4 or IPv6 packet. + */ +struct sctp_tcb * +sctp_findassociation_addr(struct mbuf *m, int iphlen, int offset, + struct sctphdr *sh, struct sctp_chunkhdr *ch, + struct sctp_inpcb **inp_p, struct sctp_nets **netp) +{ + int find_tcp_pool; + struct ip *iph; + struct sctp_tcb *retval; + struct sockaddr_storage to_store, from_store; + struct sockaddr *to = (struct sockaddr *)&to_store; + struct sockaddr *from = (struct sockaddr *)&from_store; + struct sctp_inpcb *inp; + + + iph = mtod(m, struct ip *); + if (iph->ip_v == IPVERSION) { + /* its IPv4 */ + struct sockaddr_in *to4, *from4; + + to4 = (struct sockaddr_in *)&to_store; + from4 = (struct sockaddr_in *)&from_store; + bzero(to4, sizeof(*to4)); + bzero(from4, sizeof(*from4)); + from4->sin_family = to4->sin_family = AF_INET; + from4->sin_len = to4->sin_len = sizeof(struct sockaddr_in); + from4->sin_addr.s_addr = iph->ip_src.s_addr; + to4->sin_addr.s_addr = iph->ip_dst.s_addr ; + from4->sin_port = sh->src_port; + to4->sin_port = sh->dest_port; + } else if (iph->ip_v == (IPV6_VERSION >> 4)) { + /* its IPv6 */ + struct ip6_hdr *ip6; + struct sockaddr_in6 *to6, *from6; + + ip6 = mtod(m, struct ip6_hdr *); + to6 = (struct sockaddr_in6 *)&to_store; + from6 = (struct sockaddr_in6 *)&from_store; + bzero(to6, sizeof(*to6)); + bzero(from6, sizeof(*from6)); + from6->sin6_family = to6->sin6_family = AF_INET6; + from6->sin6_len = to6->sin6_len = sizeof(struct sockaddr_in6); + to6->sin6_addr = ip6->ip6_dst; + from6->sin6_addr = ip6->ip6_src; + from6->sin6_port = sh->src_port; + to6->sin6_port = sh->dest_port; + /* Get the scopes in properly to the sin6 addr's */ + (void)in6_recoverscope(to6, &to6->sin6_addr, NULL); +#if defined(SCTP_BASE_FREEBSD) || defined(__APPLE__) + (void)in6_embedscope(&to6->sin6_addr, to6, NULL, NULL); +#else + (void)in6_embedscope(&to6->sin6_addr, to6); +#endif + + (void)in6_recoverscope(from6, &from6->sin6_addr, NULL); +#if defined(SCTP_BASE_FREEBSD) || defined(__APPLE__) + (void)in6_embedscope(&from6->sin6_addr, from6, NULL, NULL); +#else + (void)in6_embedscope(&from6->sin6_addr, from6); +#endif + } else { + /* Currently not supported. */ + return (NULL); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Looking for port %d address :", + ntohs(((struct sockaddr_in *)to)->sin_port)); + sctp_print_address(to); + printf("From for port %d address :", + ntohs(((struct sockaddr_in *)from)->sin_port)); + sctp_print_address(from); + } +#endif + + if (sh->v_tag) { + /* we only go down this path if vtag is non-zero */ + retval = sctp_findassoc_by_vtag(from, ntohl(sh->v_tag), + inp_p, netp, sh->src_port, sh->dest_port); + if (retval) { + return (retval); + } + } + find_tcp_pool = 0; + if ((ch->chunk_type != SCTP_INITIATION) && + (ch->chunk_type != SCTP_INITIATION_ACK) && + (ch->chunk_type != SCTP_COOKIE_ACK) && + (ch->chunk_type != SCTP_COOKIE_ECHO)) { + /* Other chunk types go to the tcp pool. */ + find_tcp_pool = 1; + } + if (inp_p) { + retval = sctp_findassociation_addr_sa(to, from, inp_p, netp, + find_tcp_pool); + inp = *inp_p; + } else { + retval = sctp_findassociation_addr_sa(to, from, &inp, netp, + find_tcp_pool); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("retval:%p inp:%p\n", retval, inp); + } +#endif + if (retval == NULL && inp) { + /* Found a EP but not this address */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Found endpoint %p but no asoc - ep state:%x\n", + inp, inp->sctp_flags); + } +#endif + if ((ch->chunk_type == SCTP_INITIATION) || + (ch->chunk_type == SCTP_INITIATION_ACK)) { + /* + * special hook, we do NOT return linp or an + * association that is linked to an existing + * association that is under the TCP pool (i.e. no + * listener exists). The endpoint finding routine + * will always find a listner before examining the + * TCP pool. + */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Gak, its in the TCP pool... return NULL"); + } +#endif + if (inp_p) { + *inp_p = NULL; + } + return (NULL); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Now doing SPECIAL find\n"); + } +#endif + retval = sctp_findassociation_special_addr(m, iphlen, + offset, sh, inp_p, netp, to); + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("retval is %p\n", retval); + } +#endif + return (retval); +} + +extern int sctp_max_burst_default; + +extern unsigned int sctp_delayed_sack_time_default; +extern unsigned int sctp_heartbeat_interval_default; +extern unsigned int sctp_pmtu_raise_time_default; +extern unsigned int sctp_shutdown_guard_time_default; +extern unsigned int sctp_secret_lifetime_default; + +extern unsigned int sctp_rto_max_default; +extern unsigned int sctp_rto_min_default; +extern unsigned int sctp_rto_initial_default; +extern unsigned int sctp_init_rto_max_default; +extern unsigned int sctp_valid_cookie_life_default; +extern unsigned int sctp_init_rtx_max_default; +extern unsigned int sctp_assoc_rtx_max_default; +extern unsigned int sctp_path_rtx_max_default; +extern unsigned int sctp_nr_outgoing_streams_default; + +/* + * allocate a sctp_inpcb and setup a temporary binding to a port/all + * addresses. This way if we don't get a bind we by default pick a ephemeral + * port with all addresses bound. + */ +int +sctp_inpcb_alloc(struct socket *so) +{ + /* + * we get called when a new endpoint starts up. We need to allocate + * the sctp_inpcb structure from the zone and init it. Mark it as + * unbound and find a port that we can use as an ephemeral with + * INADDR_ANY. If the user binds later no problem we can then add + * in the specific addresses. And setup the default parameters for + * the EP. + */ + int i, error; + struct sctp_inpcb *inp, *n_inp; + struct sctp_pcb *m; + struct timeval time; + + error = 0; + + /* Hack alert: + * + * This code audits the entire INP list to see if + * any ep's that are in the GONE state are now + * all free. This should not happen really since when + * the last association if freed we should end up deleting + * the inpcb. This code including the locks should + * be taken out ... since the last set of fixes I + * have not seen the "Found a GONE on list" has not + * came out. But i am paranoid and we will leave this + * in at the cost of efficency on allocation of PCB's. + * Probably we should move this to the invariant + * compile options + */ +/* #ifdef INVARIANTS*/ + SCTP_INP_INFO_RLOCK(); + inp = LIST_FIRST(&sctppcbinfo.listhead); + while (inp) { + n_inp = LIST_NEXT(inp, sctp_list); + if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) { + /* finish the job now */ + printf("Found a GONE on list\n"); + SCTP_INP_INFO_RUNLOCK(); + sctp_inpcb_free(inp, 1); + SCTP_INP_INFO_RLOCK(); + } + } + inp = n_inp; + } + SCTP_INP_INFO_RUNLOCK(); +/* #endif INVARIANTS*/ + + SCTP_INP_INFO_WLOCK(); + inp = (struct sctp_inpcb *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_ep); + if (inp == NULL) { + printf("Out of SCTP-INPCB structures - no resources\n"); + SCTP_INP_INFO_WUNLOCK(); + return (ENOBUFS); + } + + /* zap it */ + bzero(inp, sizeof(*inp)); + + /* bump generations */ + inp->ip_inp.inp.inp_socket = so; + + /* setup socket pointers */ + inp->sctp_socket = so; + + /* setup inpcb socket too */ + inp->ip_inp.inp.inp_socket = so; + inp->sctp_frag_point = SCTP_DEFAULT_MAXSEGMENT; +#ifdef IPSEC +#if !(defined(__OpenBSD__) || defined(__APPLE__)) + { + struct inpcbpolicy *pcb_sp = NULL; + error = ipsec_init_pcbpolicy(so, &pcb_sp); + /* Arrange to share the policy */ + inp->ip_inp.inp.inp_sp = pcb_sp; + ((struct in6pcb *)(&inp->ip_inp.inp))->in6p_sp = pcb_sp; + } +#else + /* not sure what to do for openbsd here */ + error = 0; +#endif + if (error != 0) { + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_ep, inp); + SCTP_INP_INFO_WUNLOCK(); + return error; + } +#endif /* IPSEC */ + sctppcbinfo.ipi_count_ep++; +#if defined(__FreeBSD__) || defined(__APPLE__) + inp->ip_inp.inp.inp_gencnt = ++sctppcbinfo.ipi_gencnt_ep; + inp->ip_inp.inp.inp_ip_ttl = ip_defttl; +#else + inp->inp_ip_ttl = ip_defttl; + inp->inp_ip_tos = 0; +#endif + + so->so_pcb = (caddr_t)inp; + + if ((so->so_type == SOCK_DGRAM) || + (so->so_type == SOCK_SEQPACKET)) { + /* UDP style socket */ + inp->sctp_flags = (SCTP_PCB_FLAGS_UDPTYPE | + SCTP_PCB_FLAGS_UNBOUND); + inp->sctp_flags |= (SCTP_PCB_FLAGS_RECVDATAIOEVNT); + /* Be sure it is NON-BLOCKING IO for UDP */ + /*so->so_state |= SS_NBIO;*/ + } else if (so->so_type == SOCK_STREAM) { + /* TCP style socket */ + inp->sctp_flags = (SCTP_PCB_FLAGS_TCPTYPE | + SCTP_PCB_FLAGS_UNBOUND); + inp->sctp_flags |= (SCTP_PCB_FLAGS_RECVDATAIOEVNT); + /* Be sure we have blocking IO bu default */ + so->so_state &= ~SS_NBIO; + } else { + /* + * unsupported socket type (RAW, etc)- in case we missed + * it in protosw + */ + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_ep, inp); + SCTP_INP_INFO_WUNLOCK(); + return (EOPNOTSUPP); + } + inp->sctp_tcbhash = hashinit(sctp_pcbtblsize, +#ifdef __NetBSD__ + HASH_LIST, +#endif + M_PCB, +#if defined(__NetBSD__) || defined(__OpenBSD__) + M_WAITOK, +#endif + &inp->sctp_hashmark); + if (inp->sctp_tcbhash == NULL) { + printf("Out of SCTP-INPCB->hashinit - no resources\n"); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_ep, inp); + SCTP_INP_INFO_WUNLOCK(); + return (ENOBUFS); + } + /* LOCK init's */ + SCTP_INP_LOCK_INIT(inp); + SCTP_ASOC_CREATE_LOCK_INIT(inp); + /* lock the new ep */ + SCTP_INP_WLOCK(inp); + + /* add it to the info area */ + LIST_INSERT_HEAD(&sctppcbinfo.listhead, inp, sctp_list); + SCTP_INP_INFO_WUNLOCK(); + + LIST_INIT(&inp->sctp_addr_list); + LIST_INIT(&inp->sctp_asoc_list); + TAILQ_INIT(&inp->sctp_queue_list); + /* Init the timer structure for signature change */ +#if defined (__FreeBSD__) && __FreeBSD_version >= 500000 + callout_init(&inp->sctp_ep.signature_change.timer, 0); +#else + callout_init(&inp->sctp_ep.signature_change.timer); +#endif + inp->sctp_ep.signature_change.type = SCTP_TIMER_TYPE_NEWCOOKIE; + + /* now init the actual endpoint default data */ + m = &inp->sctp_ep; + + /* setup the base timeout information */ + m->sctp_timeoutticks[SCTP_TIMER_SEND] = SEC_TO_TICKS(SCTP_SEND_SEC); /* needed ? */ + m->sctp_timeoutticks[SCTP_TIMER_INIT] = SEC_TO_TICKS(SCTP_INIT_SEC); /* needed ? */ + m->sctp_timeoutticks[SCTP_TIMER_RECV] = MSEC_TO_TICKS(sctp_delayed_sack_time_default); + m->sctp_timeoutticks[SCTP_TIMER_HEARTBEAT] = sctp_heartbeat_interval_default; /* this is in MSEC */ + m->sctp_timeoutticks[SCTP_TIMER_PMTU] = SEC_TO_TICKS(sctp_pmtu_raise_time_default); + m->sctp_timeoutticks[SCTP_TIMER_MAXSHUTDOWN] = SEC_TO_TICKS(sctp_shutdown_guard_time_default); + m->sctp_timeoutticks[SCTP_TIMER_SIGNATURE] = SEC_TO_TICKS(sctp_secret_lifetime_default); + /* all max/min max are in ms */ + m->sctp_maxrto = sctp_rto_max_default; + m->sctp_minrto = sctp_rto_min_default; + m->initial_rto = sctp_rto_initial_default; + m->initial_init_rto_max = sctp_init_rto_max_default; + + m->max_open_streams_intome = MAX_SCTP_STREAMS; + + m->max_init_times = sctp_init_rtx_max_default; + m->max_send_times = sctp_assoc_rtx_max_default; + m->def_net_failure = sctp_path_rtx_max_default; + m->sctp_sws_sender = SCTP_SWS_SENDER_DEF; + m->sctp_sws_receiver = SCTP_SWS_RECEIVER_DEF; + m->max_burst = sctp_max_burst_default; + /* number of streams to pre-open on a association */ + m->pre_open_stream_count = sctp_nr_outgoing_streams_default; + + /* Add adaption cookie */ + m->adaption_layer_indicator = 0x504C5253; + + /* seed random number generator */ + m->random_counter = 1; + m->store_at = SCTP_SIGNATURE_SIZE; +#if defined(__FreeBSD__) && (__FreeBSD_version < 500000) + read_random_unlimited(m->random_numbers, sizeof(m->random_numbers)); +#elif defined(__APPLE__) || (__FreeBSD_version > 500000) + read_random(m->random_numbers, sizeof(m->random_numbers)); +#elif defined(__OpenBSD__) + get_random_bytes(m->random_numbers, sizeof(m->random_numbers)); +#elif defined(__NetBSD__) && NRND > 0 + rnd_extract_data(m->random_numbers, sizeof(m->random_numbers), + RND_EXTRACT_ANY); +#else + { + u_int32_t *ranm, *ranp; + ranp = (u_int32_t *)&m->random_numbers; + ranm = ranp + (SCTP_SIGNATURE_ALOC_SIZE/sizeof(u_int32_t)); + if ((u_long)ranp % 4) { + /* not a even boundary? */ + ranp = (u_int32_t *)SCTP_SIZE32((u_long)ranp); + } + while (ranp < ranm) { + *ranp = random(); + ranp++; + } + } +#endif + sctp_fill_random_store(m); + + /* Minimum cookie size */ + m->size_of_a_cookie = (sizeof(struct sctp_init_msg) * 2) + + sizeof(struct sctp_state_cookie); + m->size_of_a_cookie += SCTP_SIGNATURE_SIZE; + + /* Setup the initial secret */ + SCTP_GETTIME_TIMEVAL(&time); + m->time_of_secret_change = time.tv_sec; + + for (i = 0; i < SCTP_NUMBER_OF_SECRETS; i++) { + m->secret_key[0][i] = sctp_select_initial_TSN(m); + } + sctp_timer_start(SCTP_TIMER_TYPE_NEWCOOKIE, inp, NULL, NULL); + + /* How long is a cookie good for ? */ + m->def_cookie_life = sctp_valid_cookie_life_default; + SCTP_INP_WUNLOCK(inp); + return (error); +} + + +void +sctp_move_pcb_and_assoc(struct sctp_inpcb *old_inp, struct sctp_inpcb *new_inp, + struct sctp_tcb *stcb) +{ + uint16_t lport, rport; + struct sctppcbhead *head; + struct sctp_laddr *laddr, *oladdr; + + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_INFO_WLOCK(); + SCTP_INP_WLOCK(old_inp); + SCTP_INP_WLOCK(new_inp); + SCTP_TCB_LOCK(stcb); + + new_inp->sctp_ep.time_of_secret_change = + old_inp->sctp_ep.time_of_secret_change; + memcpy(new_inp->sctp_ep.secret_key, old_inp->sctp_ep.secret_key, + sizeof(old_inp->sctp_ep.secret_key)); + new_inp->sctp_ep.current_secret_number = + old_inp->sctp_ep.current_secret_number; + new_inp->sctp_ep.last_secret_number = + old_inp->sctp_ep.last_secret_number; + new_inp->sctp_ep.size_of_a_cookie = old_inp->sctp_ep.size_of_a_cookie; + + /* Copy the port across */ + lport = new_inp->sctp_lport = old_inp->sctp_lport; + rport = stcb->rport; + /* Pull the tcb from the old association */ + LIST_REMOVE(stcb, sctp_tcbhash); + LIST_REMOVE(stcb, sctp_tcblist); + + /* Now insert the new_inp into the TCP connected hash */ + head = &sctppcbinfo.sctp_tcpephash[SCTP_PCBHASH_ALLADDR((lport + rport), + sctppcbinfo.hashtcpmark)]; + + LIST_INSERT_HEAD(head, new_inp, sctp_hash); + + /* Now move the tcb into the endpoint list */ + LIST_INSERT_HEAD(&new_inp->sctp_asoc_list, stcb, sctp_tcblist); + /* + * Question, do we even need to worry about the ep-hash since + * we only have one connection? Probably not :> so lets + * get rid of it and not suck up any kernel memory in that. + */ + SCTP_INP_INFO_WUNLOCK(); + stcb->sctp_socket = new_inp->sctp_socket; + stcb->sctp_ep = new_inp; + if (new_inp->sctp_tcbhash != NULL) { + FREE(new_inp->sctp_tcbhash, M_PCB); + new_inp->sctp_tcbhash = NULL; + } + if ((new_inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) { + /* Subset bound, so copy in the laddr list from the old_inp */ + LIST_FOREACH(oladdr, &old_inp->sctp_addr_list, sctp_nxt_addr) { + laddr = (struct sctp_laddr *)SCTP_ZONE_GET( + sctppcbinfo.ipi_zone_laddr); + if (laddr == NULL) { + /* + * Gak, what can we do? This assoc is really + * HOSED. We probably should send an abort + * here. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Association hosed in TCP model, out of laddr memory\n"); + } +#endif /* SCTP_DEBUG */ + continue; + } + sctppcbinfo.ipi_count_laddr++; + sctppcbinfo.ipi_gencnt_laddr++; + bzero(laddr, sizeof(*laddr)); + laddr->ifa = oladdr->ifa; + LIST_INSERT_HEAD(&new_inp->sctp_addr_list, laddr, + sctp_nxt_addr); + new_inp->laddr_count++; + } + } + SCTP_INP_WUNLOCK(new_inp); + SCTP_INP_WUNLOCK(old_inp); +} + +static int +sctp_isport_inuse(struct sctp_inpcb *inp, uint16_t lport) +{ + struct sctppcbhead *head; + struct sctp_inpcb *t_inp; + + head = &sctppcbinfo.sctp_ephash[SCTP_PCBHASH_ALLADDR(lport, + sctppcbinfo.hashmark)]; + LIST_FOREACH(t_inp, head, sctp_hash) { + if (t_inp->sctp_lport != lport) { + continue; + } + /* This one is in use. */ + /* check the v6/v4 binding issue */ + if ((t_inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) && +#if defined(__FreeBSD__) + (((struct inpcb *)t_inp)->inp_flags & IN6P_IPV6_V6ONLY) +#else +#if defined(__OpenBSD__) + (0) /* For open bsd we do dual bind only */ +#else + (((struct in6pcb *)t_inp)->in6p_flags & IN6P_IPV6_V6ONLY) +#endif +#endif + ) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + /* collision in V6 space */ + return (1); + } else { + /* inp is BOUND_V4 no conflict */ + continue; + } + } else if (t_inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + /* t_inp is bound v4 and v6, conflict always */ + return (1); + } else { + /* t_inp is bound only V4 */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) && +#if defined(__FreeBSD__) + (((struct inpcb *)inp)->inp_flags & IN6P_IPV6_V6ONLY) +#else +#if defined(__OpenBSD__) + (0) /* For open bsd we do dual bind only */ +#else + (((struct in6pcb *)inp)->in6p_flags & IN6P_IPV6_V6ONLY) +#endif +#endif + ) { + /* no conflict */ + continue; + } + /* else fall through to conflict */ + } + return (1); + } + return (0); +} + +#if !(defined(__FreeBSD__) || defined(__APPLE__)) +/* + * Don't know why, but without this there is an unknown reference when + * compiling NetBSD... hmm + */ +extern void in6_sin6_2_sin (struct sockaddr_in *, struct sockaddr_in6 *sin6); +#endif + + +int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp_inpcb_bind(struct socket *so, struct sockaddr *addr, struct thread *p) +#else +sctp_inpcb_bind(struct socket *so, struct sockaddr *addr, struct proc *p) +#endif +{ + /* bind a ep to a socket address */ + struct sctppcbhead *head; + struct sctp_inpcb *inp, *inp_tmp; + struct inpcb *ip_inp; + int bindall; + uint16_t lport; + int error; + + lport = 0; + error = 0; + bindall = 1; + inp = (struct sctp_inpcb *)so->so_pcb; + ip_inp = (struct inpcb *)so->so_pcb; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + if (addr) { + printf("Bind called port:%d\n", + ntohs(((struct sockaddr_in *)addr)->sin_port)); + printf("Addr :"); + sctp_print_address(addr); + } + } +#endif /* SCTP_DEBUG */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == 0) { + /* already did a bind, subsequent binds NOT allowed ! */ + return (EINVAL); + } + + if (addr != NULL) { + if (addr->sa_family == AF_INET) { + struct sockaddr_in *sin; + + /* IPV6_V6ONLY socket? */ + if ( +#if defined(__FreeBSD__) || defined(__APPLE__) + (ip_inp->inp_flags & IN6P_IPV6_V6ONLY) +#else +#if defined(__OpenBSD__) + (0) /* For openbsd we do dual bind only */ +#else + (((struct in6pcb *)inp)->in6p_flags & IN6P_IPV6_V6ONLY) +#endif +#endif + ) { + return (EINVAL); + } + + if (addr->sa_len != sizeof(*sin)) + return (EINVAL); + + sin = (struct sockaddr_in *)addr; + lport = sin->sin_port; + + if (sin->sin_addr.s_addr != INADDR_ANY) { + bindall = 0; + } + } else if (addr->sa_family == AF_INET6) { + /* Only for pure IPv6 Address. (No IPv4 Mapped!) */ + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)addr; + + if (addr->sa_len != sizeof(*sin6)) + return (EINVAL); + + lport = sin6->sin6_port; + if (!IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + bindall = 0; + /* KAME hack: embed scopeid */ +#if defined(SCTP_BASE_FREEBSD) || defined(__APPLE__) + if (in6_embedscope(&sin6->sin6_addr, sin6, + ip_inp, NULL) != 0) + return (EINVAL); +#elif defined(__FreeBSD__) + error = scope6_check_id(sin6, ip6_use_defzone); + if (error != 0) + return (error); +#else + if (in6_embedscope(&sin6->sin6_addr, sin6) != 0) { + return (EINVAL); + } +#endif + } +#ifndef SCOPEDROUTING + /* this must be cleared for ifa_ifwithaddr() */ + sin6->sin6_scope_id = 0; +#endif /* SCOPEDROUTING */ + } else { + return (EAFNOSUPPORT); + } + } + SCTP_INP_INFO_WLOCK(); + SCTP_INP_WLOCK(inp); + /* increase our count due to the unlock we do */ + SCTP_INP_INCR_REF(inp); + if (lport) { + /* + * Did the caller specify a port? if so we must see if a + * ep already has this one bound. + */ + /* got to be root to get at low ports */ + if (ntohs(lport) < IPPORT_RESERVED) { + if (p && (error = +#ifdef __FreeBSD__ +#if __FreeBSD_version >= 500000 + suser_cred(p->td_ucred, 0) +#else + suser(p) +#endif +#elif defined(__NetBSD__) || defined(__APPLE__) + suser(p->p_ucred, &p->p_acflag) +#else + suser(p, 0) +#endif + )) { + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_WUNLOCK(); + return (error); + } + } + if (p == NULL) { + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_WUNLOCK(); + return (error); + } + SCTP_INP_WUNLOCK(inp); + inp_tmp = sctp_pcb_findep(addr, 0, 1); + if (inp_tmp != NULL) { + /* lock guy returned and lower count + * note that we are not bound so inp_tmp + * should NEVER be inp. And it is this + * inp (inp_tmp) that gets the reference + * bump, so we must lower it. + */ + SCTP_INP_WLOCK(inp_tmp); + SCTP_INP_DECR_REF(inp_tmp); + SCTP_INP_WUNLOCK(inp_tmp); + + /* unlock info */ + SCTP_INP_INFO_WUNLOCK(); + return (EADDRNOTAVAIL); + } + SCTP_INP_WLOCK(inp); + if (bindall) { + /* verify that no lport is not used by a singleton */ + if (sctp_isport_inuse(inp, lport)) { + /* Sorry someone already has this one bound */ + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_WUNLOCK(); + return (EADDRNOTAVAIL); + } + } + } else { + /* + * get any port but lets make sure no one has any address + * with this port bound + */ + + /* + * setup the inp to the top (I could use the union but this + * is just as easy + */ + uint32_t port_guess; + uint16_t port_attempt; + int not_done=1; + + while (not_done) { + port_guess = sctp_select_initial_TSN(&inp->sctp_ep); + port_attempt = (port_guess & 0x0000ffff); + if (port_attempt == 0) { + goto next_half; + } + if (port_attempt < IPPORT_RESERVED) { + port_attempt += IPPORT_RESERVED; + } + + if (sctp_isport_inuse(inp, htons(port_attempt)) == 0) { + /* got a port we can use */ + not_done = 0; + continue; + } + /* try upper half */ + next_half: + port_attempt = ((port_guess >> 16) & 0x0000ffff); + if (port_attempt == 0) { + goto last_try; + } + if (port_attempt < IPPORT_RESERVED) { + port_attempt += IPPORT_RESERVED; + } + if (sctp_isport_inuse(inp, htons(port_attempt)) == 0) { + /* got a port we can use */ + not_done = 0; + continue; + } + /* try two half's added together */ + last_try: + port_attempt = (((port_guess >> 16) & 0x0000ffff) + (port_guess & 0x0000ffff)); + if (port_attempt == 0) { + /* get a new random number */ + continue; + } + if (port_attempt < IPPORT_RESERVED) { + port_attempt += IPPORT_RESERVED; + } + if (sctp_isport_inuse(inp, htons(port_attempt)) == 0) { + /* got a port we can use */ + not_done = 0; + continue; + } + } + /* we don't get out of the loop until we have a port */ + lport = htons(port_attempt); + } + SCTP_INP_DECR_REF(inp); + if (inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE|SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { + /* this really should not happen. The guy + * did a non-blocking bind and then did a close + * at the same time. + */ + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_WUNLOCK(); + return (EINVAL); + } + /* ok we look clear to give out this port, so lets setup the binding */ + if (bindall) { + /* binding to all addresses, so just set in the proper flags */ + inp->sctp_flags |= (SCTP_PCB_FLAGS_BOUNDALL | + SCTP_PCB_FLAGS_DO_ASCONF); + /* set the automatic addr changes from kernel flag */ + if (sctp_auto_asconf == 0) { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_AUTO_ASCONF; + } else { + inp->sctp_flags |= SCTP_PCB_FLAGS_AUTO_ASCONF; + } + } else { + /* + * bind specific, make sure flags is off and add a new address + * structure to the sctp_addr_list inside the ep structure. + * + * We will need to allocate one and insert it at the head. + * The socketopt call can just insert new addresses in there + * as well. It will also have to do the embed scope kame hack + * too (before adding). + */ + struct ifaddr *ifa; + struct sockaddr_storage store_sa; + + memset(&store_sa, 0, sizeof(store_sa)); + if (addr->sa_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)&store_sa; + memcpy(sin, addr, sizeof(struct sockaddr_in)); + sin->sin_port = 0; + } else if (addr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)&store_sa; + memcpy(sin6, addr, sizeof(struct sockaddr_in6)); + sin6->sin6_port = 0; + } + /* + * first find the interface with the bound address + * need to zero out the port to find the address! yuck! + * can't do this earlier since need port for sctp_pcb_findep() + */ + ifa = sctp_find_ifa_by_addr((struct sockaddr *)&store_sa); + if (ifa == NULL) { + /* Can't find an interface with that address */ + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_WUNLOCK(); + return (EADDRNOTAVAIL); + } + if (addr->sa_family == AF_INET6) { + struct in6_ifaddr *ifa6; + ifa6 = (struct in6_ifaddr *)ifa; + /* + * allow binding of deprecated addresses as per + * RFC 2462 and ipng discussion + */ + if (ifa6->ia6_flags & (IN6_IFF_DETACHED | + IN6_IFF_ANYCAST | + IN6_IFF_NOTREADY)) { + /* Can't bind a non-existent addr. */ + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_WUNLOCK(); + return (EINVAL); + } + } + /* we're not bound all */ + inp->sctp_flags &= ~SCTP_PCB_FLAGS_BOUNDALL; +#if 0 /* use sysctl now */ + /* don't allow automatic addr changes from kernel */ + inp->sctp_flags &= ~SCTP_PCB_FLAGS_AUTO_ASCONF; +#endif + /* set the automatic addr changes from kernel flag */ + if (sctp_auto_asconf == 0) { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_AUTO_ASCONF; + } else { + inp->sctp_flags |= SCTP_PCB_FLAGS_AUTO_ASCONF; + } + /* allow bindx() to send ASCONF's for binding changes */ + inp->sctp_flags |= SCTP_PCB_FLAGS_DO_ASCONF; + /* add this address to the endpoint list */ + error = sctp_insert_laddr(&inp->sctp_addr_list, ifa); + if (error != 0) { + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_WUNLOCK(); + return (error); + } + inp->laddr_count++; + } + /* find the bucket */ + head = &sctppcbinfo.sctp_ephash[SCTP_PCBHASH_ALLADDR(lport, + sctppcbinfo.hashmark)]; + /* put it in the bucket */ + LIST_INSERT_HEAD(head, inp, sctp_hash); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Main hash to bind at head:%p, bound port:%d\n", head, ntohs(lport)); + } +#endif + /* set in the port */ + inp->sctp_lport = lport; + + /* turn off just the unbound flag */ + inp->sctp_flags &= ~SCTP_PCB_FLAGS_UNBOUND; + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_WUNLOCK(); + return (0); +} + + +static void +sctp_iterator_inp_being_freed(struct sctp_inpcb *inp, struct sctp_inpcb *inp_next) +{ + struct sctp_iterator *it; + /* We enter with the only the ITERATOR_LOCK in place and + * A write lock on the inp_info stuff. + */ + + /* Go through all iterators, we must do this since + * it is possible that some iterator does NOT have + * the lock, but is waiting for it. And the one that + * had the lock has either moved in the last iteration + * or we just cleared it above. We need to find all + * of those guys. The list of iterators should never + * be very big though. + */ + LIST_FOREACH(it, &sctppcbinfo.iteratorhead, sctp_nxt_itr) { + if (it == inp->inp_starting_point_for_iterator) + /* skip this guy, he's special */ + continue; + if (it->inp == inp) { + /* This is tricky and we DON'T lock the iterator. + * Reason is he's running but waiting for me since + * inp->inp_starting_point_for_iterator has the lock + * on me (the guy above we skipped). This tells us + * its is not running but waiting for inp->inp_starting_point_for_iterator + * to be released by the guy that does have our INP in a lock. + */ + if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { + it->inp = NULL; + it->stcb = NULL; + } else { + /* set him up to do the next guy not me */ + it->inp = inp_next; + it->stcb = NULL; + } + } + } + it = inp->inp_starting_point_for_iterator; + if (it) { + if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { + it->inp = NULL; + } else { + it->inp = inp_next; + } + it->stcb = NULL; + } +} + +/* release sctp_inpcb unbind the port */ +void +sctp_inpcb_free(struct sctp_inpcb *inp, int immediate) +{ + /* + * Here we free a endpoint. We must find it (if it is in the Hash + * table) and remove it from there. Then we must also find it in + * the overall list and remove it from there. After all removals are + * complete then any timer has to be stopped. Then start the actual + * freeing. + * a) Any local lists. + * b) Any associations. + * c) The hash of all associations. + * d) finally the ep itself. + */ + struct sctp_pcb *m; + struct sctp_inpcb *inp_save; + struct sctp_tcb *asoc, *nasoc; + struct sctp_laddr *laddr, *nladdr; + struct inpcb *ip_pcb; + struct socket *so; + struct sctp_socket_q_list *sq; +#if !defined(__FreeBSD__) || __FreeBSD_version < 500000 + struct rtentry *rt; +#endif + int s, cnt; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + SCTP_ASOC_CREATE_LOCK(inp); + SCTP_INP_WLOCK(inp); + + if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { + /* been here before */ + splx(s); + printf("Endpoint was all gone (dup free)?\n"); + SCTP_INP_WUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + return; + } + sctp_timer_stop(SCTP_TIMER_TYPE_NEWCOOKIE, inp, NULL, NULL); + + if (inp->control) { + sctp_m_freem(inp->control); + inp->control = NULL; + } + if (inp->pkt) { + sctp_m_freem(inp->pkt); + inp->pkt = NULL; + } + so = inp->sctp_socket; + m = &inp->sctp_ep; + ip_pcb = &inp->ip_inp.inp; /* we could just cast the main + * pointer here but I will + * be nice :> (i.e. ip_pcb = ep;) + */ + + if (immediate == 0) { + int cnt_in_sd; + cnt_in_sd = 0; + for ((asoc = LIST_FIRST(&inp->sctp_asoc_list)); asoc != NULL; + asoc = nasoc) { + nasoc = LIST_NEXT(asoc, sctp_tcblist); + if ((SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_COOKIE_WAIT) || + (SCTP_GET_STATE(&asoc->asoc) == SCTP_STATE_COOKIE_ECHOED)) { + /* Just abandon things in the front states */ + SCTP_TCB_LOCK(asoc); + SCTP_INP_WUNLOCK(inp); + sctp_free_assoc(inp, asoc); + SCTP_INP_WLOCK(inp); + continue; + } else { + asoc->asoc.state |= SCTP_STATE_CLOSED_SOCKET; + } + if ((asoc->asoc.size_on_delivery_queue > 0) || + (asoc->asoc.size_on_reasm_queue > 0) || + (asoc->asoc.size_on_all_streams > 0) || + (so && (so->so_rcv.sb_cc > 0)) + ) { + /* Left with Data unread */ + struct mbuf *op_err; + MGET(op_err, M_DONTWAIT, MT_DATA); + if (op_err) { + /* Fill in the user initiated abort */ + struct sctp_paramhdr *ph; + op_err->m_len = + sizeof(struct sctp_paramhdr); + ph = mtod(op_err, + struct sctp_paramhdr *); + ph->param_type = htons( + SCTP_CAUSE_USER_INITIATED_ABT); + ph->param_length = htons(op_err->m_len); + } + SCTP_TCB_LOCK(asoc); + sctp_send_abort_tcb(asoc, op_err); + + SCTP_INP_WUNLOCK(inp); + sctp_free_assoc(inp, asoc); + SCTP_INP_WLOCK(inp); + continue; + } else if (TAILQ_EMPTY(&asoc->asoc.send_queue) && + TAILQ_EMPTY(&asoc->asoc.sent_queue)) { + if ((SCTP_GET_STATE(&asoc->asoc) != SCTP_STATE_SHUTDOWN_SENT) && + (SCTP_GET_STATE(&asoc->asoc) != SCTP_STATE_SHUTDOWN_ACK_SENT)) { + /* there is nothing queued to send, so I send shutdown */ + SCTP_TCB_LOCK(asoc); + sctp_send_shutdown(asoc, asoc->asoc.primary_destination); + asoc->asoc.state = SCTP_STATE_SHUTDOWN_SENT; + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, asoc->sctp_ep, asoc, + asoc->asoc.primary_destination); + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, asoc->sctp_ep, asoc, + asoc->asoc.primary_destination); + sctp_chunk_output(inp, asoc, 1); + SCTP_TCB_UNLOCK(asoc); + } + } else { + /* mark into shutdown pending */ + asoc->asoc.state |= SCTP_STATE_SHUTDOWN_PENDING; + } + cnt_in_sd++; + } + /* now is there some left in our SHUTDOWN state? */ + if (cnt_in_sd) { + inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_GONE; + splx(s); + SCTP_INP_WUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + return; + } + } +#if defined(__FreeBSD__) && __FreeBSD_version >= 503000 + if (inp->refcount) { + sctp_timer_start(SCTP_TIMER_TYPE_INPKILL, inp, NULL, NULL); + SCTP_INP_WUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + return; + } +#endif + inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_ALLGONE; +#if !defined(__FreeBSD__) || __FreeBSD_version < 500000 + rt = ip_pcb->inp_route.ro_rt; +#endif + + callout_stop(&inp->sctp_ep.signature_change.timer); + + if (so) { + /* First take care of socket level things */ +#ifdef IPSEC +#ifdef __OpenBSD__ + /* XXX IPsec cleanup here */ + int s2 = spltdb(); + if (ip_pcb->inp_tdb_in) + TAILQ_REMOVE(&ip_pcb->inp_tdb_in->tdb_inp_in, + ip_pcb, inp_tdb_in_next); + if (ip_pcb->inp_tdb_out) + TAILQ_REMOVE(&ip_pcb->inp_tdb_out->tdb_inp_out, ip_pcb, + inp_tdb_out_next); + if (ip_pcb->inp_ipsec_localid) + ipsp_reffree(ip_pcb->inp_ipsec_localid); + if (ip_pcb->inp_ipsec_remoteid) + ipsp_reffree(ip_pcb->inp_ipsec_remoteid); + if (ip_pcb->inp_ipsec_localcred) + ipsp_reffree(ip_pcb->inp_ipsec_localcred); + if (ip_pcb->inp_ipsec_remotecred) + ipsp_reffree(ip_pcb->inp_ipsec_remotecred); + if (ip_pcb->inp_ipsec_localauth) + ipsp_reffree(ip_pcb->inp_ipsec_localauth); + if (ip_pcb->inp_ipsec_remoteauth) + ipsp_reffree(ip_pcb->inp_ipsec_remoteauth); + splx(s2); +#else + ipsec4_delete_pcbpolicy(ip_pcb); +#endif +#endif /*IPSEC*/ +#if defined(__FreeBSD__) && __FreeBSD_version > 500000 + ACCEPT_LOCK(); + SOCK_LOCK(so); +#endif + so->so_pcb = 0; +#if defined(__FreeBSD__) && __FreeBSD_version > 500000 + sotryfree(so); +#else + sofree(so); +#endif + } + + if (ip_pcb->inp_options) { + (void)m_free(ip_pcb->inp_options); + ip_pcb->inp_options = 0; + } +#if !defined(__FreeBSD__) || __FreeBSD_version < 500000 + if (rt) { + RTFREE(rt); + ip_pcb->inp_route.ro_rt = 0; + } +#endif + if (ip_pcb->inp_moptions) { + ip_freemoptions(ip_pcb->inp_moptions); + ip_pcb->inp_moptions = 0; + } +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + inp->inp_vflag = 0; +#else + ip_pcb->inp_vflag = 0; +#endif + + /* Now the sctp_pcb things */ + + /* + * free each asoc if it is not already closed/free. we can't use + * the macro here since le_next will get freed as part of the + * sctp_free_assoc() call. + */ + cnt = 0; + for ((asoc = LIST_FIRST(&inp->sctp_asoc_list)); asoc != NULL; + asoc = nasoc) { + nasoc = LIST_NEXT(asoc, sctp_tcblist); + SCTP_TCB_LOCK(asoc); + if (SCTP_GET_STATE(&asoc->asoc) != SCTP_STATE_COOKIE_WAIT) { + struct mbuf *op_err; + MGET(op_err, M_DONTWAIT, MT_DATA); + if (op_err) { + /* Fill in the user initiated abort */ + struct sctp_paramhdr *ph; + op_err->m_len = sizeof(struct sctp_paramhdr); + ph = mtod(op_err, struct sctp_paramhdr *); + ph->param_type = htons( + SCTP_CAUSE_USER_INITIATED_ABT); + ph->param_length = htons(op_err->m_len); + } + sctp_send_abort_tcb(asoc, op_err); + } + cnt++; + /* + * sctp_free_assoc() will call sctp_inpcb_free(), + * if SCTP_PCB_FLAGS_SOCKET_GONE set. + * So, we clear it before sctp_free_assoc() making sure + * no double sctp_inpcb_free(). + */ + inp->sctp_flags &= ~SCTP_PCB_FLAGS_SOCKET_GONE; + SCTP_INP_WUNLOCK(inp); + sctp_free_assoc(inp, asoc); + SCTP_INP_WLOCK(inp); + } + while ((sq = TAILQ_FIRST(&inp->sctp_queue_list)) != NULL) { + TAILQ_REMOVE(&inp->sctp_queue_list, sq, next_sq); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_sockq, sq); + sctppcbinfo.ipi_count_sockq--; + sctppcbinfo.ipi_gencnt_sockq++; + } + inp->sctp_socket = 0; + /* Now first we remove ourselves from the overall list of all EP's */ + + /* Unlock inp first, need correct order */ + SCTP_INP_WUNLOCK(inp); + /* now iterator lock */ + SCTP_ITERATOR_LOCK(); + /* now info lock */ + SCTP_INP_INFO_WLOCK(); + /* now reget the inp lock */ + SCTP_INP_WLOCK(inp); + + inp_save = LIST_NEXT(inp, sctp_list); + LIST_REMOVE(inp, sctp_list); + /* + * Now the question comes as to if this EP was ever bound at all. + * If it was, then we must pull it out of the EP hash list. + */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) != + SCTP_PCB_FLAGS_UNBOUND) { + /* + * ok, this guy has been bound. It's port is somewhere + * in the sctppcbinfo hash table. Remove it! + */ + LIST_REMOVE(inp, sctp_hash); + } + /* fix any iterators only after out of the list */ + sctp_iterator_inp_being_freed(inp, inp_save); + SCTP_ITERATOR_UNLOCK(); + /* + * if we have an address list the following will free the list of + * ifaddr's that are set into this ep. Again macro limitations here, + * since the LIST_FOREACH could be a bad idea. + */ + for ((laddr = LIST_FIRST(&inp->sctp_addr_list)); laddr != NULL; + laddr = nladdr) { + nladdr = LIST_NEXT(laddr, sctp_nxt_addr); + LIST_REMOVE(laddr, sctp_nxt_addr); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_laddr, laddr); + sctppcbinfo.ipi_gencnt_laddr++; + sctppcbinfo.ipi_count_laddr--; + } + /* Now lets see about freeing the EP hash table. */ + if (inp->sctp_tcbhash != NULL) { + FREE(inp->sctp_tcbhash, M_PCB); + inp->sctp_tcbhash = 0; + } + SCTP_INP_WUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + SCTP_INP_LOCK_DESTROY(inp); + SCTP_ASOC_CREATE_LOCK_DESTROY(inp); + + /* Now we must put the ep memory back into the zone pool */ + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_ep, inp); + sctppcbinfo.ipi_count_ep--; + + SCTP_INP_INFO_WUNLOCK(); + splx(s); +} + + +struct sctp_nets * +sctp_findnet(struct sctp_tcb *stcb, struct sockaddr *addr) +{ + struct sctp_nets *net; + struct sockaddr_in *sin; + struct sockaddr_in6 *sin6; + /* use the peer's/remote port for lookup if unspecified */ + sin = (struct sockaddr_in *)addr; + sin6 = (struct sockaddr_in6 *)addr; +#if 0 /* why do we need to check the port for a nets list on an assoc? */ + if (stcb->rport != sin->sin_port) { + /* we cheat and just a sin for this test */ + return (NULL); + } +#endif + /* locate the address */ + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + if (sctp_cmpaddr(addr, (struct sockaddr *)&net->ro._l_addr)) + return (net); + } + return (NULL); +} + + +/* + * add's a remote endpoint address, done with the INIT/INIT-ACK + * as well as when a ASCONF arrives that adds it. It will also + * initialize all the cwnd stats of stuff. + */ +int +sctp_is_address_on_local_host(struct sockaddr *addr) +{ + struct ifnet *ifn; + struct ifaddr *ifa; + TAILQ_FOREACH(ifn, &ifnet, if_list) { + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (addr->sa_family == ifa->ifa_addr->sa_family) { + /* same family */ + if (addr->sa_family == AF_INET) { + struct sockaddr_in *sin, *sin_c; + sin = (struct sockaddr_in *)addr; + sin_c = (struct sockaddr_in *) + ifa->ifa_addr; + if (sin->sin_addr.s_addr == + sin_c->sin_addr.s_addr) { + /* we are on the same machine */ + return (1); + } + } else if (addr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6, *sin_c6; + sin6 = (struct sockaddr_in6 *)addr; + sin_c6 = (struct sockaddr_in6 *) + ifa->ifa_addr; + if (SCTP6_ARE_ADDR_EQUAL(&sin6->sin6_addr, + &sin_c6->sin6_addr)) { + /* we are on the same machine */ + return (1); + } + } + } + } + } + return (0); +} + +int +sctp_add_remote_addr(struct sctp_tcb *stcb, struct sockaddr *newaddr, + int set_scope, int from) +{ + /* + * The following is redundant to the same lines in the + * sctp_aloc_assoc() but is needed since other's call the add + * address function + */ + struct sctp_nets *net, *netfirst; + int addr_inscope; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Adding an address (from:%d) to the peer: ", from); + sctp_print_address(newaddr); + } +#endif + netfirst = sctp_findnet(stcb, newaddr); + if (netfirst) { + /* + * Lie and return ok, we don't want to make the association + * go away for this behavior. It will happen in the TCP model + * in a connected socket. It does not reach the hash table + * until after the association is built so it can't be found. + * Mark as reachable, since the initial creation will have + * been cleared and the NOT_IN_ASSOC flag will have been + * added... and we don't want to end up removing it back out. + */ + if (netfirst->dest_state & SCTP_ADDR_UNCONFIRMED) { + netfirst->dest_state = (SCTP_ADDR_REACHABLE| + SCTP_ADDR_UNCONFIRMED); + } else { + netfirst->dest_state = SCTP_ADDR_REACHABLE; + } + + return (0); + } + addr_inscope = 1; + if (newaddr->sa_family == AF_INET) { + struct sockaddr_in *sin; + sin = (struct sockaddr_in *)newaddr; + if (sin->sin_addr.s_addr == 0) { + /* Invalid address */ + return (-1); + } + /* zero out the bzero area */ + memset(&sin->sin_zero, 0, sizeof(sin->sin_zero)); + + /* assure len is set */ + sin->sin_len = sizeof(struct sockaddr_in); + if (set_scope) { +#ifdef SCTP_DONT_DO_PRIVADDR_SCOPE + stcb->ipv4_local_scope = 1; +#else + if (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) { + stcb->asoc.ipv4_local_scope = 1; + } +#endif /* SCTP_DONT_DO_PRIVADDR_SCOPE */ + + if (sctp_is_address_on_local_host(newaddr)) { + stcb->asoc.loopback_scope = 1; + stcb->asoc.ipv4_local_scope = 1; + stcb->asoc.local_scope = 1; + stcb->asoc.site_scope = 1; + } + } else { + if (from == 8) { + /* From connectx */ + if (sctp_is_address_on_local_host(newaddr)) { + stcb->asoc.loopback_scope = 1; + stcb->asoc.ipv4_local_scope = 1; + stcb->asoc.local_scope = 1; + stcb->asoc.site_scope = 1; + } + } + /* Validate the address is in scope */ + if ((IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) && + (stcb->asoc.ipv4_local_scope == 0)) { + addr_inscope = 0; + } + } + } else if (newaddr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)newaddr; + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + /* Invalid address */ + return (-1); + } + /* assure len is set */ + sin6->sin6_len = sizeof(struct sockaddr_in6); + if (set_scope) { + if (sctp_is_address_on_local_host(newaddr)) { + stcb->asoc.loopback_scope = 1; + stcb->asoc.local_scope = 1; + stcb->asoc.ipv4_local_scope = 1; + stcb->asoc.site_scope = 1; + } else if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { + /* + * If the new destination is a LINK_LOCAL + * we must have common site scope. Don't set + * the local scope since we may not share all + * links, only loopback can do this. + * Links on the local network would also + * be on our private network for v4 too. + */ + stcb->asoc.ipv4_local_scope = 1; + stcb->asoc.site_scope = 1; + } else if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) { + /* + * If the new destination is SITE_LOCAL + * then we must have site scope in common. + */ + stcb->asoc.site_scope = 1; + } + } else { + if (from == 8) { + /* From connectx */ + if (sctp_is_address_on_local_host(newaddr)) { + stcb->asoc.loopback_scope = 1; + stcb->asoc.ipv4_local_scope = 1; + stcb->asoc.local_scope = 1; + stcb->asoc.site_scope = 1; + } + } + /* Validate the address is in scope */ + if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr) && + (stcb->asoc.loopback_scope == 0)) { + addr_inscope = 0; + } else if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) && + (stcb->asoc.local_scope == 0)) { + addr_inscope = 0; + } else if (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr) && + (stcb->asoc.site_scope == 0)) { + addr_inscope = 0; + } + } + } else { + /* not supported family type */ + return (-1); + } + net = (struct sctp_nets *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_net); + if (net == NULL) { + return (-1); + } + sctppcbinfo.ipi_count_raddr++; + sctppcbinfo.ipi_gencnt_raddr++; + bzero(net, sizeof(*net)); + memcpy(&net->ro._l_addr, newaddr, newaddr->sa_len); + if (newaddr->sa_family == AF_INET) { + ((struct sockaddr_in *)&net->ro._l_addr)->sin_port = stcb->rport; + } else if (newaddr->sa_family == AF_INET6) { + ((struct sockaddr_in6 *)&net->ro._l_addr)->sin6_port = stcb->rport; + } + net->addr_is_local = sctp_is_address_on_local_host(newaddr); + net->failure_threshold = stcb->asoc.def_net_failure; + if (addr_inscope == 0) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Adding an address which is OUT OF SCOPE\n"); + } +#endif /* SCTP_DEBUG */ + net->dest_state = (SCTP_ADDR_REACHABLE | + SCTP_ADDR_OUT_OF_SCOPE); + } else { + if (from == 8) + /* 8 is passed by connect_x */ + net->dest_state = SCTP_ADDR_REACHABLE; + else + net->dest_state = SCTP_ADDR_REACHABLE | + SCTP_ADDR_UNCONFIRMED; + } + net->RTO = stcb->asoc.initial_rto; + stcb->asoc.numnets++; + net->ref_count = 1; + + /* Init the timer structure */ +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + callout_init(&net->rxt_timer.timer, 0); +#else + callout_init(&net->rxt_timer.timer); +#endif + + /* Now generate a route for this guy */ + /* KAME hack: embed scopeid */ + if (newaddr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; +#if defined(SCTP_BASE_FREEBSD) || defined(__APPLE__) + (void)in6_embedscope(&sin6->sin6_addr, sin6, + &stcb->sctp_ep->ip_inp.inp, NULL); +#else + (void)in6_embedscope(&sin6->sin6_addr, sin6); +#endif +#ifndef SCOPEDROUTING + sin6->sin6_scope_id = 0; +#endif + } +#if defined(__FreeBSD__) || defined(__APPLE__) + rtalloc_ign((struct route *)&net->ro, 0UL); +#else + rtalloc((struct route *)&net->ro); +#endif + if (newaddr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)&net->ro._l_addr; + (void)in6_recoverscope(sin6, &sin6->sin6_addr, NULL); + } + if ((net->ro.ro_rt) && + (net->ro.ro_rt->rt_ifp)) { + net->mtu = net->ro.ro_rt->rt_ifp->if_mtu; + if (from == 1) { + stcb->asoc.smallest_mtu = net->mtu; + } + /* start things off to match mtu of interface please. */ + net->ro.ro_rt->rt_rmx.rmx_mtu = net->ro.ro_rt->rt_ifp->if_mtu; + } else { + net->mtu = stcb->asoc.smallest_mtu; + } + if (stcb->asoc.smallest_mtu > net->mtu) { + stcb->asoc.smallest_mtu = net->mtu; + } + /* We take the max of the burst limit times a MTU or the INITIAL_CWND. + * We then limit this to 4 MTU's of sending. + */ + net->cwnd = min((net->mtu * 4), max((stcb->asoc.max_burst * net->mtu), SCTP_INITIAL_CWND)); + + /* we always get at LEAST 2 MTU's */ + if (net->cwnd < (2 * net->mtu)) { + net->cwnd = 2 * net->mtu; + } + + net->ssthresh = stcb->asoc.peers_rwnd; + + net->src_addr_selected = 0; + netfirst = TAILQ_FIRST(&stcb->asoc.nets); + if (net->ro.ro_rt == NULL) { + /* Since we have no route put it at the back */ + TAILQ_INSERT_TAIL(&stcb->asoc.nets, net, sctp_next); + } else if (netfirst == NULL) { + /* We are the first one in the pool. */ + TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next); + } else if (netfirst->ro.ro_rt == NULL) { + /* + * First one has NO route. Place this one ahead of the + * first one. + */ + TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next); + } else if (net->ro.ro_rt->rt_ifp != netfirst->ro.ro_rt->rt_ifp) { + /* + * This one has a different interface than the one at the + * top of the list. Place it ahead. + */ + TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next); + } else { + /* + * Ok we have the same interface as the first one. Move + * forward until we find either + * a) one with a NULL route... insert ahead of that + * b) one with a different ifp.. insert after that. + * c) end of the list.. insert at the tail. + */ + struct sctp_nets *netlook; + do { + netlook = TAILQ_NEXT(netfirst, sctp_next); + if (netlook == NULL) { + /* End of the list */ + TAILQ_INSERT_TAIL(&stcb->asoc.nets, net, + sctp_next); + break; + } else if (netlook->ro.ro_rt == NULL) { + /* next one has NO route */ + TAILQ_INSERT_BEFORE(netfirst, net, sctp_next); + break; + } else if (netlook->ro.ro_rt->rt_ifp != + net->ro.ro_rt->rt_ifp) { + TAILQ_INSERT_AFTER(&stcb->asoc.nets, netlook, + net, sctp_next); + break; + } + /* Shift forward */ + netfirst = netlook; + } while (netlook != NULL); + } + /* got to have a primary set */ + if (stcb->asoc.primary_destination == 0) { + stcb->asoc.primary_destination = net; + } else if ((stcb->asoc.primary_destination->ro.ro_rt == NULL) && + (net->ro.ro_rt)) { + /* No route to current primary adopt new primary */ + stcb->asoc.primary_destination = net; + } + sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, stcb->sctp_ep, stcb, + net); + + return (0); +} + + +/* + * allocate an association and add it to the endpoint. The caller must + * be careful to add all additional addresses once they are know right + * away or else the assoc will be may experience a blackout scenario. + */ +struct sctp_tcb * +sctp_aloc_assoc(struct sctp_inpcb *inp, struct sockaddr *firstaddr, + int for_a_init, int *error, uint32_t override_tag) +{ + struct sctp_tcb *stcb; + struct sctp_association *asoc; + struct sctpasochead *head; + uint16_t rport; + int err; + + /* + * Assumption made here: + * Caller has done a sctp_findassociation_ep_addr(ep, addr's); + * to make sure the address does not exist already. + */ + if (sctppcbinfo.ipi_count_asoc >= SCTP_MAX_NUM_OF_ASOC) { + /* Hit max assoc, sorry no more */ + *error = ENOBUFS; + return (NULL); + } + SCTP_INP_RLOCK(inp); + if (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) { + /* + * If its in the TCP pool, its NOT allowed to create an + * association. The parent listener needs to call + * sctp_aloc_assoc.. or the one-2-many socket. If a + * peeled off, or connected one does this.. its an error. + */ + SCTP_INP_RUNLOCK(inp); + *error = EINVAL; + return (NULL); + } + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB3) { + printf("Allocate an association for peer:"); + if (firstaddr) + sctp_print_address(firstaddr); + else + printf("None\n"); + printf("Port:%d\n", + ntohs(((struct sockaddr_in *)firstaddr)->sin_port)); + } +#endif /* SCTP_DEBUG */ + if (firstaddr->sa_family == AF_INET) { + struct sockaddr_in *sin; + sin = (struct sockaddr_in *)firstaddr; + if ((sin->sin_port == 0) || (sin->sin_addr.s_addr == 0)) { + /* Invalid address */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB3) { + printf("peer address invalid\n"); + } +#endif + SCTP_INP_RUNLOCK(inp); + *error = EINVAL; + return (NULL); + } + rport = sin->sin_port; + } else if (firstaddr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)firstaddr; + if ((sin6->sin6_port == 0) || + (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))) { + /* Invalid address */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB3) { + printf("peer address invalid\n"); + } +#endif + SCTP_INP_RUNLOCK(inp); + *error = EINVAL; + return (NULL); + } + rport = sin6->sin6_port; + } else { + /* not supported family type */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB3) { + printf("BAD family %d\n", firstaddr->sa_family); + } +#endif + SCTP_INP_RUNLOCK(inp); + *error = EINVAL; + return (NULL); + } + SCTP_INP_RUNLOCK(inp); + if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) { + /* + * If you have not performed a bind, then we need to do + * the ephemerial bind for you. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB3) { + printf("Doing implicit BIND\n"); + } +#endif + + if ((err = sctp_inpcb_bind(inp->sctp_socket, + (struct sockaddr *)NULL, +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + (struct thread *)NULL +#else + (struct proc *)NULL +#endif + ))){ + /* bind error, probably perm */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB3) { + printf("BIND FAILS ret:%d\n", err); + } +#endif + + *error = err; + return (NULL); + } + } + stcb = (struct sctp_tcb *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_asoc); + if (stcb == NULL) { + /* out of memory? */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB3) { + printf("aloc_assoc: no assoc mem left, stcb=NULL\n"); + } +#endif + *error = ENOMEM; + return (NULL); + } + sctppcbinfo.ipi_count_asoc++; + sctppcbinfo.ipi_gencnt_asoc++; + + bzero(stcb, sizeof(*stcb)); + asoc = &stcb->asoc; + SCTP_TCB_LOCK_INIT(stcb); + /* setup back pointer's */ + stcb->sctp_ep = inp; + stcb->sctp_socket = inp->sctp_socket; + if ((err = sctp_init_asoc(inp, asoc, for_a_init, override_tag))) { + /* failed */ + SCTP_TCB_LOCK_DESTROY (stcb); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_asoc, stcb); + sctppcbinfo.ipi_count_asoc--; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB3) { + printf("aloc_assoc: couldn't init asoc, out of mem?!\n"); + } +#endif + *error = err; + return (NULL); + } + /* and the port */ + stcb->rport = rport; + SCTP_INP_INFO_WLOCK(); + SCTP_INP_WLOCK(inp); + if (inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE|SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { + /* inpcb freed while alloc going on */ + SCTP_TCB_LOCK_DESTROY (stcb); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_asoc, stcb); + SCTP_INP_WUNLOCK(inp); + SCTP_INP_INFO_WUNLOCK(); + sctppcbinfo.ipi_count_asoc--; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB3) { + printf("aloc_assoc: couldn't init asoc, out of mem?!\n"); + } +#endif + *error = EINVAL; + return (NULL); + } + SCTP_TCB_LOCK(stcb); + + /* now that my_vtag is set, add it to the hash */ + head = &sctppcbinfo.sctp_asochash[SCTP_PCBHASH_ASOC(stcb->asoc.my_vtag, + sctppcbinfo.hashasocmark)]; + /* put it in the bucket in the vtag hash of assoc's for the system */ + LIST_INSERT_HEAD(head, stcb, sctp_asocs); + SCTP_INP_INFO_WUNLOCK(); + + + if ((err = sctp_add_remote_addr(stcb, firstaddr, 1, 1))) { + /* failure.. memory error? */ + if (asoc->strmout) + FREE(asoc->strmout, M_PCB); + if (asoc->mapping_array) + FREE(asoc->mapping_array, M_PCB); + + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_asoc, stcb); + sctppcbinfo.ipi_count_asoc--; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB3) { + printf("aloc_assoc: couldn't add remote addr!\n"); + } +#endif + SCTP_TCB_LOCK_DESTROY (stcb); + *error = ENOBUFS; + return (NULL); + } + /* Init all the timers */ +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + callout_init(&asoc->hb_timer.timer, 0); + callout_init(&asoc->dack_timer.timer, 0); + callout_init(&asoc->asconf_timer.timer, 0); + callout_init(&asoc->shut_guard_timer.timer, 0); + callout_init(&asoc->autoclose_timer.timer, 0); +#else + callout_init(&asoc->hb_timer.timer); + callout_init(&asoc->dack_timer.timer); + callout_init(&asoc->asconf_timer.timer); + callout_init(&asoc->shut_guard_timer.timer); + callout_init(&asoc->autoclose_timer.timer); +#endif + LIST_INSERT_HEAD(&inp->sctp_asoc_list, stcb, sctp_tcblist); + /* now file the port under the hash as well */ + if (inp->sctp_tcbhash != NULL) { + head = &inp->sctp_tcbhash[SCTP_PCBHASH_ALLADDR(stcb->rport, + inp->sctp_hashmark)]; + LIST_INSERT_HEAD(head, stcb, sctp_tcbhash); + } + SCTP_INP_WUNLOCK(inp); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Association %p now allocated\n", stcb); + } +#endif + return (stcb); +} + +void +sctp_free_remote_addr(struct sctp_nets *net) +{ + if (net == NULL) + return; + net->ref_count--; + if (net->ref_count <= 0) { + /* stop timer if running */ + callout_stop(&net->rxt_timer.timer); + callout_stop(&net->pmtu_timer.timer); + net->dest_state = SCTP_ADDR_NOT_REACHABLE; + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_net, net); + sctppcbinfo.ipi_count_raddr--; + } +} + +/* + * remove a remote endpoint address from an association, it + * will fail if the address does not exist. + */ +int +sctp_del_remote_addr(struct sctp_tcb *stcb, struct sockaddr *remaddr) +{ + /* + * Here we need to remove a remote address. This is quite simple, we + * first find it in the list of address for the association + * (tasoc->asoc.nets) and then if it is there, we do a LIST_REMOVE on + * that item. + * Note we do not allow it to be removed if there are no other + * addresses. + */ + struct sctp_association *asoc; + struct sctp_nets *net, *net_tmp; + asoc = &stcb->asoc; + if (asoc->numnets < 2) { + /* Must have at LEAST two remote addresses */ + return (-1); + } + /* locate the address */ + for (net = TAILQ_FIRST(&asoc->nets); net != NULL; net = net_tmp) { + net_tmp = TAILQ_NEXT(net, sctp_next); + if (net->ro._l_addr.sa.sa_family != remaddr->sa_family) { + continue; + } + if (sctp_cmpaddr((struct sockaddr *)&net->ro._l_addr, + remaddr)) { + /* we found the guy */ + asoc->numnets--; + TAILQ_REMOVE(&asoc->nets, net, sctp_next); + sctp_free_remote_addr(net); + if (net == asoc->primary_destination) { + /* Reset primary */ + struct sctp_nets *lnet; + lnet = TAILQ_FIRST(&asoc->nets); + /* Try to find a confirmed primary */ + asoc->primary_destination = + sctp_find_alternate_net(stcb, lnet); + } + if (net == asoc->last_data_chunk_from) { + /* Reset primary */ + asoc->last_data_chunk_from = + TAILQ_FIRST(&asoc->nets); + } + if (net == asoc->last_control_chunk_from) { + /* Reset primary */ + asoc->last_control_chunk_from = + TAILQ_FIRST(&asoc->nets); + } + if (net == asoc->asconf_last_sent_to) { + /* Reset primary */ + asoc->asconf_last_sent_to = + TAILQ_FIRST(&asoc->nets); + } + return (0); + } + } + /* not found. */ + return (-2); +} + + +static void +sctp_add_vtag_to_timewait(struct sctp_inpcb *inp, u_int32_t tag) +{ + struct sctpvtaghead *chain; + struct sctp_tagblock *twait_block; + struct timeval now; + int set, i; + SCTP_GETTIME_TIMEVAL(&now); + chain = &sctppcbinfo.vtag_timewait[(tag % SCTP_STACK_VTAG_HASH_SIZE)]; + set = 0; + if (!LIST_EMPTY(chain)) { + /* Block(s) present, lets find space, and expire on the fly */ + LIST_FOREACH(twait_block, chain, sctp_nxt_tagblock) { + for (i = 0; i < SCTP_NUMBER_IN_VTAG_BLOCK; i++) { + if ((twait_block->vtag_block[i].v_tag == 0) && + !set) { + twait_block->vtag_block[0].tv_sec_at_expire = + now.tv_sec + SCTP_TIME_WAIT; + twait_block->vtag_block[0].v_tag = tag; + set = 1; + } else if ((twait_block->vtag_block[i].v_tag) && + ((long)twait_block->vtag_block[i].tv_sec_at_expire > + now.tv_sec)) { + /* Audit expires this guy */ + twait_block->vtag_block[i].tv_sec_at_expire = 0; + twait_block->vtag_block[i].v_tag = 0; + if (set == 0) { + /* Reuse it for my new tag */ + twait_block->vtag_block[0].tv_sec_at_expire = now.tv_sec + SCTP_TIME_WAIT; + twait_block->vtag_block[0].v_tag = tag; + set = 1; + } + } + } + if (set) { + /* + * We only do up to the block where we can + * place our tag for audits + */ + break; + } + } + } + /* Need to add a new block to chain */ + if (!set) { + MALLOC(twait_block, struct sctp_tagblock *, + sizeof(struct sctp_tagblock), M_PCB, M_NOWAIT); + if (twait_block == NULL) { + return; + } + memset(twait_block, 0, sizeof(struct sctp_timewait)); + LIST_INSERT_HEAD(chain, twait_block, sctp_nxt_tagblock); + twait_block->vtag_block[0].tv_sec_at_expire = now.tv_sec + + SCTP_TIME_WAIT; + twait_block->vtag_block[0].v_tag = tag; + } +} + + +static void +sctp_iterator_asoc_being_freed(struct sctp_inpcb *inp, struct sctp_tcb *stcb) +{ + struct sctp_iterator *it; + + + + /* Unlock the tcb lock we do this so + * we avoid a dead lock scenario where + * the iterator is waiting on the TCB lock + * and the TCB lock is waiting on the iterator + * lock. + */ + SCTP_ITERATOR_LOCK(); + SCTP_INP_INFO_WLOCK(); + SCTP_INP_WLOCK(inp); + SCTP_TCB_LOCK(stcb); + + it = stcb->asoc.stcb_starting_point_for_iterator; + if (it == NULL) { + return; + } + if (it->inp != stcb->sctp_ep) { + /* hm, focused on the wrong one? */ + return; + } + if (it->stcb != stcb) { + return; + } + it->stcb = LIST_NEXT(stcb, sctp_tcblist); + if (it->stcb == NULL) { + /* done with all asoc's in this assoc */ + if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { + it->inp = NULL; + } else { + + it->inp = LIST_NEXT(inp, sctp_list); + } + } +} + +/* + * Free the association after un-hashing the remote port. + */ +void +sctp_free_assoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb) +{ + struct sctp_association *asoc; + struct sctp_nets *net, *prev; + struct sctp_laddr *laddr; + struct sctp_tmit_chunk *chk; + struct sctp_asconf_addr *aparam; + struct sctp_socket_q_list *sq; + int s; + + /* first, lets purge the entry from the hash table. */ +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + if (stcb->asoc.state == 0) { + printf("Freeing already free association:%p - huh??\n", + stcb); + splx(s); + return; + } + asoc = &stcb->asoc; + asoc->state = 0; + /* now clean up any other timers */ + callout_stop(&asoc->hb_timer.timer); + callout_stop(&asoc->dack_timer.timer); + callout_stop(&asoc->asconf_timer.timer); + callout_stop(&asoc->shut_guard_timer.timer); + callout_stop(&asoc->autoclose_timer.timer); + callout_stop(&asoc->delayed_event_timer.timer); + TAILQ_FOREACH(net, &asoc->nets, sctp_next) { + callout_stop(&net->rxt_timer.timer); + callout_stop(&net->pmtu_timer.timer); + } + + /* Iterator asoc being freed we send an + * unlocked TCB. It returns with INP_INFO + * and INP write locked and the TCB locked + * too and of course the iterator lock + * in place as well.. + */ + SCTP_TCB_UNLOCK(stcb); + sctp_iterator_asoc_being_freed(inp, stcb); + + /* Null all of my entry's on the socket q */ + TAILQ_FOREACH(sq, &inp->sctp_queue_list, next_sq) { + if (sq->tcb == stcb) { + sq->tcb = NULL; + } + } + + if (inp->sctp_tcb_at_block == (void *)stcb) { + inp->error_on_block = ECONNRESET; + } + + if (inp->sctp_tcbhash) { + LIST_REMOVE(stcb, sctp_tcbhash); + } + /* Now lets remove it from the list of ALL associations in the EP */ + LIST_REMOVE(stcb, sctp_tcblist); + SCTP_INP_WUNLOCK(inp); + SCTP_ITERATOR_UNLOCK(); + + + /* pull from vtag hash */ + LIST_REMOVE(stcb, sctp_asocs); + + /* + * Now before we can free the assoc, we must remove all of the + * networks and any other allocated space.. i.e. add removes here + * before the SCTP_ZONE_FREE() of the tasoc entry. + */ + + sctp_add_vtag_to_timewait(inp, asoc->my_vtag); + SCTP_INP_INFO_WUNLOCK(); + prev = NULL; + while (!TAILQ_EMPTY(&asoc->nets)) { + net = TAILQ_FIRST(&asoc->nets); + /* pull from list */ + if ((sctppcbinfo.ipi_count_raddr == 0) || (prev == net)) { + break; + } + prev = net; + TAILQ_REMOVE(&asoc->nets, net, sctp_next); + /* free it */ + net->ref_count = 0; + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_net, net); + sctppcbinfo.ipi_count_raddr--; + } + /* + * The chunk lists and such SHOULD be empty but we check them + * just in case. + */ + /* anything on the wheel needs to be removed */ + while (!TAILQ_EMPTY(&asoc->out_wheel)) { + struct sctp_stream_out *outs; + outs = TAILQ_FIRST(&asoc->out_wheel); + TAILQ_REMOVE(&asoc->out_wheel, outs, next_spoke); + /* now clean up any chunks here */ + chk = TAILQ_FIRST(&outs->outqueue); + while (chk) { + TAILQ_REMOVE(&outs->outqueue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + chk->whoTo = NULL; + chk->asoc = NULL; + /* Free the chunk */ + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + sctppcbinfo.ipi_gencnt_chunk++; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + chk = TAILQ_FIRST(&outs->outqueue); + } + outs = TAILQ_FIRST(&asoc->out_wheel); + } + + if (asoc->pending_reply) { + FREE(asoc->pending_reply, M_PCB); + asoc->pending_reply = NULL; + } + chk = TAILQ_FIRST(&asoc->pending_reply_queue); + while (chk) { + TAILQ_REMOVE(&asoc->pending_reply_queue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + chk->whoTo = NULL; + chk->asoc = NULL; + /* Free the chunk */ + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + sctppcbinfo.ipi_gencnt_chunk++; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + chk = TAILQ_FIRST(&asoc->pending_reply_queue); + } + /* pending send queue SHOULD be empty */ + if (!TAILQ_EMPTY(&asoc->send_queue)) { + chk = TAILQ_FIRST(&asoc->send_queue); + while (chk) { + TAILQ_REMOVE(&asoc->send_queue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&asoc->send_queue); + } + } + /* sent queue SHOULD be empty */ + if (!TAILQ_EMPTY(&asoc->sent_queue)) { + chk = TAILQ_FIRST(&asoc->sent_queue); + while (chk) { + TAILQ_REMOVE(&asoc->sent_queue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&asoc->sent_queue); + } + } + /* control queue MAY not be empty */ + if (!TAILQ_EMPTY(&asoc->control_send_queue)) { + chk = TAILQ_FIRST(&asoc->control_send_queue); + while (chk) { + TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&asoc->control_send_queue); + } + } + if (!TAILQ_EMPTY(&asoc->reasmqueue)) { + chk = TAILQ_FIRST(&asoc->reasmqueue); + while (chk) { + TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&asoc->reasmqueue); + } + } + if (!TAILQ_EMPTY(&asoc->delivery_queue)) { + chk = TAILQ_FIRST(&asoc->delivery_queue); + while (chk) { + TAILQ_REMOVE(&asoc->delivery_queue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&asoc->delivery_queue); + } + } + if (asoc->mapping_array) { + FREE(asoc->mapping_array, M_PCB); + asoc->mapping_array = NULL; + } + + /* the stream outs */ + if (asoc->strmout) { + FREE(asoc->strmout, M_PCB); + asoc->strmout = NULL; + } + asoc->streamoutcnt = 0; + if (asoc->strmin) { + int i; + for (i = 0; i < asoc->streamincnt; i++) { + if (!TAILQ_EMPTY(&asoc->strmin[i].inqueue)) { + /* We have somethings on the streamin queue */ + chk = TAILQ_FIRST(&asoc->strmin[i].inqueue); + while (chk) { + TAILQ_REMOVE(&asoc->strmin[i].inqueue, + chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, + chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&asoc->strmin[i].inqueue); + } + } + } + FREE(asoc->strmin, M_PCB); + asoc->strmin = NULL; + } + asoc->streamincnt = 0; + /* local addresses, if any */ + while (!LIST_EMPTY(&asoc->sctp_local_addr_list)) { + laddr = LIST_FIRST(&asoc->sctp_local_addr_list); + LIST_REMOVE(laddr, sctp_nxt_addr); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_laddr, laddr); + sctppcbinfo.ipi_count_laddr--; + } + /* pending asconf (address) parameters */ + while (!TAILQ_EMPTY(&asoc->asconf_queue)) { + aparam = TAILQ_FIRST(&asoc->asconf_queue); + TAILQ_REMOVE(&asoc->asconf_queue, aparam, next); + FREE(aparam, M_PCB); + } + if (asoc->last_asconf_ack_sent != NULL) { + sctp_m_freem(asoc->last_asconf_ack_sent); + asoc->last_asconf_ack_sent = NULL; + } + /* Insert new items here :> */ + + /* Get rid of LOCK */ + SCTP_TCB_LOCK_DESTROY(stcb); + + /* now clean up the tasoc itself */ + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_asoc, stcb); + sctppcbinfo.ipi_count_asoc--; + if ((inp->sctp_socket->so_snd.sb_cc) || + (inp->sctp_socket->so_snd.sb_mbcnt)) { + /* This will happen when a abort is done */ + inp->sctp_socket->so_snd.sb_cc = 0; + inp->sctp_socket->so_snd.sb_mbcnt = 0; + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { + if ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + /* + * For the base fd, that is NOT in TCP pool we + * turn off the connected flag. This allows + * non-listening endpoints to connect/shutdown/ + * connect. + */ + inp->sctp_flags &= ~SCTP_PCB_FLAGS_CONNECTED; + soisdisconnected(inp->sctp_socket); + } + /* + * For those that are in the TCP pool we just leave + * so it cannot be used. When they close the fd we + * will free it all. + */ + } + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + sctp_inpcb_free(inp, 0); + } + splx(s); +} + + +/* + * determine if a destination is "reachable" based upon the addresses + * bound to the current endpoint (e.g. only v4 or v6 currently bound) + */ +/* + * FIX: if we allow assoc-level bindx(), then this needs to be fixed + * to use assoc level v4/v6 flags, as the assoc *may* not have the + * same address types bound as its endpoint + */ +int +sctp_destination_is_reachable(struct sctp_tcb *stcb, struct sockaddr *destaddr) +{ + struct sctp_inpcb *inp; + int answer; + + /* No locks here, the TCB, in all cases is already + * locked and an assoc is up. There is either a + * INP lock by the caller applied (in asconf case when + * deleting an address) or NOT in the HB case, however + * if HB then the INP increment is up and the INP + * will not be removed (on top of the fact that + * we have a TCB lock). So we only want to + * read the sctp_flags, which is either bound-all + * or not.. no protection needed since once an + * assoc is up you can't be changing your binding. + */ + inp = stcb->sctp_ep; + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + /* if bound all, destination is not restricted */ + /* RRS: Question during lock work: Is this + * correct? If you are bound-all you still + * might need to obey the V4--V6 flags??? + * IMO this bound-all stuff needs to be removed! + */ + return (1); + } + /* NOTE: all "scope" checks are done when local addresses are added */ + if (destaddr->sa_family == AF_INET6) { +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + answer = inp->inp_vflag & INP_IPV6; +#else + answer = inp->ip_inp.inp.inp_vflag & INP_IPV6; +#endif + } else if (destaddr->sa_family == AF_INET) { +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + answer = inp->inp_vflag & INP_IPV4; +#else + answer = inp->ip_inp.inp.inp_vflag & INP_IPV4; +#endif + } else { + /* invalid family, so it's unreachable */ + answer = 0; + } + return (answer); +} + +/* + * update the inp_vflags on an endpoint + */ +static void +sctp_update_ep_vflag(struct sctp_inpcb *inp) { + struct sctp_laddr *laddr; + + /* first clear the flag */ +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + inp->inp_vflag = 0; +#else + inp->ip_inp.inp.inp_vflag = 0; +#endif + /* set the flag based on addresses on the ep list */ + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("An ounce of prevention is worth a pound of cure\n"); + } +#endif /* SCTP_DEBUG */ + continue; + } + if (laddr->ifa->ifa_addr) { + continue; + } + if (laddr->ifa->ifa_addr->sa_family == AF_INET6) { +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + inp->inp_vflag |= INP_IPV6; +#else + inp->ip_inp.inp.inp_vflag |= INP_IPV6; +#endif + } else if (laddr->ifa->ifa_addr->sa_family == AF_INET) { +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + inp->inp_vflag |= INP_IPV4; +#else + inp->ip_inp.inp.inp_vflag |= INP_IPV4; +#endif + } + } +} + +/* + * Add the address to the endpoint local address list + * There is nothing to be done if we are bound to all addresses + */ +int +sctp_add_local_addr_ep(struct sctp_inpcb *inp, struct ifaddr *ifa) +{ + struct sctp_laddr *laddr; + int fnd, error; + fnd = 0; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + /* You are already bound to all. You have it already */ + return (0); + } + if (ifa->ifa_addr->sa_family == AF_INET6) { + struct in6_ifaddr *ifa6; + ifa6 = (struct in6_ifaddr *)ifa; + if (ifa6->ia6_flags & (IN6_IFF_DETACHED | + IN6_IFF_DEPRECATED | IN6_IFF_ANYCAST | IN6_IFF_NOTREADY)) + /* Can't bind a non-existent addr. */ + return (-1); + } + /* first, is it already present? */ + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == ifa) { + fnd = 1; + break; + } + } + + if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) && (fnd == 0)) { + /* Not bound to all */ + error = sctp_insert_laddr(&inp->sctp_addr_list, ifa); + if (error != 0) + return (error); + inp->laddr_count++; + /* update inp_vflag flags */ + if (ifa->ifa_addr->sa_family == AF_INET6) { +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + inp->inp_vflag |= INP_IPV6; +#else + inp->ip_inp.inp.inp_vflag |= INP_IPV6; +#endif + } else if (ifa->ifa_addr->sa_family == AF_INET) { +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + inp->inp_vflag |= INP_IPV4; +#else + inp->ip_inp.inp.inp_vflag |= INP_IPV4; +#endif + } + } + return (0); +} + + +/* + * select a new (hopefully reachable) destination net + * (should only be used when we deleted an ep addr that is the + * only usable source address to reach the destination net) + */ +static void +sctp_select_primary_destination(struct sctp_tcb *stcb) +{ + struct sctp_nets *net; + + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + /* for now, we'll just pick the first reachable one we find */ + if (net->dest_state & SCTP_ADDR_UNCONFIRMED) + continue; + if (sctp_destination_is_reachable(stcb, + (struct sockaddr *)&net->ro._l_addr)) { + /* found a reachable destination */ + stcb->asoc.primary_destination = net; + } + } + /* I can't there from here! ...we're gonna die shortly... */ +} + + +/* + * Delete the address from the endpoint local address list + * There is nothing to be done if we are bound to all addresses + */ +int +sctp_del_local_addr_ep(struct sctp_inpcb *inp, struct ifaddr *ifa) +{ + struct sctp_laddr *laddr; + int fnd; + fnd = 0; + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + /* You are already bound to all. You have it already */ + return (EINVAL); + } + + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa == ifa) { + fnd = 1; + break; + } + } + if (fnd && (inp->laddr_count < 2)) { + /* can't delete unless there are at LEAST 2 addresses */ + return (-1); + } + if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) && (fnd)) { + /* + * clean up any use of this address + * go through our associations and clear any + * last_used_address that match this one + * for each assoc, see if a new primary_destination is needed + */ + struct sctp_tcb *stcb; + + /* clean up "next_addr_touse" */ + if (inp->next_addr_touse == laddr) + /* delete this address */ + inp->next_addr_touse = NULL; + + /* clean up "last_used_address" */ + LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { + if (stcb->asoc.last_used_address == laddr) + /* delete this address */ + stcb->asoc.last_used_address = NULL; + } /* for each tcb */ + + /* remove it from the ep list */ + sctp_remove_laddr(laddr); + inp->laddr_count--; + /* update inp_vflag flags */ + sctp_update_ep_vflag(inp); + /* select a new primary destination if needed */ + LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { + /* presume caller (sctp_asconf.c) already owns INP lock */ + SCTP_TCB_LOCK(stcb); + if (sctp_destination_is_reachable(stcb, + (struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr) == 0) { + sctp_select_primary_destination(stcb); + } + SCTP_TCB_UNLOCK(stcb); + } /* for each tcb */ + } + return (0); +} + +/* + * Add the addr to the TCB local address list + * For the BOUNDALL or dynamic case, this is a "pending" address list + * (eg. addresses waiting for an ASCONF-ACK response) + * For the subset binding, static case, this is a "valid" address list + */ +int +sctp_add_local_addr_assoc(struct sctp_tcb *stcb, struct ifaddr *ifa) +{ + struct sctp_inpcb *inp; + struct sctp_laddr *laddr; + int error; + + /* Assumes TCP is locked.. and possiblye + * the INP. May need to confirm/fix that if + * we need it and is not the case. + */ + inp = stcb->sctp_ep; + if (ifa->ifa_addr->sa_family == AF_INET6) { + struct in6_ifaddr *ifa6; + ifa6 = (struct in6_ifaddr *)ifa; + if (ifa6->ia6_flags & (IN6_IFF_DETACHED | + /* IN6_IFF_DEPRECATED | */ + IN6_IFF_ANYCAST | + IN6_IFF_NOTREADY)) + /* Can't bind a non-existent addr. */ + return (-1); + } + /* does the address already exist? */ + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, sctp_nxt_addr) { + if (laddr->ifa == ifa) { + return (-1); + } + } + + /* add to the list */ + error = sctp_insert_laddr(&stcb->asoc.sctp_local_addr_list, ifa); + if (error != 0) + return (error); + return (0); +} + +/* + * insert an laddr entry with the given ifa for the desired list + */ +int +sctp_insert_laddr(struct sctpladdr *list, struct ifaddr *ifa) { + struct sctp_laddr *laddr; + int s; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + + laddr = (struct sctp_laddr *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_laddr); + if (laddr == NULL) { + /* out of memory? */ + splx(s); + return (EINVAL); + } + sctppcbinfo.ipi_count_laddr++; + sctppcbinfo.ipi_gencnt_laddr++; + bzero(laddr, sizeof(*laddr)); + laddr->ifa = ifa; + /* insert it */ + LIST_INSERT_HEAD(list, laddr, sctp_nxt_addr); + + splx(s); + return (0); +} + +/* + * Remove an laddr entry from the local address list (on an assoc) + */ +void +sctp_remove_laddr(struct sctp_laddr *laddr) +{ + int s; +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + /* remove from the list */ + LIST_REMOVE(laddr, sctp_nxt_addr); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_laddr, laddr); + sctppcbinfo.ipi_count_laddr--; + sctppcbinfo.ipi_gencnt_laddr++; + + splx(s); +} + +/* + * Remove an address from the TCB local address list + */ +int +sctp_del_local_addr_assoc(struct sctp_tcb *stcb, struct ifaddr *ifa) +{ + struct sctp_inpcb *inp; + struct sctp_laddr *laddr; + + /* This is called by asconf work. It is assumed that + * a) The TCB is locked + * and + * b) The INP is locked. + * This is true in as much as I can trace through + * the entry asconf code where I did these locks. + * Again, the ASCONF code is a bit different in + * that it does lock the INP during its work often + * times. This must be since we don't want other + * proc's looking up things while what they are + * looking up is changing :-D + */ + + inp = stcb->sctp_ep; + /* if subset bound and don't allow ASCONF's, can't delete last */ + if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) && + ((inp->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) == 0)) { + if (stcb->asoc.numnets < 2) { + /* can't delete last address */ + return (-1); + } + } + + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, sctp_nxt_addr) { + /* remove the address if it exists */ + if (laddr->ifa == NULL) + continue; + if (laddr->ifa == ifa) { + sctp_remove_laddr(laddr); + return (0); + } + } + + /* address not found! */ + return (-1); +} + +/* + * Remove an address from the TCB local address list + * lookup using a sockaddr addr + */ +int +sctp_del_local_addr_assoc_sa(struct sctp_tcb *stcb, struct sockaddr *sa) +{ + struct sctp_inpcb *inp; + struct sctp_laddr *laddr; + struct sockaddr *l_sa; + + /* + * This function I find does not seem to have a caller. + * As such we NEED TO DELETE this code. If we do + * find a caller, the caller MUST have locked the TCB + * at the least and probably the INP as well. + */ + inp = stcb->sctp_ep; + /* if subset bound and don't allow ASCONF's, can't delete last */ + if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) && + ((inp->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) == 0)) { + if (stcb->asoc.numnets < 2) { + /* can't delete last address */ + return (-1); + } + } + + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, sctp_nxt_addr) { + /* make sure the address exists */ + if (laddr->ifa == NULL) + continue; + if (laddr->ifa->ifa_addr == NULL) + continue; + + l_sa = laddr->ifa->ifa_addr; + if (l_sa->sa_family == AF_INET6) { + /* IPv6 address */ + struct sockaddr_in6 *sin1, *sin2; + sin1 = (struct sockaddr_in6 *)l_sa; + sin2 = (struct sockaddr_in6 *)sa; + if (memcmp(&sin1->sin6_addr, &sin2->sin6_addr, + sizeof(struct in6_addr)) == 0) { + /* matched */ + sctp_remove_laddr(laddr); + return (0); + } + } else if (l_sa->sa_family == AF_INET) { + /* IPv4 address */ + struct sockaddr_in *sin1, *sin2; + sin1 = (struct sockaddr_in *)l_sa; + sin2 = (struct sockaddr_in *)sa; + if (sin1->sin_addr.s_addr == sin2->sin_addr.s_addr) { + /* matched */ + sctp_remove_laddr(laddr); + return (0); + } + } else { + /* invalid family */ + return (-1); + } + } /* end foreach */ + /* address not found! */ + return (-1); +} + +static char sctp_pcb_initialized = 0; + +#if defined(__FreeBSD__) || defined(__APPLE__) +/* sysctl */ +static int sctp_max_number_of_assoc = SCTP_MAX_NUM_OF_ASOC; +static int sctp_scale_up_for_address = SCTP_SCALE_FOR_ADDR; + +#endif /* FreeBSD || APPLE */ + +#ifndef SCTP_TCBHASHSIZE +#define SCTP_TCBHASHSIZE 1024 +#endif + +#ifndef SCTP_CHUNKQUEUE_SCALE +#define SCTP_CHUNKQUEUE_SCALE 10 +#endif + +void +sctp_pcb_init() +{ + /* + * SCTP initialization for the PCB structures + * should be called by the sctp_init() funciton. + */ + int i; + int hashtblsize = SCTP_TCBHASHSIZE; + +#if defined(__FreeBSD__) || defined(__APPLE__) + int sctp_chunkscale = SCTP_CHUNKQUEUE_SCALE; +#endif + + if (sctp_pcb_initialized != 0) { + /* error I was called twice */ + return; + } + sctp_pcb_initialized = 1; + + /* Init all peg counts */ + for (i = 0; i < SCTP_NUMBER_OF_PEGS; i++) { + sctp_pegs[i] = 0; + } + + /* init the empty list of (All) Endpoints */ + LIST_INIT(&sctppcbinfo.listhead); + + /* init the iterator head */ + LIST_INIT(&sctppcbinfo.iteratorhead); + + /* init the hash table of endpoints */ +#if defined(__FreeBSD__) +#if defined(__FreeBSD_cc_version) && __FreeBSD_cc_version >= 440000 + TUNABLE_INT_FETCH("net.inet.sctp.tcbhashsize", &hashtblsize); + TUNABLE_INT_FETCH("net.inet.sctp.pcbhashsize", &sctp_pcbtblsize); + TUNABLE_INT_FETCH("net.inet.sctp.chunkscale", &sctp_chunkscale); +#else + TUNABLE_INT_FETCH("net.inet.sctp.tcbhashsize", SCTP_TCBHASHSIZE, + hashtblsize); + TUNABLE_INT_FETCH("net.inet.sctp.pcbhashsize", SCTP_PCBHASHSIZE, + sctp_pcbtblsize); + TUNABLE_INT_FETCH("net.inet.sctp.chunkscale", SCTP_CHUNKQUEUE_SCALE, + sctp_chunkscale); +#endif +#endif + + sctppcbinfo.sctp_asochash = hashinit((hashtblsize * 31), +#ifdef __NetBSD__ + HASH_LIST, +#endif + M_PCB, +#if defined(__NetBSD__) || defined(__OpenBSD__) + M_WAITOK, +#endif + &sctppcbinfo.hashasocmark); + + sctppcbinfo.sctp_ephash = hashinit(hashtblsize, +#ifdef __NetBSD__ + HASH_LIST, +#endif + M_PCB, +#if defined(__NetBSD__) || defined(__OpenBSD__) + M_WAITOK, +#endif + &sctppcbinfo.hashmark); + + sctppcbinfo.sctp_tcpephash = hashinit(hashtblsize, +#ifdef __NetBSD__ + HASH_LIST, +#endif + M_PCB, +#if defined(__NetBSD__) || defined(__OpenBSD__) + M_WAITOK, +#endif + &sctppcbinfo.hashtcpmark); + + sctppcbinfo.hashtblsize = hashtblsize; + + /* init the zones */ + /* + * FIX ME: Should check for NULL returns, but if it does fail we + * are doomed to panic anyways... add later maybe. + */ + SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_ep, "sctp_ep", + sizeof(struct sctp_inpcb), maxsockets); + + SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_asoc, "sctp_asoc", + sizeof(struct sctp_tcb), sctp_max_number_of_assoc); + + SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_laddr, "sctp_laddr", + sizeof(struct sctp_laddr), + (sctp_max_number_of_assoc * sctp_scale_up_for_address)); + + SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_net, "sctp_raddr", + sizeof(struct sctp_nets), + (sctp_max_number_of_assoc * sctp_scale_up_for_address)); + + SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_chunk, "sctp_chunk", + sizeof(struct sctp_tmit_chunk), + (sctp_max_number_of_assoc * sctp_scale_up_for_address * + sctp_chunkscale)); + + SCTP_ZONE_INIT(sctppcbinfo.ipi_zone_sockq, "sctp_sockq", + sizeof(struct sctp_socket_q_list), + (sctp_max_number_of_assoc * sctp_scale_up_for_address * + sctp_chunkscale)); + + /* Master Lock INIT for info structure */ + SCTP_INP_INFO_LOCK_INIT(); + SCTP_ITERATOR_LOCK_INIT(); + /* not sure if we need all the counts */ + sctppcbinfo.ipi_count_ep = 0; + sctppcbinfo.ipi_gencnt_ep = 0; + /* assoc/tcb zone info */ + sctppcbinfo.ipi_count_asoc = 0; + sctppcbinfo.ipi_gencnt_asoc = 0; + /* local addrlist zone info */ + sctppcbinfo.ipi_count_laddr = 0; + sctppcbinfo.ipi_gencnt_laddr = 0; + /* remote addrlist zone info */ + sctppcbinfo.ipi_count_raddr = 0; + sctppcbinfo.ipi_gencnt_raddr = 0; + /* chunk info */ + sctppcbinfo.ipi_count_chunk = 0; + sctppcbinfo.ipi_gencnt_chunk = 0; + + /* socket queue zone info */ + sctppcbinfo.ipi_count_sockq = 0; + sctppcbinfo.ipi_gencnt_sockq = 0; + + /* mbuf tracker */ + sctppcbinfo.mbuf_track = 0; + /* port stuff */ +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__APPLE__) + sctppcbinfo.lastlow = ipport_firstauto; +#else + sctppcbinfo.lastlow = anonportmin; +#endif + /* Init the TIMEWAIT list */ + for (i = 0; i < SCTP_STACK_VTAG_HASH_SIZE; i++) { + LIST_INIT(&sctppcbinfo.vtag_timewait[i]); + } + +#if defined(_SCTP_NEEDS_CALLOUT_) && !defined(__APPLE__) + TAILQ_INIT(&sctppcbinfo.callqueue); +#endif + +} + +int +sctp_load_addresses_from_init(struct sctp_tcb *stcb, struct mbuf *m, + int iphlen, int offset, int limit, struct sctphdr *sh, + struct sockaddr *altsa) +{ + /* + * grub through the INIT pulling addresses and + * loading them to the nets structure in the asoc. + * The from address in the mbuf should also be loaded + * (if it is not already). This routine can be called + * with either INIT or INIT-ACK's as long as the + * m points to the IP packet and the offset points + * to the beginning of the parameters. + */ + struct sctp_inpcb *inp, *l_inp; + struct sctp_nets *net, *net_tmp; + struct ip *iph; + struct sctp_paramhdr *phdr, parm_buf; + struct sctp_tcb *stcb_tmp; + u_int16_t ptype, plen; + struct sockaddr *sa; + struct sockaddr_storage dest_store; + struct sockaddr *local_sa = (struct sockaddr *)&dest_store; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + + /* First get the destination address setup too. */ + memset(&sin, 0, sizeof(sin)); + memset(&sin6, 0, sizeof(sin6)); + + sin.sin_family = AF_INET; + sin.sin_len = sizeof(sin); + sin.sin_port = stcb->rport; + + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_port = stcb->rport; + if (altsa == NULL) { + iph = mtod(m, struct ip *); + if (iph->ip_v == IPVERSION) { + /* its IPv4 */ + struct sockaddr_in *sin_2; + sin_2 = (struct sockaddr_in *)(local_sa); + memset(sin_2, 0, sizeof(sin)); + sin_2->sin_family = AF_INET; + sin_2->sin_len = sizeof(sin); + sin_2->sin_port = sh->dest_port; + sin_2->sin_addr.s_addr = iph->ip_dst.s_addr ; + sin.sin_addr = iph->ip_src; + sa = (struct sockaddr *)&sin; + } else if (iph->ip_v == (IPV6_VERSION >> 4)) { + /* its IPv6 */ + struct ip6_hdr *ip6; + struct sockaddr_in6 *sin6_2; + + ip6 = mtod(m, struct ip6_hdr *); + sin6_2 = (struct sockaddr_in6 *)(local_sa); + memset(sin6_2, 0, sizeof(sin6)); + sin6_2->sin6_family = AF_INET6; + sin6_2->sin6_len = sizeof(struct sockaddr_in6); + sin6_2->sin6_port = sh->dest_port; + sin6.sin6_addr = ip6->ip6_src; + sa = (struct sockaddr *)&sin6; + } else { + sa = NULL; + } + } else { + /* + * For cookies we use the src address NOT from the packet + * but from the original INIT + */ + sa = altsa; + } + /* Turn off ECN until we get through all params */ + stcb->asoc.ecn_allowed = 0; + + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + /* mark all addresses that we have currently on the list */ + net->dest_state |= SCTP_ADDR_NOT_IN_ASSOC; + } + /* does the source address already exist? if so skip it */ + l_inp = inp = stcb->sctp_ep; + stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net_tmp, local_sa, stcb); + if ((stcb_tmp == NULL && inp == stcb->sctp_ep) || inp == NULL) { + /* we must add the source address */ + /* no scope set here since we have a tcb already. */ + if ((sa->sa_family == AF_INET) && + (stcb->asoc.ipv4_addr_legal)) { + if (sctp_add_remote_addr(stcb, sa, 0, 2)) { + return (-1); + } + } else if ((sa->sa_family == AF_INET6) && + (stcb->asoc.ipv6_addr_legal)) { + if (sctp_add_remote_addr(stcb, sa, 0, 3)) { + return (-1); + } + } + } else { + if (net_tmp != NULL && stcb_tmp == stcb) { + net_tmp->dest_state &= ~SCTP_ADDR_NOT_IN_ASSOC; + } else if (stcb_tmp != stcb) { + /* It belongs to another association? */ + return (-1); + } + } + /* since a unlock occured we must check the + * TCB's state and the pcb's gone flags. + */ + if (l_inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE|SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { + /* the user freed the ep */ + return (-1); + } + if (stcb->asoc.state == 0) { + /* the assoc was freed? */ + return (-1); + } + + /* now we must go through each of the params. */ + phdr = sctp_get_next_param(m, offset, &parm_buf, sizeof(parm_buf)); + while (phdr) { + ptype = ntohs(phdr->param_type); + plen = ntohs(phdr->param_length); + /*printf("ptype => %d, plen => %d\n", ptype, plen);*/ + if (offset + plen > limit) { + break; + } + if (plen == 0) { + break; + } + if ((ptype == SCTP_IPV4_ADDRESS) && + (stcb->asoc.ipv4_addr_legal)) { + struct sctp_ipv4addr_param *p4, p4_buf; + /* ok get the v4 address and check/add */ + phdr = sctp_get_next_param(m, offset, + (struct sctp_paramhdr *)&p4_buf, sizeof(p4_buf)); + if (plen != sizeof(struct sctp_ipv4addr_param) || + phdr == NULL) { + return (-1); + } + p4 = (struct sctp_ipv4addr_param *)phdr; + sin.sin_addr.s_addr = p4->addr; + sa = (struct sockaddr *)&sin; + inp = stcb->sctp_ep; + stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net, + local_sa, stcb); + + if ((stcb_tmp== NULL && inp == stcb->sctp_ep) || + inp == NULL) { + /* we must add the source address */ + /* no scope set since we have a tcb already */ + + /* we must validate the state again here */ + if (l_inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE|SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { + /* the user freed the ep */ + return (-1); + } + if (stcb->asoc.state == 0) { + /* the assoc was freed? */ + return (-1); + } + if (sctp_add_remote_addr(stcb, sa, 0, 4)) { + return (-1); + } + } else if (stcb_tmp == stcb) { + if (l_inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE|SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { + /* the user freed the ep */ + return (-1); + } + if (stcb->asoc.state == 0) { + /* the assoc was freed? */ + return (-1); + } + if (net != NULL) { + /* clear flag */ + net->dest_state &= + ~SCTP_ADDR_NOT_IN_ASSOC; + } + } else { + /* strange, address is in another assoc? + * straighten out locks. + */ + SCTP_TCB_UNLOCK(stcb_tmp); + SCTP_INP_RLOCK(inp); + if (l_inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE|SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { + /* the user freed the ep */ + SCTP_INP_RUNLOCK(l_inp); + return (-1); + } + if (stcb->asoc.state == 0) { + /* the assoc was freed? */ + SCTP_INP_RUNLOCK(l_inp); + return (-1); + } + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(stcb->sctp_ep); + return (-1); + } + } else if ((ptype == SCTP_IPV6_ADDRESS) && + (stcb->asoc.ipv6_addr_legal)) { + /* ok get the v6 address and check/add */ + struct sctp_ipv6addr_param *p6, p6_buf; + phdr = sctp_get_next_param(m, offset, + (struct sctp_paramhdr *)&p6_buf, sizeof(p6_buf)); + if (plen != sizeof(struct sctp_ipv6addr_param) || + phdr == NULL) { + return (-1); + } + p6 = (struct sctp_ipv6addr_param *)phdr; + memcpy((caddr_t)&sin6.sin6_addr, p6->addr, + sizeof(p6->addr)); + sa = (struct sockaddr *)&sin6; + inp = stcb->sctp_ep; + stcb_tmp= sctp_findassociation_ep_addr(&inp, sa, &net, + local_sa, stcb); + if (stcb_tmp == NULL && (inp == stcb->sctp_ep || + inp == NULL)) { + /* we must validate the state again here */ + if (l_inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE|SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { + /* the user freed the ep */ + return (-1); + } + if (stcb->asoc.state == 0) { + /* the assoc was freed? */ + return (-1); + } + /* we must add the address, no scope set */ + if (sctp_add_remote_addr(stcb, sa, 0, 5)) { + return (-1); + } + } else if (stcb_tmp == stcb) { + /* we must validate the state again here */ + if (l_inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE|SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { + /* the user freed the ep */ + return (-1); + } + if (stcb->asoc.state == 0) { + /* the assoc was freed? */ + return (-1); + } + if (net != NULL) { + /* clear flag */ + net->dest_state &= + ~SCTP_ADDR_NOT_IN_ASSOC; + } + } else { + /* strange, address is in another assoc? + * straighten out locks. + */ + SCTP_TCB_UNLOCK(stcb_tmp); + SCTP_INP_RLOCK(l_inp); + /* we must validate the state again here */ + if (l_inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE|SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { + /* the user freed the ep */ + SCTP_INP_RUNLOCK(l_inp); + return (-1); + } + if (stcb->asoc.state == 0) { + /* the assoc was freed? */ + SCTP_INP_RUNLOCK(l_inp); + return (-1); + } + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(l_inp); + return (-1); + } + } else if (ptype == SCTP_ECN_CAPABLE) { + stcb->asoc.ecn_allowed = 1; + } else if (ptype == SCTP_ULP_ADAPTION) { + if (stcb->asoc.state != SCTP_STATE_OPEN) { + struct sctp_adaption_layer_indication ai, *aip; + + phdr = sctp_get_next_param(m, offset, + (struct sctp_paramhdr *)&ai, sizeof(ai)); + aip = (struct sctp_adaption_layer_indication *)phdr; + sctp_ulp_notify(SCTP_NOTIFY_ADAPTION_INDICATION, + stcb, ntohl(aip->indication), NULL); + } + } else if (ptype == SCTP_SET_PRIM_ADDR) { + struct sctp_asconf_addr_param lstore, *fee; + struct sctp_asconf_addrv4_param *fii; + int lptype; + struct sockaddr *lsa = NULL; + + stcb->asoc.peer_supports_asconf = 1; + stcb->asoc.peer_supports_asconf_setprim = 1; + if (plen > sizeof(lstore)) { + return (-1); + } + phdr = sctp_get_next_param(m, offset, + (struct sctp_paramhdr *)&lstore, plen); + if (phdr == NULL) { + return (-1); + } + + fee = (struct sctp_asconf_addr_param *)phdr; + lptype = ntohs(fee->addrp.ph.param_type); + if (lptype == SCTP_IPV4_ADDRESS) { + if (plen != + sizeof(struct sctp_asconf_addrv4_param)) { + printf("Sizeof setprim in init/init ack not %d but %d - ignored\n", + (int)sizeof(struct sctp_asconf_addrv4_param), + plen); + } else { + fii = (struct sctp_asconf_addrv4_param *)fee; + sin.sin_addr.s_addr = fii->addrp.addr; + lsa = (struct sockaddr *)&sin; + } + } else if (lptype == SCTP_IPV6_ADDRESS) { + if (plen != + sizeof(struct sctp_asconf_addr_param)) { + printf("Sizeof setprim (v6) in init/init ack not %d but %d - ignored\n", + (int)sizeof(struct sctp_asconf_addr_param), + plen); + } else { + memcpy(sin6.sin6_addr.s6_addr, + fee->addrp.addr, + sizeof(fee->addrp.addr)); + lsa = (struct sockaddr *)&sin6; + } + } + if (lsa) { + sctp_set_primary_addr(stcb, sa, NULL); + } + + } else if (ptype == SCTP_PRSCTP_SUPPORTED) { + /* Peer supports pr-sctp */ + stcb->asoc.peer_supports_prsctp = 1; + } else if (ptype == SCTP_SUPPORTED_CHUNK_EXT) { + /* A supported extension chunk */ + struct sctp_supported_chunk_types_param *pr_supported; + uint8_t local_store[128]; + int num_ent, i; + + phdr = sctp_get_next_param(m, offset, + (struct sctp_paramhdr *)&local_store, plen); + if (phdr == NULL) { + return (-1); + } + stcb->asoc.peer_supports_asconf = 0; + stcb->asoc.peer_supports_asconf_setprim = 0; + stcb->asoc.peer_supports_prsctp = 0; + stcb->asoc.peer_supports_pktdrop = 0; + stcb->asoc.peer_supports_strreset = 0; + pr_supported = (struct sctp_supported_chunk_types_param *)phdr; + num_ent = plen - sizeof(struct sctp_paramhdr); + for (i=0; ichunk_types[i]) { + case SCTP_ASCONF: + stcb->asoc.peer_supports_asconf = 1; + stcb->asoc.peer_supports_asconf_setprim = 1; + break; + case SCTP_ASCONF_ACK: + stcb->asoc.peer_supports_asconf = 1; + stcb->asoc.peer_supports_asconf_setprim = 1; + break; + case SCTP_FORWARD_CUM_TSN: + stcb->asoc.peer_supports_prsctp = 1; + break; + case SCTP_PACKET_DROPPED: + stcb->asoc.peer_supports_pktdrop = 1; + break; + case SCTP_STREAM_RESET: + stcb->asoc.peer_supports_strreset = 1; + break; + default: + /* one I have not learned yet */ + break; + + } + } + } else if (ptype == SCTP_ECN_NONCE_SUPPORTED) { + /* Peer supports ECN-nonce */ + stcb->asoc.peer_supports_ecn_nonce = 1; + stcb->asoc.ecn_nonce_allowed = 1; + } else if ((ptype == SCTP_HEARTBEAT_INFO) || + (ptype == SCTP_STATE_COOKIE) || + (ptype == SCTP_UNRECOG_PARAM) || + (ptype == SCTP_COOKIE_PRESERVE) || + (ptype == SCTP_SUPPORTED_ADDRTYPE) || + (ptype == SCTP_ADD_IP_ADDRESS) || + (ptype == SCTP_DEL_IP_ADDRESS) || + (ptype == SCTP_ERROR_CAUSE_IND) || + (ptype == SCTP_SUCCESS_REPORT)) { + /* don't care */; + } else { + if ((ptype & 0x8000) == 0x0000) { + /* must stop processing the rest of + * the param's. Any report bits were + * handled with the call to sctp_arethere_unrecognized_parameters() + * when the INIT or INIT-ACK was first seen. + */ + break; + } + } + offset += SCTP_SIZE32(plen); + if (offset >= limit) { + break; + } + phdr = sctp_get_next_param(m, offset, &parm_buf, + sizeof(parm_buf)); + } + /* Now check to see if we need to purge any addresses */ + for (net = TAILQ_FIRST(&stcb->asoc.nets); net != NULL; net = net_tmp) { + net_tmp = TAILQ_NEXT(net, sctp_next); + if ((net->dest_state & SCTP_ADDR_NOT_IN_ASSOC) == + SCTP_ADDR_NOT_IN_ASSOC) { + /* This address has been removed from the asoc */ + /* remove and free it */ + stcb->asoc.numnets--; + TAILQ_REMOVE(&stcb->asoc.nets, net, sctp_next); + sctp_free_remote_addr(net); + if (net == stcb->asoc.primary_destination) { + stcb->asoc.primary_destination = NULL; + sctp_select_primary_destination(stcb); + } + } + } + return (0); +} + +int +sctp_set_primary_addr(struct sctp_tcb *stcb, struct sockaddr *sa, + struct sctp_nets *net) +{ + /* make sure the requested primary address exists in the assoc */ + if (net == NULL && sa) + net = sctp_findnet(stcb, sa); + + if (net == NULL) { + /* didn't find the requested primary address! */ + return (-1); + } else { + /* set the primary address */ + if (net->dest_state & SCTP_ADDR_UNCONFIRMED) { + /* Must be confirmed */ + return (-1); + } + stcb->asoc.primary_destination = net; + net->dest_state &= ~SCTP_ADDR_WAS_PRIMARY; + return (0); + } +} + + +int +sctp_is_vtag_good(struct sctp_inpcb *inp, u_int32_t tag, struct timeval *now) +{ + /* + * This function serves two purposes. It will see if a TAG can be + * re-used and return 1 for yes it is ok and 0 for don't use that + * tag. + * A secondary function it will do is purge out old tags that can + * be removed. + */ + struct sctpasochead *head; + struct sctpvtaghead *chain; + struct sctp_tagblock *twait_block; + struct sctp_tcb *stcb; + + int i; + SCTP_INP_INFO_WLOCK(); + chain = &sctppcbinfo.vtag_timewait[(tag % SCTP_STACK_VTAG_HASH_SIZE)]; + /* First is the vtag in use ? */ + + head = &sctppcbinfo.sctp_asochash[SCTP_PCBHASH_ASOC(tag, + sctppcbinfo.hashasocmark)]; + if (head == NULL) { + SCTP_INP_INFO_WUNLOCK(); + return (0); + } + LIST_FOREACH(stcb, head, sctp_asocs) { + if (stcb->asoc.my_vtag == tag) { + /* We should remove this if and + * return 0 always if we want vtags + * unique across all endpoints. For + * now within a endpoint is ok. + */ + if (inp == stcb->sctp_ep) { + /* bad tag, in use */ + SCTP_INP_INFO_WUNLOCK(); + return (0); + } + } + } + if (!LIST_EMPTY(chain)) { + /* + * Block(s) are present, lets see if we have this tag in + * the list + */ + LIST_FOREACH(twait_block, chain, sctp_nxt_tagblock) { + for (i = 0; i < SCTP_NUMBER_IN_VTAG_BLOCK; i++) { + if (twait_block->vtag_block[i].v_tag == 0) { + /* not used */ + continue; + } else if ((long)twait_block->vtag_block[i].tv_sec_at_expire > + now->tv_sec) { + /* Audit expires this guy */ + twait_block->vtag_block[i].tv_sec_at_expire = 0; + twait_block->vtag_block[i].v_tag = 0; + } else if (twait_block->vtag_block[i].v_tag == + tag) { + /* Bad tag, sorry :< */ + SCTP_INP_INFO_WUNLOCK(); + return (0); + } + } + } + } + /* Not found, ok to use the tag */ + SCTP_INP_INFO_WUNLOCK(); + return (1); +} + + +/* + * Delete the address from the endpoint local address list + * Lookup using a sockaddr address (ie. not an ifaddr) + */ +int +sctp_del_local_addr_ep_sa(struct sctp_inpcb *inp, struct sockaddr *sa) +{ + struct sctp_laddr *laddr; + struct sockaddr *l_sa; + int found = 0; + /* Here is another function I cannot find a + * caller for. As such we SHOULD delete it + * if we have no users. If we find a user that + * user MUST have the INP locked. + * + */ + + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + /* You are already bound to all. You have it already */ + return (EINVAL); + } + + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + /* make sure the address exists */ + if (laddr->ifa == NULL) + continue; + if (laddr->ifa->ifa_addr == NULL) + continue; + + l_sa = laddr->ifa->ifa_addr; + if (l_sa->sa_family == AF_INET6) { + /* IPv6 address */ + struct sockaddr_in6 *sin1, *sin2; + sin1 = (struct sockaddr_in6 *)l_sa; + sin2 = (struct sockaddr_in6 *)sa; + if (memcmp(&sin1->sin6_addr, &sin2->sin6_addr, + sizeof(struct in6_addr)) == 0) { + /* matched */ + found = 1; + break; + } + } else if (l_sa->sa_family == AF_INET) { + /* IPv4 address */ + struct sockaddr_in *sin1, *sin2; + sin1 = (struct sockaddr_in *)l_sa; + sin2 = (struct sockaddr_in *)sa; + if (sin1->sin_addr.s_addr == sin2->sin_addr.s_addr) { + /* matched */ + found = 1; + break; + } + } else { + /* invalid family */ + return (-1); + } + } + + if (found && inp->laddr_count < 2) { + /* can't delete unless there are at LEAST 2 addresses */ + return (-1); + } + + if (found && (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) { + /* + * remove it from the ep list, this should NOT be + * done until its really gone from the interface list and + * we won't be receiving more of these. Probably right + * away. If we do allow a removal of an address from + * an association (sub-set bind) than this should NOT + * be called until the all ASCONF come back from this + * association. + */ + sctp_remove_laddr(laddr); + return (0); + } else { + return (-1); + } +} + +static void +sctp_drain_mbufs(struct sctp_inpcb *inp, struct sctp_tcb *stcb) +{ + /* + * We must hunt this association for MBUF's past the cumack + * (i.e. out of order data that we can renege on). + */ + struct sctp_association *asoc; + struct sctp_tmit_chunk *chk, *nchk; + u_int32_t cumulative_tsn_p1, tsn; + int cnt, strmat, gap; + /* We look for anything larger than the cum-ack + 1 */ + + asoc = &stcb->asoc; + cumulative_tsn_p1 = asoc->cumulative_tsn + 1; + cnt = 0; + /* First look in the re-assembly queue */ + chk = TAILQ_FIRST(&asoc->reasmqueue); + while (chk) { + /* Get the next one */ + nchk = TAILQ_NEXT(chk, sctp_next); + if (compare_with_wrap(chk->rec.data.TSN_seq, + cumulative_tsn_p1, MAX_TSN)) { + /* Yep it is above cum-ack */ + cnt++; + tsn = chk->rec.data.TSN_seq; + if (tsn >= asoc->mapping_array_base_tsn) { + gap = tsn - asoc->mapping_array_base_tsn; + } else { + gap = (MAX_TSN - asoc->mapping_array_base_tsn) + + tsn + 1; + } + asoc->size_on_reasm_queue -= chk->send_size; + asoc->cnt_on_reasm_queue--; + SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap); + TAILQ_REMOVE(&asoc->reasmqueue, chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + } + chk = nchk; + } + /* Ok that was fun, now we will drain all the inbound streams? */ + for (strmat = 0; strmat < asoc->streamincnt; strmat++) { + chk = TAILQ_FIRST(&asoc->strmin[strmat].inqueue); + while (chk) { + nchk = TAILQ_NEXT(chk, sctp_next); + if (compare_with_wrap(chk->rec.data.TSN_seq, + cumulative_tsn_p1, MAX_TSN)) { + /* Yep it is above cum-ack */ + cnt++; + tsn = chk->rec.data.TSN_seq; + if (tsn >= asoc->mapping_array_base_tsn) { + gap = tsn - + asoc->mapping_array_base_tsn; + } else { + gap = (MAX_TSN - + asoc->mapping_array_base_tsn) + + tsn + 1; + } + asoc->size_on_all_streams -= chk->send_size; + asoc->cnt_on_all_streams--; + + SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, + gap); + TAILQ_REMOVE(&asoc->strmin[strmat].inqueue, + chk, sctp_next); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + } + chk = nchk; + } + } + /* + * Question, should we go through the delivery queue? + * The only reason things are on here is the app not reading OR a + * p-d-api up. An attacker COULD send enough in to initiate the + * PD-API and then send a bunch of stuff to other streams... these + * would wind up on the delivery queue.. and then we would not get + * to them. But in order to do this I then have to back-track and + * un-deliver sequence numbers in streams.. el-yucko. I think for + * now we will NOT look at the delivery queue and leave it to be + * something to consider later. An alternative would be to abort + * the P-D-API with a notification and then deliver the data.... + * Or another method might be to keep track of how many times the + * situation occurs and if we see a possible attack underway just + * abort the association. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + if (cnt) { + printf("Freed %d chunks from reneg harvest\n", cnt); + } + } +#endif /* SCTP_DEBUG */ + + /* + * Another issue, in un-setting the TSN's in the mapping array we + * DID NOT adjust the higest_tsn marker. This will cause one of + * two things to occur. It may cause us to do extra work in checking + * for our mapping array movement. More importantly it may cause us + * to SACK every datagram. This may not be a bad thing though since + * we will recover once we get our cum-ack above and all this stuff + * we dumped recovered. + */ +} + +void +sctp_drain() +{ + /* + * We must walk the PCB lists for ALL associations here. The system + * is LOW on MBUF's and needs help. This is where reneging will + * occur. We really hope this does NOT happen! + */ + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + + SCTP_INP_INFO_RLOCK(); + LIST_FOREACH(inp, &sctppcbinfo.listhead, sctp_list) { + /* For each endpoint */ + SCTP_INP_RLOCK(inp); + LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) { + /* For each association */ + SCTP_TCB_LOCK(stcb); + sctp_drain_mbufs(inp, stcb); + SCTP_TCB_UNLOCK(stcb); + } + SCTP_INP_RUNLOCK(inp); + } + SCTP_INP_INFO_RUNLOCK(); +} + +int +sctp_add_to_socket_q(struct sctp_inpcb *inp, struct sctp_tcb *stcb) +{ + struct sctp_socket_q_list *sq; + + /* write lock on INP assumed */ + if ((inp == NULL) || (stcb == NULL)) { + /* I am paranoid */ + return (0); + } + sq = (struct sctp_socket_q_list *)SCTP_ZONE_GET( + sctppcbinfo.ipi_zone_sockq); + if (sq == NULL) { + /* out of sq structs */ + return (0); + } + sctppcbinfo.ipi_count_sockq++; + sctppcbinfo.ipi_gencnt_sockq++; + if (stcb) + stcb->asoc.cnt_msg_on_sb++; + sq->tcb = stcb; + TAILQ_INSERT_TAIL(&inp->sctp_queue_list, sq, next_sq); + return (1); +} + + +struct sctp_tcb * +sctp_remove_from_socket_q(struct sctp_inpcb *inp) +{ + struct sctp_tcb *stcb = NULL; + struct sctp_socket_q_list *sq; + + /* W-Lock on INP assumed held */ + sq = TAILQ_FIRST(&inp->sctp_queue_list); + if (sq == NULL) + return (NULL); + + stcb = sq->tcb; + TAILQ_REMOVE(&inp->sctp_queue_list, sq, next_sq); + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_sockq, sq); + sctppcbinfo.ipi_count_sockq--; + sctppcbinfo.ipi_gencnt_sockq++; + if (stcb) { + stcb->asoc.cnt_msg_on_sb--; + } + return (stcb); +} + +int +sctp_initiate_iterator(asoc_func af, uint32_t pcb_state, uint32_t asoc_state, + void *argp, uint32_t argi, end_func ef, + struct sctp_inpcb *s_inp) +{ + struct sctp_iterator *it=NULL; + int s; + if (af == NULL) { + return (-1); + } + MALLOC(it, struct sctp_iterator *, sizeof(struct sctp_iterator), M_PCB, + M_WAITOK); + if (it == NULL) { + return (ENOMEM); + } + memset(it, 0, sizeof(*it)); + it->function_toapply = af; + it->function_atend = ef; + it->pointer = argp; + it->val = argi; + it->pcb_flags = pcb_state; + it->asoc_state = asoc_state; + if (s_inp) { + it->inp = s_inp; + it->iterator_flags = SCTP_ITERATOR_DO_SINGLE_INP; + } else { + SCTP_INP_INFO_RLOCK(); + it->inp = LIST_FIRST(&sctppcbinfo.listhead); + SCTP_INP_INFO_RUNLOCK(); + it->iterator_flags = SCTP_ITERATOR_DO_ALL_INP; + + } + /* Init the timer */ +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + callout_init(&it->tmr.timer, 0); +#else + callout_init(&it->tmr.timer); +#endif + /* add to the list of all iterators */ + SCTP_INP_INFO_WLOCK(); + LIST_INSERT_HEAD(&sctppcbinfo.iteratorhead, it, sctp_nxt_itr); + SCTP_INP_INFO_WUNLOCK(); +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + sctp_iterator_timer(it); + splx(s); + return (0); +} + + +/* + * Callout/Timer routines for OS that doesn't have them + */ +#ifdef _SCTP_NEEDS_CALLOUT_ +#ifndef __APPLE__ +extern int ticks; +#endif + +void +callout_init(struct callout *c) +{ + bzero(c, sizeof(*c)); +} + +void +callout_reset(struct callout *c, int to_ticks, void (*ftn)(void *), void *arg) +{ + int s; + + s = splhigh(); + if (c->c_flags & CALLOUT_PENDING) + callout_stop(c); + + /* + * We could spl down here and back up at the TAILQ_INSERT_TAIL, + * but there's no point since doing this setup doesn't take much + * time. + */ + if (to_ticks <= 0) + to_ticks = 1; + + c->c_arg = arg; + c->c_flags = (CALLOUT_ACTIVE | CALLOUT_PENDING); + c->c_func = ftn; +#ifdef __APPLE__ + c->c_time = to_ticks; /* just store the requested timeout */ + timeout(ftn, arg, to_ticks); +#else + c->c_time = ticks + to_ticks; + TAILQ_INSERT_TAIL(&sctppcbinfo.callqueue, c, tqe); +#endif + splx(s); +} + +int +callout_stop(struct callout *c) +{ + int s; + + s = splhigh(); + /* + * Don't attempt to delete a callout that's not on the queue. + */ + if (!(c->c_flags & CALLOUT_PENDING)) { + c->c_flags &= ~CALLOUT_ACTIVE; + splx(s); + return (0); + } + c->c_flags &= ~(CALLOUT_ACTIVE | CALLOUT_PENDING| CALLOUT_FIRED); +#ifdef __APPLE__ +/* thread_call_cancel(c->c_call); */ + untimeout(c->c_func, c->c_arg); +#else + TAILQ_REMOVE(&sctppcbinfo.callqueue, c, tqe); + c->c_func = NULL; +#endif + splx(s); + return (1); +} + +#if !defined(__APPLE__) +void +sctp_fasttim(void) +{ + struct callout *c, *n; + struct calloutlist locallist; + int inited = 0; + int s; + s = splhigh(); + /* run through and subtract and mark all callouts */ + c = TAILQ_FIRST(&sctppcbinfo.callqueue); + while (c) { + n = TAILQ_NEXT(c, tqe); + if (c->c_time <= ticks) { + c->c_flags |= CALLOUT_FIRED; + c->c_time = 0; + TAILQ_REMOVE(&sctppcbinfo.callqueue, c, tqe); + if (inited == 0) { + TAILQ_INIT(&locallist); + inited = 1; + } + /* move off of main list */ + TAILQ_INSERT_TAIL(&locallist, c, tqe); + } + c = n; + } + /* Now all the ones on the locallist must be called */ + if (inited) { + c = TAILQ_FIRST(&locallist); + while (c) { + /* remove it */ + TAILQ_REMOVE(&locallist, c, tqe); + /* now validate that it did not get canceled */ + if (c->c_flags & CALLOUT_FIRED) { + c->c_flags &= ~CALLOUT_PENDING; + splx(s); + (*c->c_func)(c->c_arg); + s = splhigh(); + } + c = TAILQ_FIRST(&locallist); + } + } + splx(s); +} +#endif +#endif /* _SCTP_NEEDS_CALLOUT_ */ diff --git a/sys/netinet/sctp_pcb.h b/sys/netinet/sctp_pcb.h new file mode 100644 index 0000000000..693a61407c --- /dev/null +++ b/sys/netinet/sctp_pcb.h @@ -0,0 +1,707 @@ +/* $KAME: sctp_pcb.h,v 1.19 2004/08/17 06:28:02 t-momose Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_pcb.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +#ifndef __sctp_pcb_h__ +#define __sctp_pcb_h__ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * We must have V6 so the size of the proto can be calculated. Otherwise + * we would not allocate enough for Net/Open BSD :-< + */ +#if defined(__FreeBSD__) && __FreeBSD_version > 500000 +#include +#endif +#include +#ifdef __FreeBSD__ +#include +#endif +#include +#include +#include +#include +#if defined(__OpenBSD__) +#include +#else +#include +#endif + +#if defined(__OpenBSD__) || defined(__FreeBSD__) +#ifndef in6pcb +#define in6pcb inpcb +#endif +#endif + +#include +#include + +LIST_HEAD(sctppcbhead, sctp_inpcb); +LIST_HEAD(sctpasochead, sctp_tcb); +TAILQ_HEAD(sctpsocketq, sctp_socket_q_list); +LIST_HEAD(sctpladdr, sctp_laddr); +LIST_HEAD(sctpvtaghead, sctp_tagblock); + +#include +#include + +/* + * PCB flags + */ +#define SCTP_PCB_FLAGS_UDPTYPE 0x00000001 +#define SCTP_PCB_FLAGS_TCPTYPE 0x00000002 +#define SCTP_PCB_FLAGS_BOUNDALL 0x00000004 +#define SCTP_PCB_FLAGS_ACCEPTING 0x00000008 +#define SCTP_PCB_FLAGS_UNBOUND 0x00000010 +#define SCTP_PCB_FLAGS_DO_ASCONF 0x00000020 +#define SCTP_PCB_FLAGS_AUTO_ASCONF 0x00000040 +/* socket options */ +#define SCTP_PCB_FLAGS_NODELAY 0x00000100 +#define SCTP_PCB_FLAGS_AUTOCLOSE 0x00000200 +#define SCTP_PCB_FLAGS_RECVDATAIOEVNT 0x00000400 +#define SCTP_PCB_FLAGS_RECVASSOCEVNT 0x00000800 +#define SCTP_PCB_FLAGS_RECVPADDREVNT 0x00001000 +#define SCTP_PCB_FLAGS_RECVPEERERR 0x00002000 +#define SCTP_PCB_FLAGS_RECVSENDFAILEVNT 0x00004000 +#define SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT 0x00008000 +#define SCTP_PCB_FLAGS_ADAPTIONEVNT 0x00010000 +#define SCTP_PCB_FLAGS_PDAPIEVNT 0x00020000 +#define SCTP_PCB_FLAGS_STREAM_RESETEVNT 0x00040000 +#define SCTP_PCB_FLAGS_NO_FRAGMENT 0x00080000 +/* TCP model support */ +#define SCTP_PCB_FLAGS_CONNECTED 0x00100000 +#define SCTP_PCB_FLAGS_IN_TCPPOOL 0x00200000 +#define SCTP_PCB_FLAGS_DONT_WAKE 0x00400000 +#define SCTP_PCB_FLAGS_WAKEOUTPUT 0x00800000 +#define SCTP_PCB_FLAGS_WAKEINPUT 0x01000000 +#define SCTP_PCB_FLAGS_BOUND_V6 0x02000000 +#define SCTP_PCB_FLAGS_NEEDS_MAPPED_V4 0x04000000 +#define SCTP_PCB_FLAGS_BLOCKING_IO 0x08000000 +#define SCTP_PCB_FLAGS_SOCKET_GONE 0x10000000 +#define SCTP_PCB_FLAGS_SOCKET_ALLGONE 0x20000000 + +/* flags to copy to new PCB */ +#define SCTP_PCB_COPY_FLAGS 0x0707ff64 + +#define SCTP_PCBHASH_ALLADDR(port, mask) (port & mask) +#define SCTP_PCBHASH_ASOC(tag, mask) (tag & mask) + +struct sctp_laddr { + LIST_ENTRY(sctp_laddr) sctp_nxt_addr; /* next in list */ + struct ifaddr *ifa; +}; + +struct sctp_timewait { + uint32_t tv_sec_at_expire; /* the seconds from boot to expire */ + uint32_t v_tag; /* the vtag that can not be reused */ +}; + +struct sctp_tagblock { + LIST_ENTRY(sctp_tagblock) sctp_nxt_tagblock; + struct sctp_timewait vtag_block[SCTP_NUMBER_IN_VTAG_BLOCK]; +}; + +struct sctp_epinfo { + struct sctpasochead *sctp_asochash; + u_long hashasocmark; + + struct sctppcbhead *sctp_ephash; + u_long hashmark; + + /* + * The TCP model represents a substantial overhead in that we get + * an additional hash table to keep explicit connections in. The + * listening TCP endpoint will exist in the usual ephash above and + * accept only INIT's. It will be incapable of sending off an INIT. + * When a dg arrives we must look in the normal ephash. If we find + * a TCP endpoint that will tell us to go to the specific endpoint + * hash and re-hash to find the right assoc/socket. If we find a + * UDP model socket we then must complete the lookup. If this fails, + * i.e. no association can be found then we must continue to see if + * a sctp_peeloff()'d socket is in the tcpephash (a spun off socket + * acts like a TCP model connected socket). + */ + struct sctppcbhead *sctp_tcpephash; + u_long hashtcpmark; + uint32_t hashtblsize; + + struct sctppcbhead listhead; + + struct sctpiterators iteratorhead; + + /* ep zone info */ +#if defined(__FreeBSD__) || defined(__APPLE__) +#if __FreeBSD_version >= 500000 + struct uma_zone *ipi_zone_ep; + struct uma_zone *ipi_zone_asoc; + struct uma_zone *ipi_zone_laddr; + struct uma_zone *ipi_zone_net; + struct uma_zone *ipi_zone_chunk; + struct uma_zone *ipi_zone_sockq; +#else + struct vm_zone *ipi_zone_ep; + struct vm_zone *ipi_zone_asoc; + struct vm_zone *ipi_zone_laddr; + struct vm_zone *ipi_zone_net; + struct vm_zone *ipi_zone_chunk; + struct vm_zone *ipi_zone_sockq; +#endif +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) + struct pool ipi_zone_ep; + struct pool ipi_zone_asoc; + struct pool ipi_zone_laddr; + struct pool ipi_zone_net; + struct pool ipi_zone_chunk; + struct pool ipi_zone_sockq; +#endif + +#if defined(__FreeBSD__) && __FreeBSD_version >= 503000 + struct mtx ipi_ep_mtx; + struct mtx it_mtx; +#endif + u_int ipi_count_ep; + u_quad_t ipi_gencnt_ep; + + /* assoc/tcb zone info */ + u_int ipi_count_asoc; + u_quad_t ipi_gencnt_asoc; + + /* local addrlist zone info */ + u_int ipi_count_laddr; + u_quad_t ipi_gencnt_laddr; + + /* remote addrlist zone info */ + u_int ipi_count_raddr; + u_quad_t ipi_gencnt_raddr; + + /* chunk structure list for output */ + u_int ipi_count_chunk; + u_quad_t ipi_gencnt_chunk; + + /* socket queue zone info */ + u_int ipi_count_sockq; + u_quad_t ipi_gencnt_sockq; + + struct sctpvtaghead vtag_timewait[SCTP_STACK_VTAG_HASH_SIZE]; + +#ifdef _SCTP_NEEDS_CALLOUT_ + struct calloutlist callqueue; +#endif /* _SCTP_NEEDS_CALLOUT_ */ + + uint32_t mbuf_track; + + /* for port allocations */ + uint16_t lastport; + uint16_t lastlow; + uint16_t lasthi; + +}; + +extern uint32_t sctp_pegs[SCTP_NUMBER_OF_PEGS]; +/* + * Here we have all the relevant information for each SCTP entity created. + * We will need to modify this as approprate. We also need to figure out + * how to access /dev/random. + */ +struct sctp_pcb { + unsigned int time_of_secret_change; /* number of seconds from timeval.tv_sec */ + uint32_t secret_key[SCTP_HOW_MANY_SECRETS][SCTP_NUMBER_OF_SECRETS]; + unsigned int size_of_a_cookie; + + unsigned int sctp_timeoutticks[SCTP_NUM_TMRS]; + unsigned int sctp_minrto; + unsigned int sctp_maxrto; + unsigned int initial_rto; + + int initial_init_rto_max; + + uint32_t sctp_sws_sender; + uint32_t sctp_sws_receiver; + + /* various thresholds */ + /* Max times I will init at a guy */ + uint16_t max_init_times; + + /* Max times I will send before we consider someone dead */ + uint16_t max_send_times; + + uint16_t def_net_failure; + + /* number of streams to pre-open on a association */ + uint16_t pre_open_stream_count; + uint16_t max_open_streams_intome; + + /* random number generator */ + uint32_t random_counter; + uint8_t random_numbers[SCTP_SIGNATURE_ALOC_SIZE]; + uint8_t random_store[SCTP_SIGNATURE_ALOC_SIZE]; + + /* + * This timer is kept running per endpoint. When it fires it + * will change the secret key. The default is once a hour + */ + struct sctp_timer signature_change; + int def_cookie_life; + /* defaults to 0 */ + int auto_close_time; + uint32_t initial_sequence_debug; + uint32_t adaption_layer_indicator; + char store_at; + uint8_t max_burst; + char current_secret_number; + char last_secret_number; +}; + +#ifndef SCTP_ALIGNMENT +#define SCTP_ALIGNMENT 32 +#endif + +#ifndef SCTP_ALIGNM1 +#define SCTP_ALIGNM1 (SCTP_ALIGNMENT-1) +#endif + +#define sctp_lport ip_inp.inp.inp_lport + +struct sctp_socket_q_list { + struct sctp_tcb *tcb; + TAILQ_ENTRY(sctp_socket_q_list) next_sq; +}; + +struct sctp_inpcb { + /* + * put an inpcb in front of it all, kind of a waste but we need + * to for compatability with all the other stuff. + */ + union { + struct inpcb inp; + char align[(sizeof(struct in6pcb) + SCTP_ALIGNM1) & + ~SCTP_ALIGNM1]; + } ip_inp; + LIST_ENTRY(sctp_inpcb) sctp_list; /* lists all endpoints */ + /* hash of all endpoints for model */ + LIST_ENTRY(sctp_inpcb) sctp_hash; + + /* count of local addresses bound, 0 if bound all */ + int laddr_count; + /* list of addrs in use by the EP */ + struct sctpladdr sctp_addr_list; + /* used for source address selection rotation */ + struct sctp_laddr *next_addr_touse; + struct ifnet *next_ifn_touse; + /* back pointer to our socket */ + struct socket *sctp_socket; + uint32_t sctp_flags; /* flag set */ + struct sctp_pcb sctp_ep; /* SCTP ep data */ + /* head of the hash of all associations */ + struct sctpasochead *sctp_tcbhash; + u_long sctp_hashmark; + /* head of the list of all associations */ + struct sctpasochead sctp_asoc_list; + /* queue of TCB's waiting to stuff data up the socket */ + struct sctpsocketq sctp_queue_list; + void *sctp_tcb_at_block; + struct sctp_iterator *inp_starting_point_for_iterator; + int error_on_block; + uint32_t sctp_frag_point; + uint32_t sctp_vtag_first; + struct mbuf *pkt, *pkt_last, *sb_last_mpkt; + struct mbuf *control; +#if !(defined(__FreeBSD__) || defined(__APPLE__)) +#ifndef INP_IPV6 +#define INP_IPV6 0x1 +#endif +#ifndef INP_IPV4 +#define INP_IPV4 0x2 +#endif + u_char inp_vflag; + u_char inp_ip_ttl; + u_char inp_ip_tos; + u_char inp_ip_resv; +#endif +#if defined(__FreeBSD__) && __FreeBSD_version >= 503000 + struct mtx inp_mtx; + struct mtx inp_create_mtx; + u_int32_t refcount; +#endif +}; + +struct sctp_tcb { + struct socket *sctp_socket; /* back pointer to socket */ + struct sctp_inpcb *sctp_ep; /* back pointer to ep */ + LIST_ENTRY(sctp_tcb) sctp_tcbhash; /* next link in hash table */ + LIST_ENTRY(sctp_tcb) sctp_tcblist; /* list of all of the TCB's */ + LIST_ENTRY(sctp_tcb) sctp_asocs; + struct sctp_association asoc; + uint16_t rport; /* remote port in network format */ + uint16_t resv; +#if defined(__FreeBSD__) && __FreeBSD_version >= 503000 + struct mtx tcb_mtx; +#endif +}; + +#if defined(__FreeBSD__) && __FreeBSD_version >= 503000 + +/* General locking concepts: + * The goal of our locking is to of course provide + * consistency and yet minimize overhead. We will + * attempt to use non-recursive locks which are supposed + * to be quite inexpensive. Now in order to do this the goal + * is that most functions are not aware of locking. Once we + * have a TCB we lock it and unlock when we are through. This + * means that the TCB lock is kind-of a "global" lock when + * working on an association. Caution must be used when + * asserting a TCB_LOCK since if we recurse we deadlock. + * + * Most other locks (INP and INFO) attempt to localize + * the locking i.e. we try to contain the lock and + * unlock within the function that needs to lock it. This + * sometimes mean we do extra locks and unlocks and loose + * a bit of efficency, but if the performance statements about + * non-recursive locks are true this should not be a problem. + * One issue that arises with this only lock when needed + * is that if an implicit association setup is done we + * have a problem. If at the time I lookup an association + * I have NULL in the tcb return, by the time I call to + * create the association some other processor could + * have created it. This is what the CREATE lock on + * the endpoint. Places where we will be implicitly + * creating the association OR just creating an association + * (the connect call) will assert the CREATE_INP lock. This + * will assure us that during all the lookup of INP and INFO + * if another creator is also locking/looking up we can + * gate the two to synchronize. So the CREATE_INP lock is + * also another one we must use extreme caution in locking + * to make sure we don't hit a re-entrancy issue. + * + * For non FreeBSD 5.x and above we provide a bunch + * of EMPTY lock macro's so we can blatantly put locks + * everywhere and they reduce to nothing on NetBSD/OpenBSD + * and FreeBSD 4.x + * + */ + + +/* When working with the global SCTP lists we lock and unlock + * the INP_INFO lock. So when we go to lookup an association + * we will want to do a SCTP_INP_INFO_RLOCK() and then when + * we want to add a new association to the sctppcbinfo list's + * we will do a SCTP_INP_INFO_WLOCK(). + */ + +/* + * FIX ME, all locks right now have a + * recursive check/panic to validate that I + * don't have any lock recursion going on. + */ + +#define SCTP_INP_INFO_LOCK_INIT() \ + mtx_init(&sctppcbinfo.ipi_ep_mtx, "sctp", "inp_info", MTX_DEF) + +#ifdef xyzzy +#define SCTP_INP_INFO_RLOCK() do { \ + if (mtx_owned(&sctppcbinfo.ipi_ep_mtx)) \ + panic("INP INFO Recursive Lock-R"); \ + mtx_lock(&sctppcbinfo.ipi_ep_mtx); \ +} while (0) + +#define SCTP_INP_INFO_WLOCK() do { \ + if (mtx_owned(&sctppcbinfo.ipi_ep_mtx)) \ + panic("INP INFO Recursive Lock-W"); \ + mtx_lock(&sctppcbinfo.ipi_ep_mtx); \ +} while (0) + +#else + +void SCTP_INP_INFO_RLOCK(void); +void SCTP_INP_INFO_WLOCK(void); + +#endif + +#define SCTP_INP_INFO_RUNLOCK() mtx_unlock(&sctppcbinfo.ipi_ep_mtx) +#define SCTP_INP_INFO_WUNLOCK() mtx_unlock(&sctppcbinfo.ipi_ep_mtx) + +/* The INP locks we will use for locking an SCTP endpoint, so for + * example if we want to change something at the endpoint level for + * example random_store or cookie secrets we lock the INP level. + */ +#define SCTP_INP_LOCK_INIT(_inp) \ + mtx_init(&(_inp)->inp_mtx, "sctp", "inp", MTX_DEF | MTX_DUPOK) + +#define SCTP_ASOC_CREATE_LOCK_INIT(_inp) \ + mtx_init(&(_inp)->inp_create_mtx, "sctp", "inp_create", \ + MTX_DEF | MTX_DUPOK) + +#define SCTP_INP_LOCK_DESTROY(_inp) mtx_destroy(&(_inp)->inp_mtx) +#define SCTP_ASOC_CREATE_LOCK_DESTROY(_inp) mtx_destroy(&(_inp)->inp_create_mtx) + +#ifdef xyzzy +#define SCTP_INP_RLOCK(_inp) do { \ + struct sctp_tcb *xx_stcb; \ + xx_stcb = LIST_FIRST(&_inp->sctp_asoc_list); \ + if (xx_stcb) \ + if (mtx_owned(&(xx_stcb)->tcb_mtx)) \ + panic("I own TCB lock?"); \ + if (mtx_owned(&(_inp)->inp_mtx)) \ + panic("INP Recursive Lock-R"); \ + mtx_lock(&(_inp)->inp_mtx); \ +} while (0) + +#define SCTP_INP_WLOCK(_inp) do { \ + struct sctp_tcb *xx_stcb; \ + xx_stcb = LIST_FIRST(&_inp->sctp_asoc_list); \ + if (xx_stcb) \ + if (mtx_owned(&(xx_stcb)->tcb_mtx)) \ + panic("I own TCB lock?"); \ + if (mtx_owned(&(_inp)->inp_mtx)) \ + panic("INP Recursive Lock-W"); \ + mtx_lock(&(_inp)->inp_mtx); \ +} while (0) + +#else +void SCTP_INP_RLOCK(struct sctp_inpcb *); +void SCTP_INP_WLOCK(struct sctp_inpcb *); + +#endif + + +#define SCTP_INP_INCR_REF(_inp) _inp->refcount++ + +#define SCTP_INP_DECR_REF(_inp) do { \ + if (_inp->refcount > 0) \ + _inp->refcount--; \ + else \ + panic("bad inp refcount"); \ +}while (0) + +#define SCTP_ASOC_CREATE_LOCK(_inp) do { \ + if (mtx_owned(&(_inp)->inp_create_mtx)) \ + panic("INP Recursive CREATE"); \ + mtx_lock(&(_inp)->inp_create_mtx); \ +} while (0) + +#define SCTP_INP_RUNLOCK(_inp) mtx_unlock(&(_inp)->inp_mtx) +#define SCTP_INP_WUNLOCK(_inp) mtx_unlock(&(_inp)->inp_mtx) +#define SCTP_ASOC_CREATE_UNLOCK(_inp) mtx_unlock(&(_inp)->inp_create_mtx) + +/* For the majority of things (once we have found the association) we + * will lock the actual association mutex. This will protect all + * the assoiciation level queues and streams and such. We will + * need to lock the socket layer when we stuff data up into + * the receiving sb_mb. I.e. we will need to do an extra + * SOCKBUF_LOCK(&so->so_rcv) even though the association is + * locked. + */ + +#define SCTP_TCB_LOCK_INIT(_tcb) \ + mtx_init(&(_tcb)->tcb_mtx, "sctp", "tcb", MTX_DEF | MTX_DUPOK) +#define SCTP_TCB_LOCK_DESTROY(_tcb) mtx_destroy(&(_tcb)->tcb_mtx) +#define SCTP_TCB_LOCK(_tcb) do { \ + if (!mtx_owned(&(_tcb->sctp_ep->inp_mtx))) \ + panic("TCB locking and no INP lock"); \ + if (mtx_owned(&(_tcb)->tcb_mtx)) \ + panic("TCB Lock-recursive"); \ + mtx_lock(&(_tcb)->tcb_mtx); \ +} while (0) +#define SCTP_TCB_UNLOCK(_tcb) mtx_unlock(&(_tcb)->tcb_mtx) + +#define SCTP_ITERATOR_LOCK_INIT() \ + mtx_init(&sctppcbinfo.it_mtx, "sctp", "iterator", MTX_DEF) +#define SCTP_ITERATOR_LOCK() do { \ + if (mtx_owned(&sctppcbinfo.it_mtx)) \ + panic("Iterator Lock"); \ + mtx_lock(&sctppcbinfo.it_mtx); \ +} while (0) + +#define SCTP_ITERATOR_UNLOCK() mtx_unlock(&sctppcbinfo.it_mtx) +#define SCTP_ITERATOR_LOCK_DESTROY() mtx_destroy(&sctppcbinfo.it_mtx) +#else + +/* Empty Lock declarations for all other + * platforms pre-process away to nothing. + */ + +/* Lock for INFO stuff */ +#define SCTP_INP_INFO_LOCK_INIT() +#define SCTP_INP_INFO_RLOCK() +#define SCTP_INP_INFO_RLOCK() +#define SCTP_INP_INFO_WLOCK() + +#define SCTP_INP_INFO_RUNLOCK() +#define SCTP_INP_INFO_WUNLOCK() +/* Lock for INP */ +#define SCTP_INP_LOCK_INIT(_inp) +#define SCTP_INP_LOCK_DESTROY(_inp) +#define SCTP_INP_RLOCK(_inp) +#define SCTP_INP_RUNLOCK(_inp) +#define SCTP_INP_WLOCK(_inp) +#define SCTP_INP_INCR_REF(_inp) +#define SCTP_INP_DECR_REF(_inp) +#define SCTP_INP_WUNLOCK(_inp) +#define SCTP_ASOC_CREATE_LOCK_INIT(_inp) +#define SCTP_ASOC_CREATE_LOCK_DESTROY(_inp) +#define SCTP_ASOC_CREATE_LOCK(_inp) +#define SCTP_ASOC_CREATE_UNLOCK(_inp) +/* Lock for TCB */ +#define SCTP_TCB_LOCK_INIT(_tcb) +#define SCTP_TCB_LOCK_DESTROY(_tcb) +#define SCTP_TCB_LOCK(_tcb) +#define SCTP_TCB_UNLOCK(_tcb) +/* socket locks that are not here in other than 5.3 > FreeBSD*/ +#define SOCK_LOCK(_so) +#define SOCK_UNLOCK(_so) +#define SOCKBUF_LOCK(_so_buf) +#define SOCKBUF_UNLOCK(_so_buf) +/* iterator locks */ +#define SCTP_ITERATOR_LOCK_INIT() +#define SCTP_ITERATOR_LOCK() +#define SCTP_ITERATOR_UNLOCK() +#define SCTP_ITERATOR_LOCK_DESTROY() +#endif + +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) + +extern struct sctp_epinfo sctppcbinfo; +extern int sctp_auto_asconf; + +int SCTP6_ARE_ADDR_EQUAL(struct in6_addr *a, struct in6_addr *b); + +void sctp_fill_pcbinfo(struct sctp_pcbinfo *); + +struct sctp_nets *sctp_findnet(struct sctp_tcb *, struct sockaddr *); + +struct sctp_inpcb *sctp_pcb_findep(struct sockaddr *, int, int); + +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +int sctp_inpcb_bind(struct socket *, struct sockaddr *, struct thread *); +#else +int sctp_inpcb_bind(struct socket *, struct sockaddr *, struct proc *); +#endif + +struct sctp_tcb *sctp_findassociation_addr(struct mbuf *, int, int, + struct sctphdr *, struct sctp_chunkhdr *, struct sctp_inpcb **, + struct sctp_nets **); + +struct sctp_tcb *sctp_findassociation_addr_sa(struct sockaddr *, + struct sockaddr *, struct sctp_inpcb **, struct sctp_nets **, int); + +void sctp_move_pcb_and_assoc(struct sctp_inpcb *, struct sctp_inpcb *, + struct sctp_tcb *); + +/* + * For this call ep_addr, the to is the destination endpoint address + * of the peer (relative to outbound). The from field is only used if + * the TCP model is enabled and helps distingush amongst the subset + * bound (non-boundall). The TCP model MAY change the actual ep field, + * this is why it is passed. + */ +struct sctp_tcb *sctp_findassociation_ep_addr(struct sctp_inpcb **, + struct sockaddr *, struct sctp_nets **, struct sockaddr *, struct sctp_tcb *); + +struct sctp_tcb *sctp_findassociation_ep_asocid(struct sctp_inpcb *, caddr_t); + +struct sctp_tcb *sctp_findassociation_ep_asconf(struct mbuf *, int, int, + struct sctphdr *, struct sctp_inpcb **, struct sctp_nets **); + +int sctp_inpcb_alloc(struct socket *); + + +int sctp_is_address_on_local_host(struct sockaddr *addr); + +void sctp_inpcb_free(struct sctp_inpcb *, int); + +struct sctp_tcb *sctp_aloc_assoc(struct sctp_inpcb *, struct sockaddr *, + int, int *, uint32_t); + +void sctp_free_assoc(struct sctp_inpcb *, struct sctp_tcb *); + +int sctp_add_local_addr_ep(struct sctp_inpcb *, struct ifaddr *); + +int sctp_insert_laddr(struct sctpladdr *, struct ifaddr *); + +void sctp_remove_laddr(struct sctp_laddr *); + +int sctp_del_local_addr_ep(struct sctp_inpcb *, struct ifaddr *); + +int sctp_del_local_addr_ep_sa(struct sctp_inpcb *, struct sockaddr *); + +int sctp_add_remote_addr(struct sctp_tcb *, struct sockaddr *, int, int); + +int sctp_del_remote_addr(struct sctp_tcb *, struct sockaddr *); + +void sctp_pcb_init(void); + +void sctp_free_remote_addr(struct sctp_nets *); + +int sctp_add_local_addr_assoc(struct sctp_tcb *, struct ifaddr *); + +int sctp_del_local_addr_assoc(struct sctp_tcb *, struct ifaddr *); + +int sctp_del_local_addr_assoc_sa(struct sctp_tcb *, struct sockaddr *); + +int sctp_load_addresses_from_init(struct sctp_tcb *, struct mbuf *, int, int, + int, struct sctphdr *, struct sockaddr *); + +int sctp_set_primary_addr(struct sctp_tcb *, struct sockaddr *, struct sctp_nets *); + +int sctp_is_vtag_good(struct sctp_inpcb *, uint32_t, struct timeval *); + +/*void sctp_drain(void);*/ + +int sctp_destination_is_reachable(struct sctp_tcb *, struct sockaddr *); + +int sctp_add_to_socket_q(struct sctp_inpcb *, struct sctp_tcb *); + +struct sctp_tcb *sctp_remove_from_socket_q(struct sctp_inpcb *); + + +/* Null in last arg inpcb indicate run on ALL ep's. Specific + * inp in last arg indicates run on ONLY assoc's of the + * specified endpoint. + */ +int +sctp_initiate_iterator(asoc_func af, uint32_t, uint32_t, void *, uint32_t, + end_func ef, struct sctp_inpcb *); + +#if defined(__APPLE__) +void sctp_callout_alloc(struct sctp_timer *); +void sctp_callout_free(struct callout *); +#endif + +#ifdef __NetBSD__ +extern void in6_sin6_2_sin (struct sockaddr_in *, + struct sockaddr_in6 *sin6); +#endif + +#endif /* _KERNEL */ +#endif /* !__sctp_pcb_h__ */ diff --git a/sys/netinet/sctp_peeloff.c b/sys/netinet/sctp_peeloff.c new file mode 100644 index 0000000000..1fe93c6951 --- /dev/null +++ b/sys/netinet/sctp_peeloff.c @@ -0,0 +1,223 @@ +/* $KAME: sctp_peeloff.c,v 1.12 2004/08/17 04:06:19 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_peeloff.c,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +/* + * Copyright (C) 2002, 2003 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#if !(defined(__OpenBSD__) || defined(__APPLE__)) +#include "opt_ipsec.h" +#endif +#if defined(__FreeBSD__) +#include "opt_inet6.h" +#include "opt_inet.h" +#endif +#if defined(__NetBSD__) +#include "opt_inet.h" +#endif + +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef INET6 +#include +#endif +#include +#include +#include +#ifdef INET6 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef IPSEC +#ifndef __OpenBSD__ +#include +#include +#else +#undef IPSEC +#endif +#endif /*IPSEC*/ + +#ifdef SCTP_DEBUG +extern u_int32_t sctp_debug_on; +#endif /* SCTP_DEBUG */ + + +int +sctp_can_peel_off(struct socket *head, caddr_t assoc_id) +{ + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + inp = (struct sctp_inpcb *)head->so_pcb; + if (inp == NULL) { + return (EFAULT); + } + stcb = sctp_findassociation_ep_asocid(inp, assoc_id); + if (stcb == NULL) { + return (ENOTCONN); + } + /* We are clear to peel this one off */ + return (0); +} + +int +sctp_do_peeloff(struct socket *head, struct socket *so, caddr_t assoc_id) +{ + struct sctp_inpcb *inp, *n_inp; + struct sctp_tcb *stcb; + + inp = (struct sctp_inpcb *)head->so_pcb; + if (inp == NULL) + return (EFAULT); + stcb = sctp_findassociation_ep_asocid(inp, assoc_id); + if (stcb == NULL) + return (ENOTCONN); + + n_inp = (struct sctp_inpcb *)so->so_pcb; + n_inp->sctp_flags = (SCTP_PCB_FLAGS_UDPTYPE | + SCTP_PCB_FLAGS_CONNECTED | + SCTP_PCB_FLAGS_IN_TCPPOOL | /* Turn on Blocking IO */ + (SCTP_PCB_COPY_FLAGS & inp->sctp_flags)); + n_inp->sctp_socket = so; + + /* + * Now we must move it from one hash table to another and get + * the stcb in the right place. + */ + sctp_move_pcb_and_assoc(inp, n_inp, stcb); + /* + * And now the final hack. We move data in the + * pending side i.e. head to the new socket + * buffer. Let the GRUBBING begin :-0 + */ + sctp_grub_through_socket_buffer(inp, head, so, stcb); + return (0); +} + +struct socket * +sctp_get_peeloff(struct socket *head, caddr_t assoc_id, int *error) +{ + struct socket *newso; + struct sctp_inpcb *inp, *n_inp; + struct sctp_tcb *stcb; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PEEL1) { + printf("SCTP peel-off called\n"); + } +#endif /* SCTP_DEBUG */ + + inp = (struct sctp_inpcb *)head->so_pcb; + if (inp == NULL) { + *error = EFAULT; + return (NULL); + } + stcb = sctp_findassociation_ep_asocid(inp, assoc_id); + if (stcb == NULL) { + *error = ENOTCONN; + return (NULL); + } + newso = sonewconn(head, SS_ISCONNECTED); + if (newso == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PEEL1) { + printf("sctp_peeloff:sonewconn failed err\n"); + } +#endif /* SCTP_DEBUG */ + *error = ENOMEM; + SCTP_TCB_UNLOCK(stcb); + return (NULL); + } + n_inp = (struct sctp_inpcb *)newso->so_pcb; + SCTP_INP_WLOCK(n_inp); + n_inp->sctp_flags = (SCTP_PCB_FLAGS_UDPTYPE | + SCTP_PCB_FLAGS_CONNECTED | + SCTP_PCB_FLAGS_IN_TCPPOOL | /* Turn on Blocking IO */ + (SCTP_PCB_COPY_FLAGS & inp->sctp_flags)); + n_inp->sctp_socket = newso; + /* Turn off any non-blocking symantic. */ + newso->so_state &= ~SS_NBIO; + newso->so_state |= SS_ISCONNECTED; + /* We remove it right away */ +#if defined(__FreeBSD__) || defined(__APPLE__) + SOCK_LOCK(head); + TAILQ_REMOVE(&head->so_comp, newso, so_list); + head->so_qlen--; + SOCK_UNLOCK(head); +#else + +#if defined(__NetBSD__) || defined(__OpenBSD__) + newso = TAILQ_FIRST(&head->so_q); +#else + newso = head->so_q; +#endif + if (soqremque(newso, 1) == 0) + panic("sctp_peeloff"); +#endif /* __FreeBSD__ */ + /* + * Now we must move it from one hash table to another and get + * the stcb in the right place. + */ + SCTP_INP_WUNLOCK(n_inp); + sctp_move_pcb_and_assoc(inp, n_inp, stcb); + /* + * And now the final hack. We move data in the + * pending side i.e. head to the new socket + * buffer. Let the GRUBBING begin :-0 + */ + sctp_grub_through_socket_buffer(inp, head, newso, stcb); + SCTP_TCB_UNLOCK(stcb); + return (newso); +} diff --git a/sys/netinet/sctp_peeloff.h b/sys/netinet/sctp_peeloff.h new file mode 100644 index 0000000000..aa860ba9c5 --- /dev/null +++ b/sys/netinet/sctp_peeloff.h @@ -0,0 +1,59 @@ +/* $KAME: sctp_peeloff.h,v 1.5 2004/08/17 04:06:19 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_peeloff.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +#ifndef __sctp_peeloff_h__ +#define __sctp_peeloff_h__ + +/* + * Copyright (C) 2002, 2004 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#if !defined(__OpenBSD__) +#include +#endif +#include + +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) + +int sctp_can_peel_off(struct socket *, caddr_t); + +int sctp_do_peeloff(struct socket *, struct socket *, caddr_t); + +struct socket *sctp_get_peeloff(struct socket *, caddr_t, int *); + +#ifdef __APPLE__ +struct sctp_peeloff_args { + int sd; + caddr_t name; +}; +#endif + +#endif /* _KERNEL */ + +#endif diff --git a/sys/netinet/sctp_sha1.c b/sys/netinet/sctp_sha1.c new file mode 100644 index 0000000000..2110714a3f --- /dev/null +++ b/sys/netinet/sctp_sha1.c @@ -0,0 +1,260 @@ +/* $KAME: sctp_sha1.c,v 1.8 2004/02/24 21:52:27 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_sha1.c,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include +#include +#include +void +SHA1_Init(struct sha1_context *ctx) +{ + /* Init the SHA-1 context structure */ + ctx->A = 0; + ctx->B = 0; + ctx->C = 0; + ctx->D = 0; + ctx->E = 0; + ctx->H0 = H0INIT; + ctx->H1 = H1INIT; + ctx->H2 = H2INIT; + ctx->H3 = H3INIT; + ctx->H4 = H4INIT; + ctx->TEMP = 0; + memset(ctx->words, 0, sizeof(ctx->words)); + ctx->how_many_in_block = 0; + ctx->running_total = 0; +} + +static void +sha1_process_a_block(struct sha1_context *ctx, unsigned int *block) +{ + int i; + /* init the W0-W15 to the block of words being hashed. */ + /* step a) */ + for (i = 0; i < 16; i++) { + ctx->words[i] = ntohl(block[i]); + } + /* now init the rest based on the SHA-1 formula, step b) */ + for (i = 16; i < 80; i++) { + ctx->words[i] = CSHIFT(1, ((ctx->words[(i-3)]) ^ + (ctx->words[(i-8)]) ^ + (ctx->words[(i-14)]) ^ + (ctx->words[(i-16)]))); + } + /* step c) */ + ctx->A = ctx->H0; + ctx->B = ctx->H1; + ctx->C = ctx->H2; + ctx->D = ctx->H3; + ctx->E = ctx->H4; + + /* step d) */ + for (i = 0; i < 80; i++) { + if (i < 20) { + ctx->TEMP = ((CSHIFT(5, ctx->A)) + + (F1(ctx->B, ctx->C, ctx->D)) + + (ctx->E) + + ctx->words[i] + + K1); + } else if (i < 40) { + ctx->TEMP = ((CSHIFT(5, ctx->A)) + + (F2(ctx->B, ctx->C, ctx->D)) + + (ctx->E) + + (ctx->words[i]) + + K2); + } else if (i < 60) { + ctx->TEMP = ((CSHIFT(5, ctx->A)) + + (F3(ctx->B, ctx->C, ctx->D)) + + (ctx->E) + + (ctx->words[i]) + + K3); + } else { + ctx->TEMP = ((CSHIFT(5, ctx->A)) + + (F4(ctx->B, ctx->C, ctx->D)) + + (ctx->E) + + (ctx->words[i]) + + K4); + } + ctx->E = ctx->D; + ctx->D = ctx->C; + ctx->C = CSHIFT(30, ctx->B); + ctx->B = ctx->A; + ctx->A = ctx->TEMP; + } + /* step e) */ + ctx->H0 = (ctx->H0) + (ctx->A); + ctx->H1 = (ctx->H1) + (ctx->B); + ctx->H2 = (ctx->H2) + (ctx->C); + ctx->H3 = (ctx->H3) + (ctx->D); + ctx->H4 = (ctx->H4) + (ctx->E); +} + + +void +SHA1_Process(struct sha1_context *ctx, unsigned char *ptr, int siz) +{ + int number_left, left_to_fill; + number_left = siz; + while (number_left > 0) { + left_to_fill = sizeof(ctx->sha_block) - ctx->how_many_in_block; + if (left_to_fill > number_left) { + /* can only partially fill up this one */ + memcpy(&ctx->sha_block[ctx->how_many_in_block], + ptr, number_left); + ctx->how_many_in_block += number_left; + ctx->running_total += number_left; + number_left = 0; + break; + } else { + /* block is now full, process it */ + memcpy(&ctx->sha_block[ctx->how_many_in_block], + ptr, left_to_fill); + sha1_process_a_block(ctx, + (unsigned int *)ctx->sha_block); + number_left -= left_to_fill; + ctx->running_total += left_to_fill; + ctx->how_many_in_block = 0; + ptr = (unsigned char *)((caddr_t)ptr + left_to_fill); + } + } +} + +void +SHA1_Final(struct sha1_context *ctx, unsigned char *digest) +{ + /* + * if any left in block fill with padding and process. Then + * transfer the digest to the pointer. At the last block some + * special rules need to apply. We must add a 1 bit following + * the message, then we pad with 0's. The total size is encoded + * as a 64 bit number at the end. Now if the last buffer has + * more than 55 octets in it we cannot fit the 64 bit number + + * 10000000 pad on the end and must add the 10000000 pad, pad + * the rest of the message with 0's and then create an all 0 + * message with just the 64 bit size at the end and run this + * block through by itself. Also the 64 bit int must be in + * network byte order. + */ + int left_to_fill; + unsigned int i, *ptr; + if (ctx->how_many_in_block > 55) { + /* + * special case, we need to process two blocks here. + * One for the current stuff plus possibly the pad. + * The other for the size. + */ + left_to_fill = sizeof(ctx->sha_block) - ctx->how_many_in_block; + if (left_to_fill == 0) { + /* Should not really happen but I am paranoid */ + sha1_process_a_block(ctx, + (unsigned int *)ctx->sha_block); + /* init last block, a bit different than the rest */ + ctx->sha_block[0] = 0x80; + for (i = 1; i < sizeof(ctx->sha_block); i++) { + ctx->sha_block[i] = 0x0; + } + } else if (left_to_fill == 1) { + ctx->sha_block[ctx->how_many_in_block] = 0x80; + sha1_process_a_block(ctx, + (unsigned int *)ctx->sha_block); + /* init last block */ + memset(ctx->sha_block, 0, sizeof(ctx->sha_block)); + } else { + ctx->sha_block[ctx->how_many_in_block] = 0x80; + for (i =( ctx->how_many_in_block + 1); + i < sizeof(ctx->sha_block); + i++) { + ctx->sha_block[i] = 0x0; + } + sha1_process_a_block(ctx, + (unsigned int *)ctx->sha_block); + /* init last block */ + memset(ctx->sha_block, 0, sizeof(ctx->sha_block)); + } + /* This is in bits so multiply by 8 */ + ctx->running_total *= 8; + ptr = (unsigned int *)&ctx->sha_block[60]; + *ptr = htonl(ctx->running_total); + sha1_process_a_block(ctx, (unsigned int *)ctx->sha_block); + } else { + /* + * easy case, we just pad this message to size - end with 0 + * add the magic 0x80 to the next word and then put the + * network byte order size in the last spot and process + * the block. + */ + ctx->sha_block[ctx->how_many_in_block] = 0x80; + for (i = (ctx->how_many_in_block + 1); + i < sizeof(ctx->sha_block); + i++) { + ctx->sha_block[i] = 0x0; + } + /* get last int spot */ + ctx->running_total *= 8; + ptr = (unsigned int *)&ctx->sha_block[60]; + *ptr = htonl(ctx->running_total); + sha1_process_a_block(ctx, (unsigned int *)ctx->sha_block); + } + /* transfer the digest back to the user */ + digest[3] = (ctx->H0 & 0xff); + digest[2] = ((ctx->H0 >> 8) & 0xff); + digest[1] = ((ctx->H0 >> 16) & 0xff); + digest[0] = ((ctx->H0 >> 24) & 0xff); + + digest[7] = (ctx->H1 & 0xff); + digest[6] = ((ctx->H1 >> 8) & 0xff); + digest[5] = ((ctx->H1 >> 16) & 0xff); + digest[4] = ((ctx->H1 >> 24) & 0xff); + + digest[11] = (ctx->H2 & 0xff); + digest[10] = ((ctx->H2 >> 8) & 0xff); + digest[9] = ((ctx->H2 >> 16) & 0xff); + digest[8] = ((ctx->H2 >> 24) & 0xff); + + digest[15] = (ctx->H3 & 0xff); + digest[14] = ((ctx->H3 >> 8) & 0xff); + digest[13] = ((ctx->H3 >> 16) & 0xff); + digest[12] = ((ctx->H3 >> 24) & 0xff); + + digest[19] = (ctx->H4 & 0xff); + digest[18] = ((ctx->H4 >> 8) & 0xff); + digest[17] = ((ctx->H4 >> 16) & 0xff); + digest[16] = ((ctx->H4 >> 24) & 0xff); +} + + + + + + + diff --git a/sys/netinet/sctp_sha1.h b/sys/netinet/sctp_sha1.h new file mode 100644 index 0000000000..2f5f81615c --- /dev/null +++ b/sys/netinet/sctp_sha1.h @@ -0,0 +1,86 @@ +/* $KAME: sctp_sha1.h,v 1.4 2002/10/09 18:01:22 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_sha1.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +#ifndef __SCTP_SLA1_h__ +#define __SCTP_SLA1_h__ +/* + * Copyright (c) 2001, 2002, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include + +struct sha1_context { + unsigned int A; + unsigned int B; + unsigned int C; + unsigned int D; + unsigned int E; + unsigned int H0; + unsigned int H1; + unsigned int H2; + unsigned int H3; + unsigned int H4; + unsigned int words[80]; + unsigned int TEMP; + /* block I am collecting to process */ + char sha_block[64]; + /* collected so far */ + int how_many_in_block; + unsigned int running_total; +}; + + +#define F1(B,C,D) (((B & C) | ((~B) & D))) /* 0 <= t <= 19 */ +#define F2(B,C,D) (B ^ C ^ D) /* 20 <= t <= 39 */ +#define F3(B,C,D) ((B & C) | (B & D) | (C & D)) /* 40 <= t <= 59 */ +#define F4(B,C,D) (B ^ C ^ D) /* 600 <= t <= 79 */ + +/* circular shift */ +#define CSHIFT(A,B) ((B << A) | (B >> (32-A))) + +#define K1 0x5a827999 /* 0 <= t <= 19 */ +#define K2 0x6ed9eba1 /* 20 <= t <= 39 */ +#define K3 0x8f1bbcdc /* 40 <= t <= 59 */ +#define K4 0xca62c1d6 /* 60 <= t <= 79 */ + +#define H0INIT 0x67452301 +#define H1INIT 0xefcdab89 +#define H2INIT 0x98badcfe +#define H3INIT 0x10325476 +#define H4INIT 0xc3d2e1f0 + +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) + +void SHA1_Init(struct sha1_context *); +void SHA1_Process(struct sha1_context *, unsigned char *, int); +void SHA1_Final(struct sha1_context *, unsigned char *); + +#endif /* _KERNEL */ +#endif diff --git a/sys/netinet/sctp_structs.h b/sys/netinet/sctp_structs.h new file mode 100644 index 0000000000..6cda5310fc --- /dev/null +++ b/sys/netinet/sctp_structs.h @@ -0,0 +1,655 @@ +/* $KAME: sctp_structs.h,v 1.12 2004/08/17 04:06:19 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_structs.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +#ifndef __sctp_structs_h__ +#define __sctp_structs_h__ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include + +#if defined(__APPLE__) +#include +#elif defined(__OpenBSD__) +#include +#else +#include +#endif + +#ifdef IPSEC +#ifndef __OpenBSD__ +#include +#include +#endif +#endif + +#include +#include + +struct sctp_timer { +#if defined(__OpenBSD__) + struct timeout timer; +#else + struct callout timer; +#endif + int type; + /* + * Depending on the timer type these will be setup and cast with + * the appropriate entity. + */ + void *ep; + void *tcb; + void *net; +}; + +/* + * This is the information we track on each interface that we know about * from the distant end. + */ +TAILQ_HEAD(sctpnetlisthead, sctp_nets); + +/* + * Users of the iterator need to malloc a iterator with a call to + * sctp_initiate_iterator(func, pcb_flags, asoc_state, void-ptr-arg, u_int32_t, + * u_int32-arg, end_func, inp); + * + * Use the following two defines if you don't care what pcb flags are on the + * EP and/or you don't care what state the association is in. + * + * Note that if you specify an INP as the last argument then ONLY each + * association of that single INP will be executed upon. Note that the + * pcb flags STILL apply so if the inp you specify has different pcb_flags + * then what you put in pcb_flags nothing will happen. use SCTP_PCB_ANY_FLAGS + * to assure the inp you specify gets treated. + */ +#define SCTP_PCB_ANY_FLAGS 0x00000000 +#define SCTP_ASOC_ANY_STATE 0x00000000 + +typedef void (*asoc_func)(struct sctp_inpcb *, struct sctp_tcb *, void *ptr, + u_int32_t val); +typedef void (*end_func)(void *ptr, u_int32_t val); + +#define SCTP_ITERATOR_DO_ALL_INP 0x00000001 +#define SCTP_ITERATOR_DO_SINGLE_INP 0x00000002 + +struct sctp_iterator { + LIST_ENTRY(sctp_iterator) sctp_nxt_itr; + struct sctp_timer tmr; + struct sctp_inpcb *inp; /* ep */ + struct sctp_tcb *stcb; /* assoc */ + asoc_func function_toapply; + end_func function_atend; + void *pointer; /* pointer for apply func to use */ + u_int32_t val; /* value for apply func to use */ + u_int32_t pcb_flags; + u_int32_t asoc_state; + u_int32_t iterator_flags; +}; + +LIST_HEAD(sctpiterators, sctp_iterator); + +struct sctp_copy_all { + struct sctp_inpcb *inp; /* ep */ + struct mbuf *m; + struct sctp_sndrcvinfo sndrcv; + int sndlen; + int cnt_sent; + int cnt_failed; +}; + +union sctp_sockstore { +#ifdef AF_INET + struct sockaddr_in sin; +#endif +#ifdef AF_INET6 + struct sockaddr_in6 sin6; +#endif + struct sockaddr sa; +}; + +struct sctp_nets { + TAILQ_ENTRY(sctp_nets) sctp_next; /* next link */ + + /* Things on the top half may be able to be split + * into a common structure shared by all. + */ + struct sctp_timer pmtu_timer; + + /* + * The following two in combination equate to a route entry for + * v6 or v4. + */ + struct sctp_route { + struct rtentry *ro_rt; + union sctp_sockstore _l_addr; /* remote peer addr */ + union sctp_sockstore _s_addr; /* our selected src addr */ + } ro; + /* mtu discovered so far */ + u_int32_t mtu; + u_int32_t ssthresh; /* not sure about this one for split */ + + /* smoothed average things for RTT and RTO itself */ + int lastsa; + int lastsv; + unsigned int RTO; + + /* This is used for SHUTDOWN/SHUTDOWN-ACK/SEND or INIT timers */ + struct sctp_timer rxt_timer; + + /* last time in seconds I sent to it */ + struct timeval last_sent_time; + int ref_count; + + /* Congestion stats per destination */ + /* + * flight size variables and such, sorry Vern, I could not avoid + * this if I wanted performance :> + */ + u_int32_t flight_size; + u_int32_t cwnd; /* actual cwnd */ + u_int32_t prev_cwnd; /* cwnd before any processing */ + u_int32_t partial_bytes_acked; /* in CA tracks when to incr a MTU */ + + /* tracking variables to avoid the aloc/free in sack processing */ + unsigned int net_ack; + unsigned int net_ack2; + /* + * These only are valid if the primary dest_sstate holds the + * SCTP_ADDR_SWITCH_PRIMARY flag + */ + u_int32_t next_tsn_at_change; + u_int32_t heartbeat_random1; + u_int32_t heartbeat_random2; + + /* if this guy is ok or not ... status */ + u_int16_t dest_state; + /* number of transmit failures to down this guy */ + u_int16_t failure_threshold; + /* error stats on destination */ + u_int16_t error_count; + + /* Flags that probably can be combined into dest_state */ + u_int8_t rto_pending; /* is segment marked for RTO update ** if we split?*/ + u_int8_t fast_retran_ip; /* fast retransmit in progress */ + u_int8_t hb_responded; + u_int8_t cacc_saw_newack; /* CACC algorithm flag */ + u_int8_t src_addr_selected; /* if we split we move */ + u_int8_t indx_of_eligible_next_to_use; + u_int8_t addr_is_local; /* its a local address (if known) could move in split */ +#ifdef SCTP_HIGH_SPEED + u_int8_t last_hs_used; /* index into the last HS table entry we used */ +#endif +}; + + +struct sctp_data_chunkrec { + u_int32_t TSN_seq; /* the TSN of this transmit */ + u_int16_t stream_seq; /* the stream sequence number of this transmit */ + u_int16_t stream_number; /* the stream number of this guy */ + u_int32_t payloadtype; + u_int32_t context; /* from send */ + + /* ECN Nonce: Nonce Value for this chunk */ + u_int8_t ect_nonce; + + /* part of the Highest sacked algorithm to be able to + * stroke counts on ones that are FR'd. + */ + u_int32_t fast_retran_tsn; /* sending_seq at the time of FR */ + struct timeval timetodrop; /* time we drop it from queue */ + u_int8_t doing_fast_retransmit; + u_int8_t rcv_flags; /* flags pulled from data chunk on inbound + * for outbound holds sending flags. + */ + u_int8_t state_flags; +}; + +TAILQ_HEAD(sctpchunk_listhead, sctp_tmit_chunk); + +#define CHUNK_FLAGS_FRAGMENT_OK 0x0001 + +struct sctp_tmit_chunk { + union { + struct sctp_data_chunkrec data; + int chunk_id; + } rec; + int32_t sent; /* the send status */ + int32_t snd_count; /* number of times I sent */ + u_int32_t flags; /* flags, such as FRAGMENT_OK */ + u_int32_t send_size; + u_int32_t book_size; + u_int32_t mbcnt; + struct sctp_association *asoc; /* bp to asoc this belongs to */ + struct timeval sent_rcv_time; /* filled in if RTT being calculated */ + struct mbuf *data; /* pointer to mbuf chain of data */ + struct sctp_nets *whoTo; + TAILQ_ENTRY(sctp_tmit_chunk) sctp_next; /* next link */ + uint8_t do_rtt; +}; + + +/* + * this struct contains info that is used to track inbound stream data + * and help with ordering. + */ +TAILQ_HEAD(sctpwheelunrel_listhead, sctp_stream_in); +struct sctp_stream_in { + struct sctpchunk_listhead inqueue; + TAILQ_ENTRY(sctp_stream_in) next_spoke; + uint16_t stream_no; + uint16_t last_sequence_delivered; /* used for re-order */ +}; + +/* This struct is used to track the traffic on outbound streams */ +TAILQ_HEAD(sctpwheel_listhead, sctp_stream_out); +struct sctp_stream_out { + struct sctpchunk_listhead outqueue; + TAILQ_ENTRY(sctp_stream_out) next_spoke; /* next link in wheel */ + uint16_t stream_no; + uint16_t next_sequence_sent; /* next one I expect to send out */ +}; + +/* used to keep track of the addresses yet to try to add/delete */ +TAILQ_HEAD(sctp_asconf_addrhead, sctp_asconf_addr); +struct sctp_asconf_addr { + TAILQ_ENTRY(sctp_asconf_addr) next; + struct sctp_asconf_addr_param ap; + struct ifaddr *ifa; /* save the ifa for add/del ip */ + uint8_t sent; /* has this been sent yet? */ +}; + + +/* + * Here we have information about each individual association that we + * track. We probably in production would be more dynamic. But for ease + * of implementation we will have a fixed array that we hunt for in a + * linear fashion. + */ +struct sctp_association { + /* association state */ + int state; + /* queue of pending addrs to add/delete */ + struct sctp_asconf_addrhead asconf_queue; + struct timeval time_entered; /* time we entered state */ + struct timeval time_last_rcvd; + struct timeval time_last_sent; + struct timeval time_last_sat_advance; + struct sctp_sndrcvinfo def_send; /* default send parameters */ + + /* timers and such */ + struct sctp_timer hb_timer; /* hb timer */ + struct sctp_timer dack_timer; /* Delayed ack timer */ + struct sctp_timer asconf_timer; /* Asconf */ + struct sctp_timer strreset_timer; /* stream reset */ + struct sctp_timer shut_guard_timer; /* guard */ + struct sctp_timer autoclose_timer; /* automatic close timer */ + struct sctp_timer delayed_event_timer; /* timer for delayed events */ + + /* list of local addresses when add/del in progress */ + struct sctpladdr sctp_local_addr_list; + struct sctpnetlisthead nets; + + /* Control chunk queue */ + struct sctpchunk_listhead control_send_queue; + + /* Once a TSN hits the wire it is moved to the sent_queue. We + * maintain two counts here (don't know if any but retran_cnt + * is needed). The idea is that the sent_queue_retran_cnt + * reflects how many chunks have been marked for retranmission + * by either T3-rxt or FR. + */ + struct sctpchunk_listhead sent_queue; + struct sctpchunk_listhead send_queue; + + + /* re-assembly queue for fragmented chunks on the inbound path */ + struct sctpchunk_listhead reasmqueue; + + /* + * this queue is used when we reach a condition that we can NOT + * put data into the socket buffer. We track the size of this + * queue and set our rwnd to the space in the socket minus also + * the size_on_delivery_queue. + */ + struct sctpchunk_listhead delivery_queue; + + struct sctpwheel_listhead out_wheel; + + /* If an iterator is looking at me, this is it */ + struct sctp_iterator *stcb_starting_point_for_iterator; + + /* ASCONF destination address last sent to */ + struct sctp_nets *asconf_last_sent_to; + + /* ASCONF save the last ASCONF-ACK so we can resend it if necessary */ + struct mbuf *last_asconf_ack_sent; + + /* + * if Source Address Selection happening, this will rotate through + * the link list. + */ + struct sctp_laddr *last_used_address; + + /* stream arrays */ + struct sctp_stream_in *strmin; + struct sctp_stream_out *strmout; + u_int8_t *mapping_array; + /* primary destination to use */ + struct sctp_nets *primary_destination; + + /* last place I got a data chunk from */ + struct sctp_nets *last_data_chunk_from; + /* last place I got a control from */ + struct sctp_nets *last_control_chunk_from; + + /* circular looking for output selection */ + struct sctp_stream_out *last_out_stream; + + /* wait to the point the cum-ack passes + * pending_reply->sr_resp.reset_at_tsn. + */ + struct sctp_stream_reset_response *pending_reply; + struct sctpchunk_listhead pending_reply_queue; + + u_int32_t cookie_preserve_req; + /* ASCONF next seq I am sending out, inits at init-tsn */ + uint32_t asconf_seq_out; + /* ASCONF last received ASCONF from peer, starts at peer's TSN-1 */ + uint32_t asconf_seq_in; + + /* next seq I am sending in str reset messages */ + uint32_t str_reset_seq_out; + + /* next seq I am expecting in str reset messages */ + uint32_t str_reset_seq_in; + u_int32_t str_reset_sending_seq; + + /* various verification tag information */ + u_int32_t my_vtag; /* + * The tag to be used. if assoc is + * re-initited by remote end, and + * I have unlocked this will be + * regenrated to a new random value. + */ + u_int32_t peer_vtag; /* The peers last tag */ + + u_int32_t my_vtag_nonce; + u_int32_t peer_vtag_nonce; + + + /* This is the SCTP fragmentation threshold */ + u_int32_t smallest_mtu; + + /* + * Special hook for Fast retransmit, allows us to track the highest + * TSN that is NEW in this SACK if gap ack blocks are present. + */ + u_int32_t this_sack_highest_gap; + + /* + * The highest consecutive TSN that has been acked by peer on my + * sends + */ + u_int32_t last_acked_seq; + + /* The next TSN that I will use in sending. */ + u_int32_t sending_seq; + + /* Original seq number I used ??questionable to keep?? */ + u_int32_t init_seq_number; + + /* + * We use this value to know if FR's are allowed, i.e. did the + * cum-ack pass this point or equal it so FR's are now allowed. + */ + u_int32_t t3timeout_highest_marked; + + /* The Advanced Peer Ack Point, as required by the PR-SCTP */ + /* (A1 in Section 4.2) */ + u_int32_t advanced_peer_ack_point; + + /* + * The highest consequetive TSN at the bottom of the mapping + * array (for his sends). + */ + u_int32_t cumulative_tsn; + /* + * Used to track the mapping array and its offset bits. This + * MAY be lower then cumulative_tsn. + */ + u_int32_t mapping_array_base_tsn; + /* + * used to track highest TSN we have received and is listed in + * the mapping array. + */ + u_int32_t highest_tsn_inside_map; + + u_int32_t last_echo_tsn; + u_int32_t last_cwr_tsn; + u_int32_t fast_recovery_tsn; + u_int32_t sat_t3_recovery_tsn; + + u_int32_t tsn_last_delivered; + + /* + * window state information and smallest MTU that I use to bound + * segmentation + */ + u_int32_t peers_rwnd; + u_int32_t my_rwnd; + u_int32_t my_last_reported_rwnd; + u_int32_t my_rwnd_control_len; + + u_int32_t total_output_queue_size; + u_int32_t total_output_mbuf_queue_size; + + /* 32 bit nonce stuff */ + u_int32_t nonce_resync_tsn; + u_int32_t nonce_wait_tsn; + + int ctrl_queue_cnt; /* could be removed REM */ + /* + * All outbound datagrams queue into this list from the + * individual stream queue. Here they get assigned a TSN + * and then await sending. The stream seq comes when it + * is first put in the individual str queue + */ + unsigned int stream_queue_cnt; + unsigned int send_queue_cnt; + unsigned int sent_queue_cnt; + unsigned int sent_queue_cnt_removeable; + /* + * Number on sent queue that are marked for retran until this + * value is 0 we only send one packet of retran'ed data. + */ + unsigned int sent_queue_retran_cnt; + + unsigned int size_on_reasm_queue; + unsigned int cnt_on_reasm_queue; + /* amount of data (bytes) currently in flight (on all destinations) */ + unsigned int total_flight; + /* Total book size in flight */ + unsigned int total_flight_count; /* count of chunks used with book total */ + /* count of destinaton nets and list of destination nets */ + unsigned int numnets; + + /* Total error count on this association */ + unsigned int overall_error_count; + + unsigned int size_on_delivery_queue; + unsigned int cnt_on_delivery_queue; + + unsigned int cnt_msg_on_sb; + + /* All stream count of chunks for delivery */ + unsigned int size_on_all_streams; + unsigned int cnt_on_all_streams; + + /* Heart Beat delay in ticks */ + unsigned int heart_beat_delay; + + /* autoclose */ + unsigned int sctp_autoclose_ticks; + + /* how many preopen streams we have */ + unsigned int pre_open_streams; + + /* How many streams I support coming into me */ + unsigned int max_inbound_streams; + + /* the cookie life I award for any cookie, in seconds */ + unsigned int cookie_life; + + unsigned int numduptsns; + int dup_tsns[SCTP_MAX_DUP_TSNS]; + unsigned int initial_init_rto_max; /* initial RTO for INIT's */ + unsigned int initial_rto; /* initial send RTO */ + unsigned int minrto; /* per assoc RTO-MIN */ + unsigned int maxrto; /* per assoc RTO-MAX */ + /* Being that we have no bag to collect stale cookies, and + * that we really would not want to anyway.. we will count + * them in this counter. We of course feed them to the + * pigeons right away (I have always thought of pigeons + * as flying rats). + */ + u_int16_t stale_cookie_count; + + /* For the partial delivery API, if up, invoked + * this is what last TSN I delivered + */ + u_int16_t str_of_pdapi; + u_int16_t ssn_of_pdapi; + + + /* counts of actual built streams. Allocation may be more however */ + /* could re-arrange to optimize space here. */ + u_int16_t streamincnt; + u_int16_t streamoutcnt; + + /* my maximum number of retrans of INIT and SEND */ + /* copied from SCTP but should be individually setable */ + u_int16_t max_init_times; + u_int16_t max_send_times; + + u_int16_t def_net_failure; + + /* + * lock flag: 0 is ok to send, 1+ (duals as a retran count) is + * awaiting ACK + */ + u_int16_t asconf_sent; /* possibly removable REM */ + u_int16_t mapping_array_size; + + u_int16_t chunks_on_out_queue; /* total chunks floating around */ + int16_t num_send_timers_up; + /* + * This flag indicates that we need to send the first SACK. If + * in place it says we have NOT yet sent a SACK and need to. + */ + u_int8_t first_ack_sent; + + /* max burst after fast retransmit completes */ + u_int8_t max_burst; + + u_int8_t sat_network; /* RTT is in range of sat net or greater */ + u_int8_t sat_network_lockout;/* lockout code */ + u_int8_t burst_limit_applied; /* Burst limit in effect at last send? */ + /* flag goes on when we are doing a partial delivery api */ + u_int8_t hb_random_values[4]; + u_int8_t fragmented_delivery_inprogress; + u_int8_t fragment_flags; + u_int8_t hb_ect_randombit; + u_int8_t hb_random_idx; + + /* ECN Nonce stuff */ + u_int8_t receiver_nonce_sum; /* nonce I sum and put in my sack */ + u_int8_t ecn_nonce_allowed; /* Tells us if ECN nonce is on */ + u_int8_t nonce_sum_check; /* On off switch used during re-sync */ + u_int8_t nonce_wait_for_ecne;/* flag when we expect a ECN */ + u_int8_t peer_supports_ecn_nonce; + + /* + * This value, plus all other ack'd but above cum-ack is added + * together to cross check against the bit that we have yet to + * define (probably in the SACK). + * When the cum-ack is updated, this sum is updated as well. + */ + u_int8_t nonce_sum_expect_base; + /* Flag to tell if ECN is allowed */ + u_int8_t ecn_allowed; + + /* flag to indicate if peer can do asconf */ + uint8_t peer_supports_asconf; + uint8_t peer_supports_asconf_setprim; /* possibly removable REM */ + /* pr-sctp support flag */ + uint8_t peer_supports_prsctp; + + /* stream resets are supported by the peer */ + uint8_t peer_supports_strreset; + + /* + * packet drop's are supported by the peer, we don't really care + * about this but we bookkeep it anyway. + */ + uint8_t peer_supports_pktdrop; + + /* Do we allow V6/V4? */ + u_int8_t ipv4_addr_legal; + u_int8_t ipv6_addr_legal; + /* Address scoping flags */ + /* scope value for IPv4 */ + u_int8_t ipv4_local_scope; + /* scope values for IPv6 */ + u_int8_t local_scope; + u_int8_t site_scope; + /* loopback scope */ + u_int8_t loopback_scope; + /* flags to handle send alternate net tracking */ + u_int8_t used_alt_onsack; + u_int8_t used_alt_asconfack; + u_int8_t fast_retran_loss_recovery; + u_int8_t sat_t3_loss_recovery; + u_int8_t dropped_special_cnt; + u_int8_t seen_a_sack_this_pkt; + u_int8_t stream_reset_outstanding; + u_int8_t delayed_connection; + u_int8_t ifp_had_enobuf; + u_int8_t saw_sack_with_frags; + /* + * The mapping array is used to track out of order sequences above + * last_acked_seq. 0 indicates packet missing 1 indicates packet + * rec'd. We slide it up every time we raise last_acked_seq and 0 + * trailing locactions out. If I get a TSN above the array + * mappingArraySz, I discard the datagram and let retransmit happen. + */ +}; + +#endif diff --git a/sys/netinet/sctp_timer.c b/sys/netinet/sctp_timer.c new file mode 100644 index 0000000000..e3a5360879 --- /dev/null +++ b/sys/netinet/sctp_timer.c @@ -0,0 +1,1561 @@ +/* $KAME: sctp_timer.c,v 1.28 2004/08/17 04:06:20 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_timer.c,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +/* + * Copyright (C) 2002, 2003, 2004 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#if !(defined(__OpenBSD__) || defined(__APPLE__)) +#include "opt_ipsec.h" +#endif +#if defined(__FreeBSD__) +#include "opt_compat.h" +#include "opt_inet6.h" +#include "opt_inet.h" +#endif +#if defined(__NetBSD__) +#include "opt_inet.h" +#endif +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif + +#include +#include +#include +#include +#ifndef __OpenBSD__ +#include +#endif +#include +#include +#include +#include +#include +#include +#ifdef INET6 +#include +#endif + +#if (defined(__FreeBSD__) && __FreeBSD_version >= 500000) +#include +#else +#include +#endif + +#include +#include +#include +#include +#include +#define _IP_VHL +#include +#include +#include +#include + +#ifdef INET6 +#include +#include +#endif /* INET6 */ + +#include + +#ifdef IPSEC +#ifndef __OpenBSD__ +#include +#include +#else +#undef IPSEC +#endif +#endif /* IPSEC */ +#ifdef INET6 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#ifdef SCTP_DEBUG +extern u_int32_t sctp_debug_on; +#endif /* SCTP_DEBUG */ + +void +sctp_audit_retranmission_queue(struct sctp_association *asoc) +{ + struct sctp_tmit_chunk *chk; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER4) { + printf("Audit invoked on send queue cnt:%d onqueue:%d\n", + asoc->sent_queue_retran_cnt, + asoc->sent_queue_cnt); + } +#endif /* SCTP_DEBUG */ + asoc->sent_queue_retran_cnt = 0; + asoc->sent_queue_cnt = 0; + TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { + if (chk->sent == SCTP_DATAGRAM_RESEND) { + asoc->sent_queue_retran_cnt++; + } + asoc->sent_queue_cnt++; + } + TAILQ_FOREACH(chk, &asoc->control_send_queue, sctp_next) { + if (chk->sent == SCTP_DATAGRAM_RESEND) { + asoc->sent_queue_retran_cnt++; + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER4) { + printf("Audit completes retran:%d onqueue:%d\n", + asoc->sent_queue_retran_cnt, + asoc->sent_queue_cnt); + } +#endif /* SCTP_DEBUG */ +} + +int +sctp_threshold_management(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct sctp_nets *net, uint16_t threshold) +{ + if (net) { + net->error_count++; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER4) { + printf("Error count for %p now %d thresh:%d\n", + net, net->error_count, + net->failure_threshold); + } +#endif /* SCTP_DEBUG */ + if (net->error_count >= net->failure_threshold) { + /* We had a threshold failure */ + if (net->dest_state & SCTP_ADDR_REACHABLE) { + net->dest_state &= ~SCTP_ADDR_REACHABLE; + net->dest_state |= SCTP_ADDR_NOT_REACHABLE; + if (net == stcb->asoc.primary_destination) { + net->dest_state |= SCTP_ADDR_WAS_PRIMARY; + } + sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, + stcb, + SCTP_FAILED_THRESHOLD, + (void *)net); + } + } + /*********HOLD THIS COMMENT FOR PATCH OF ALTERNATE + *********ROUTING CODE + */ + /*********HOLD THIS COMMENT FOR END OF PATCH OF ALTERNATE + *********ROUTING CODE + */ + } + if (stcb == NULL) + return (0); + + if (net) { + if ((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0) { + stcb->asoc.overall_error_count++; + } + } else { + stcb->asoc.overall_error_count++; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER4) { + printf("Overall error count for %p now %d thresh:%u state:%x\n", + &stcb->asoc, + stcb->asoc.overall_error_count, + (u_int)threshold, + ((net == NULL) ? (u_int)0 : (u_int)net->dest_state)); + } +#endif /* SCTP_DEBUG */ + /* We specifically do not do >= to give the assoc one more + * change before we fail it. + */ + if (stcb->asoc.overall_error_count > threshold) { + /* Abort notification sends a ULP notify */ + struct mbuf *oper; + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, struct sctp_paramhdr *); + ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x40000001); + } + sctp_abort_an_association(inp, stcb, SCTP_FAILED_THRESHOLD, oper); + return (1); + } + return (0); +} + +struct sctp_nets * +sctp_find_alternate_net(struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + /* Find and return an alternate network if possible */ + struct sctp_nets *alt, *mnet; + int once; + + if (stcb->asoc.numnets == 1) { + /* No others but net */ + return (TAILQ_FIRST(&stcb->asoc.nets)); + } + mnet = net; + once = 0; + + if (mnet == NULL) { + mnet = TAILQ_FIRST(&stcb->asoc.nets); + } + do { + alt = TAILQ_NEXT(mnet, sctp_next); + if (alt == NULL) { + once++; + if (once > 1) { + break; + } + alt = TAILQ_FIRST(&stcb->asoc.nets); + } + if (alt->ro.ro_rt == NULL) { +#ifndef SCOPEDROUTING + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)&alt->ro._l_addr; + if (sin6->sin6_family == AF_INET6) { +#if defined(SCTP_BASE_FREEBSD) || defined(__APPLE__) + (void)in6_embedscope(&sin6->sin6_addr, sin6, + NULL, NULL); +#else + (void)in6_embedscope(&sin6->sin6_addr, sin6); +#endif + } +#endif +#if defined(__FreeBSD__) || defined(__APPLE__) + rtalloc_ign((struct route*)&alt->ro, 0UL); +#else + rtalloc((struct route*)&alt->ro); +#endif +#ifndef SCOPEDROUTING + if (sin6->sin6_family == AF_INET6) { + (void)in6_recoverscope(sin6, &sin6->sin6_addr, + NULL); + } +#endif + alt->src_addr_selected = 0; + } + if ( + ((alt->dest_state & SCTP_ADDR_REACHABLE) == SCTP_ADDR_REACHABLE) && + (alt->ro.ro_rt != NULL) && + (!(alt->dest_state & SCTP_ADDR_UNCONFIRMED)) + ) { + /* Found a reachable address */ + break; + } + mnet = alt; + } while (alt != NULL); + + if (alt == NULL) { + /* Case where NO insv network exists (dormant state) */ + /* we rotate destinations */ + once = 0; + mnet = net; + do { + alt = TAILQ_NEXT(mnet, sctp_next); + if (alt == NULL) { + once++; + if (once > 1) { + break; + } + alt = TAILQ_FIRST(&stcb->asoc.nets); + } + if ((!(alt->dest_state & SCTP_ADDR_UNCONFIRMED)) && + (alt != net)) { + /* Found an alternate address */ + break; + } + mnet = alt; + } while (alt != NULL); + } + if (alt == NULL) { + return (net); + } + return (alt); +} + +static void +sctp_backoff_on_timeout(struct sctp_tcb *stcb, + struct sctp_nets *net, + int win_probe, + int num_marked) +{ +#ifdef SCTP_DEBUG + int oldRTO; + + oldRTO = net->RTO; +#endif /* SCTP_DEBUG */ + net->RTO <<= 1; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER2) { + printf("Timer doubles from %d ms -to-> %d ms\n", + oldRTO, net->RTO); + } +#endif /* SCTP_DEBUG */ + + if (net->RTO > stcb->asoc.maxrto) { + net->RTO = stcb->asoc.maxrto; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER2) { + printf("Growth capped by maxrto %d\n", + net->RTO); + } +#endif /* SCTP_DEBUG */ + } + + + if ((win_probe == 0) && num_marked) { + /* We don't apply penalty to window probe scenarios */ +#ifdef SCTP_CWND_LOGGING + int old_cwnd=net->cwnd; +#endif + net->ssthresh = net->cwnd >> 1; + if (net->ssthresh < (net->mtu << 1)) { + net->ssthresh = (net->mtu << 1); + } + net->cwnd = net->mtu; + /* floor of 1 mtu */ + if (net->cwnd < net->mtu) + net->cwnd = net->mtu; +#ifdef SCTP_CWND_LOGGING + sctp_log_cwnd(net, net->cwnd-old_cwnd, SCTP_CWND_LOG_FROM_RTX); +#endif + + net->partial_bytes_acked = 0; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("collapse cwnd to 1MTU ssthresh to %d\n", + net->ssthresh); + } +#endif + + } +} + + +static int +sctp_mark_all_for_resend(struct sctp_tcb *stcb, + struct sctp_nets *net, + struct sctp_nets *alt, + int *num_marked) +{ + + /* + * Mark all chunks (well not all) that were sent to *net for retransmission. + * Move them to alt for there destination as well... We only + * mark chunks that have been outstanding long enough to have + * received feed-back. + */ + struct sctp_tmit_chunk *chk, *tp2; + struct sctp_nets *lnets; + struct timeval now, min_wait, tv; + int cur_rto; + int win_probes, non_win_probes, orig_rwnd, audit_tf, num_mk, fir; + unsigned int cnt_mk; + u_int32_t orig_flight; + u_int32_t tsnlast, tsnfirst; + + /* none in flight now */ + audit_tf = 0; + fir=0; + /* figure out how long a data chunk must be pending + * before we can mark it .. + */ + SCTP_GETTIME_TIMEVAL(&now); + /* get cur rto in micro-seconds */ + cur_rto = (((net->lastsa >> 2) + net->lastsv) >> 1); +#ifdef SCTP_FR_LOGGING + sctp_log_fr(cur_rto, 0, 0, SCTP_FR_T3_MARK_TIME); +#endif + cur_rto *= 1000; +#ifdef SCTP_FR_LOGGING + sctp_log_fr(cur_rto, 0, 0, SCTP_FR_T3_MARK_TIME); +#endif + tv.tv_sec = cur_rto / 1000000; + tv.tv_usec = cur_rto % 1000000; +#ifndef __FreeBSD__ + timersub(&now, &tv, &min_wait); +#else + min_wait = now; + timevalsub(&min_wait, &tv); +#endif + if (min_wait.tv_sec < 0 || min_wait.tv_usec < 0) { + /* + * if we hit here, we don't + * have enough seconds on the clock to account + * for the RTO. We just let the lower seconds + * be the bounds and don't worry about it. This + * may mean we will mark a lot more than we should. + */ + min_wait.tv_sec = min_wait.tv_usec = 0; + } +#ifdef SCTP_FR_LOGGING + sctp_log_fr(cur_rto, now.tv_sec, now.tv_usec, SCTP_FR_T3_MARK_TIME); + sctp_log_fr(0, min_wait.tv_sec, min_wait.tv_usec, SCTP_FR_T3_MARK_TIME); +#endif + stcb->asoc.total_flight -= net->flight_size; + if (stcb->asoc.total_flight < 0) { + audit_tf = 1; + stcb->asoc.total_flight = 0; + } + /* Our rwnd will be incorrect here since we are not adding + * back the cnt * mbuf but we will fix that down below. + */ + orig_rwnd = stcb->asoc.peers_rwnd; + orig_flight = net->flight_size; + stcb->asoc.peers_rwnd += net->flight_size; + net->flight_size = 0; + net->rto_pending = 0; + net->fast_retran_ip= 0; + win_probes = non_win_probes = 0; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER2) { + printf("Marking ALL un-acked for retransmission at t3-timeout\n"); + } +#endif /* SCTP_DEBUG */ + /* Now on to each chunk */ + num_mk = cnt_mk = 0; + tsnfirst = tsnlast = 0; + chk = TAILQ_FIRST(&stcb->asoc.sent_queue); + for (;chk != NULL; chk = tp2) { + tp2 = TAILQ_NEXT(chk, sctp_next); + if ((compare_with_wrap(stcb->asoc.last_acked_seq, + chk->rec.data.TSN_seq, + MAX_TSN)) || + (stcb->asoc.last_acked_seq == chk->rec.data.TSN_seq)) { + /* Strange case our list got out of order? */ + printf("Our list is out of order?\n"); + TAILQ_REMOVE(&stcb->asoc.sent_queue, chk, sctp_next); + if (chk->data) { + sctp_release_pr_sctp_chunk(stcb, chk, 0xffff, + &stcb->asoc.sent_queue); + if (chk->flags & SCTP_PR_SCTP_BUFFER) { + stcb->asoc.sent_queue_cnt_removeable--; + } + } + stcb->asoc.sent_queue_cnt--; + sctp_free_remote_addr(chk->whoTo); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is going negative"); + } + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_gencnt_chunk++; + continue; + } + if ((chk->whoTo == net) && (chk->sent < SCTP_DATAGRAM_ACKED)) { + /* found one to mark: + * If it is less than DATAGRAM_ACKED it MUST + * not be a skipped or marked TSN but instead + * one that is either already set for retransmission OR + * one that needs retransmission. + */ + + /* validate its been outstanding long enough */ +#ifdef SCTP_FR_LOGGING + sctp_log_fr(chk->rec.data.TSN_seq, + chk->sent_rcv_time.tv_sec, + chk->sent_rcv_time.tv_usec, + SCTP_FR_T3_MARK_TIME); +#endif + if (chk->sent_rcv_time.tv_sec > min_wait.tv_sec) { + /* we have reached a chunk that was sent some + * seconds past our min.. forget it we will + * find no more to send. + */ +#ifdef SCTP_FR_LOGGING + sctp_log_fr(0, + chk->sent_rcv_time.tv_sec, + chk->sent_rcv_time.tv_usec, + SCTP_FR_T3_STOPPED); +#endif + continue; + } else if (chk->sent_rcv_time.tv_sec == min_wait.tv_sec) { + /* we must look at the micro seconds to know. + */ + if (chk->sent_rcv_time.tv_usec >= min_wait.tv_usec) { + /* ok it was sent after our boundary time. */ +#ifdef SCTP_FR_LOGGING + sctp_log_fr(0, + chk->sent_rcv_time.tv_sec, + chk->sent_rcv_time.tv_usec, + SCTP_FR_T3_STOPPED); +#endif + continue; + } + } + stcb->asoc.total_flight_count--; + if (stcb->asoc.total_flight_count < 0) { + stcb->asoc.total_flight_count = 0; + } + if ((chk->flags & (SCTP_PR_SCTP_ENABLED|SCTP_PR_SCTP_BUFFER)) == SCTP_PR_SCTP_ENABLED) { + /* Is it expired? */ + if ((now.tv_sec > chk->rec.data.timetodrop.tv_sec) || + ((chk->rec.data.timetodrop.tv_sec == now.tv_sec) && + (now.tv_usec > chk->rec.data.timetodrop.tv_usec))) { + /* Yes so drop it */ + if (chk->data) { + sctp_release_pr_sctp_chunk(stcb, + chk, + (SCTP_RESPONSE_TO_USER_REQ|SCTP_NOTIFY_DATAGRAM_SENT), + &stcb->asoc.sent_queue); + } + } + continue; + } + if (chk->sent != SCTP_DATAGRAM_RESEND) { + stcb->asoc.sent_queue_retran_cnt++; + num_mk++; + if (fir == 0) { + fir = 1; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("First TSN marked was %x\n", + chk->rec.data.TSN_seq); + } +#endif + tsnfirst = chk->rec.data.TSN_seq; + } + tsnlast = chk->rec.data.TSN_seq; +#ifdef SCTP_FR_LOGGING + sctp_log_fr(chk->rec.data.TSN_seq, chk->snd_count, + 0, SCTP_FR_T3_MARKED); + +#endif + } + chk->sent = SCTP_DATAGRAM_RESEND; + /* reset the TSN for striking and other FR stuff */ + chk->rec.data.doing_fast_retransmit = 0; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER3) { + printf("mark TSN:%x for retransmission\n", chk->rec.data.TSN_seq); + } +#endif /* SCTP_DEBUG */ + /* Clear any time so NO RTT is being done */ + chk->do_rtt = 0; + /* Bump up the count */ + if (compare_with_wrap(chk->rec.data.TSN_seq, + stcb->asoc.t3timeout_highest_marked, + MAX_TSN)) { + /* TSN_seq > than t3timeout so update */ + stcb->asoc.t3timeout_highest_marked = chk->rec.data.TSN_seq; + } + if (alt != net) { + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = alt; + alt->ref_count++; + } + if ((chk->rec.data.state_flags & SCTP_WINDOW_PROBE) != + SCTP_WINDOW_PROBE) { + non_win_probes++; + } else { + chk->rec.data.state_flags &= ~SCTP_WINDOW_PROBE; + win_probes++; + } + } + if (chk->sent == SCTP_DATAGRAM_RESEND) { + cnt_mk++; + } + } + +#ifdef SCTP_FR_LOGGING + sctp_log_fr(tsnfirst, tsnlast, num_mk, SCTP_FR_T3_TIMEOUT); +#endif + /* compensate for the number we marked */ + stcb->asoc.peers_rwnd += (num_mk /* * sizeof(struct mbuf)*/); + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + if (num_mk) { + printf("LAST TSN marked was %x\n", tsnlast); + printf("Num marked for retransmission was %d peer-rwd:%ld\n", + num_mk, (u_long)stcb->asoc.peers_rwnd); + printf("LAST TSN marked was %x\n", tsnlast); + printf("Num marked for retransmission was %d peer-rwd:%d\n", + num_mk, + (int)stcb->asoc.peers_rwnd + ); + } + } +#endif + *num_marked = num_mk; + if (stcb->asoc.sent_queue_retran_cnt != cnt_mk) { + printf("Local Audit says there are %d for retran asoc cnt:%d\n", + cnt_mk, stcb->asoc.sent_queue_retran_cnt); +#ifndef SCTP_AUDITING_ENABLED + stcb->asoc.sent_queue_retran_cnt = cnt_mk; +#endif + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER3) { + printf("**************************\n"); + } +#endif /* SCTP_DEBUG */ + + /* Now check for a ECN Echo that may be stranded */ + TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) { + if ((chk->whoTo == net) && + (chk->rec.chunk_id == SCTP_ECN_ECHO)) { + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = alt; + if (chk->sent != SCTP_DATAGRAM_RESEND) { + chk->sent = SCTP_DATAGRAM_RESEND; + stcb->asoc.sent_queue_retran_cnt++; + } + alt->ref_count++; + } + } + if ((orig_rwnd == 0) && (stcb->asoc.total_flight == 0) && + (orig_flight <= net->mtu)) { + /* + * If the LAST packet sent was not acked and our rwnd is 0 + * then we are in a win-probe state. + */ + win_probes = 1; + non_win_probes = 0; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("WIN_PROBE set via o_rwnd=0 tf=0 and all:%d fit in mtu:%d\n", + orig_flight, net->mtu); + } +#endif + } + + if (audit_tf) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER4) { + printf("Audit total flight due to negative value net:%p\n", + net); + } +#endif /* SCTP_DEBUG */ + stcb->asoc.total_flight = 0; + stcb->asoc.total_flight_count = 0; + /* Clear all networks flight size */ + TAILQ_FOREACH(lnets, &stcb->asoc.nets, sctp_next) { + lnets->flight_size = 0; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER4) { + printf("Net:%p c-f cwnd:%d ssthresh:%d\n", + lnets, lnets->cwnd, lnets->ssthresh); + } +#endif /* SCTP_DEBUG */ + } + TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { + if (chk->sent < SCTP_DATAGRAM_RESEND) { + stcb->asoc.total_flight += chk->book_size; + chk->whoTo->flight_size += chk->book_size; + stcb->asoc.total_flight_count++; + } + } + } + /* Setup the ecn nonce re-sync point. We + * do this since retranmissions are NOT + * setup for ECN. This means that do to + * Karn's rule, we don't know the total + * of the peers ecn bits. + */ + chk = TAILQ_FIRST(&stcb->asoc.send_queue); + if (chk == NULL) { + stcb->asoc.nonce_resync_tsn = stcb->asoc.sending_seq; + } else { + stcb->asoc.nonce_resync_tsn = chk->rec.data.TSN_seq; + } + stcb->asoc.nonce_wait_for_ecne = 0; + stcb->asoc.nonce_sum_check = 0; + /* We return 1 if we only have a window probe outstanding */ + if (win_probes && (non_win_probes == 0)) { + return (1); + } + return (0); +} + +static void +sctp_move_all_chunks_to_alt(struct sctp_tcb *stcb, + struct sctp_nets *net, + struct sctp_nets *alt) +{ + struct sctp_association *asoc; + struct sctp_stream_out *outs; + struct sctp_tmit_chunk *chk; + + if (net == alt) + /* nothing to do */ + return; + + asoc = &stcb->asoc; + + /* + * now through all the streams checking for chunks sent to our + * bad network. + */ + TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) { + /* now clean up any chunks here */ + TAILQ_FOREACH(chk, &outs->outqueue, sctp_next) { + if (chk->whoTo == net) { + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = alt; + alt->ref_count++; + } + } + } + /* Now check the pending queue */ + TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) { + if (chk->whoTo == net) { + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = alt; + alt->ref_count++; + } + } + +} + +int +sctp_t3rxt_timer(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_nets *alt; + int win_probe, num_mk; + + +#ifdef SCTP_FR_LOGGING + sctp_log_fr(0, 0, 0, SCTP_FR_T3_TIMEOUT); +#endif + /* Find an alternate and mark those for retransmission */ + alt = sctp_find_alternate_net(stcb, net); + win_probe = sctp_mark_all_for_resend(stcb, net, alt, &num_mk); + + /* FR Loss recovery just ended with the T3. */ + stcb->asoc.fast_retran_loss_recovery = 0; + + /* setup the sat loss recovery that prevents + * satellite cwnd advance. + */ + stcb->asoc.sat_t3_loss_recovery = 1; + stcb->asoc.sat_t3_recovery_tsn = stcb->asoc.sending_seq; + + /* Backoff the timer and cwnd */ + sctp_backoff_on_timeout(stcb, net, win_probe, num_mk); + if (win_probe == 0) { + /* We don't do normal threshold management on window probes */ + if (sctp_threshold_management(inp, stcb, net, + stcb->asoc.max_send_times)) { + /* Association was destroyed */ + return (1); + } else { + if (net != stcb->asoc.primary_destination) { + /* send a immediate HB if our RTO is stale */ + struct timeval now; + unsigned int ms_goneby; + SCTP_GETTIME_TIMEVAL(&now); + if (net->last_sent_time.tv_sec) { + ms_goneby = (now.tv_sec - net->last_sent_time.tv_sec) * 1000; + } else { + ms_goneby = 0; + } + if ((ms_goneby > net->RTO) || (net->RTO == 0)) { + /* no recent feed back in an RTO or more, request a RTT update */ + sctp_send_hb(stcb, 1, net); + } + } + } + } else { + /* + * For a window probe we don't penalize the net's but only + * the association. This may fail it if SACKs are not coming + * back. If sack's are coming with rwnd locked at 0, we will + * continue to hold things waiting for rwnd to raise + */ + if (sctp_threshold_management(inp, stcb, NULL, + stcb->asoc.max_send_times)) { + /* Association was destroyed */ + return (1); + } + } + if (net->dest_state & SCTP_ADDR_NOT_REACHABLE) { + /* Move all pending over too */ + sctp_move_all_chunks_to_alt(stcb, net, alt); + /* Was it our primary? */ + if ((stcb->asoc.primary_destination == net) && (alt != net)) { + /* + * Yes, note it as such and find an alternate + * note: this means HB code must use this to resent + * the primary if it goes active AND if someone does + * a change-primary then this flag must be cleared + * from any net structures. + */ + if (sctp_set_primary_addr(stcb, + (struct sockaddr *)NULL, + alt) == 0) { + net->dest_state |= SCTP_ADDR_WAS_PRIMARY; + net->src_addr_selected = 0; + } + } + } + /* + * Special case for cookie-echo'ed case, we don't do output + * but must await the COOKIE-ACK before retransmission + */ + if (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_ECHOED) { + /* + * Here we just reset the timer and start again since we + * have not established the asoc + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("Special cookie case return\n"); + } +#endif /* SCTP_DEBUG */ + sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, net); + return (0); + } + if (stcb->asoc.peer_supports_prsctp) { + struct sctp_tmit_chunk *lchk; + lchk = sctp_try_advance_peer_ack_point(stcb, &stcb->asoc); + /* C3. See if we need to send a Fwd-TSN */ + if (compare_with_wrap(stcb->asoc.advanced_peer_ack_point, + stcb->asoc.last_acked_seq, MAX_TSN)) { + /* + * ISSUE with ECN, see FWD-TSN processing for notes + * on issues that will occur when the ECN NONCE stuff + * is put into SCTP for cross checking. + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("Forward TSN time\n"); + } +#endif /* SCTP_DEBUG */ + send_forward_tsn(stcb, &stcb->asoc); + if (lchk) { + /* Assure a timer is up */ + sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, lchk->whoTo); + } + } + } + return (0); +} + +int +sctp_t1init_timer(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + /* bump the thresholds */ + if (stcb->asoc.delayed_connection) { + /* special hook for delayed connection. The + * library did NOT complete the rest of its + * sends. + */ + stcb->asoc.delayed_connection = 0; + sctp_send_initiate(inp, stcb); + return (0); + } + if (sctp_threshold_management(inp, stcb, net, + stcb->asoc.max_init_times)) { + /* Association was destroyed */ + return (1); + } + stcb->asoc.dropped_special_cnt = 0; + sctp_backoff_on_timeout(stcb, stcb->asoc.primary_destination, 1, 0); + if (stcb->asoc.initial_init_rto_max < net->RTO) { + net->RTO = stcb->asoc.initial_init_rto_max; + } + if (stcb->asoc.numnets > 1) { + /* If we have more than one addr use it */ + struct sctp_nets *alt; + alt = sctp_find_alternate_net(stcb, stcb->asoc.primary_destination); + if ((alt != NULL) && (alt != stcb->asoc.primary_destination)) { + sctp_move_all_chunks_to_alt(stcb, stcb->asoc.primary_destination, alt); + stcb->asoc.primary_destination = alt; + } + } + /* Send out a new init */ + sctp_send_initiate(inp, stcb); + return (0); +} + +/* + * For cookie and asconf we actually need to find and mark for resend, + * then increment the resend counter (after all the threshold management + * stuff of course). + */ +int sctp_cookie_timer(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_nets *alt; + struct sctp_tmit_chunk *cookie; + /* first before all else we must find the cookie */ + TAILQ_FOREACH(cookie, &stcb->asoc.control_send_queue, sctp_next) { + if (cookie->rec.chunk_id == SCTP_COOKIE_ECHO) { + break; + } + } + if (cookie == NULL) { + if (SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_COOKIE_ECHOED) { + /* FOOBAR! */ + struct mbuf *oper; + MGET(oper, M_DONTWAIT, MT_DATA); + if (oper) { + struct sctp_paramhdr *ph; + u_int32_t *ippp; + + oper->m_len = sizeof(struct sctp_paramhdr) + + sizeof(*ippp); + ph = mtod(oper, struct sctp_paramhdr *); + ph->param_type = htons(SCTP_CAUSE_PROTOCOL_VIOLATION); + ph->param_length = htons(oper->m_len); + ippp = (u_int32_t *)(ph + 1); + *ippp = htonl(0x40000002); + } + sctp_abort_an_association(inp, stcb, SCTP_INTERNAL_ERROR, + oper); + } + return (1); + } + /* Ok we found the cookie, threshold management next */ + if (sctp_threshold_management(inp, stcb, cookie->whoTo, + stcb->asoc.max_init_times)) { + /* Assoc is over */ + return (1); + } + /* + * cleared theshold management now lets backoff the address & + * select an alternate + */ + stcb->asoc.dropped_special_cnt = 0; + sctp_backoff_on_timeout(stcb, cookie->whoTo, 1, 0); + alt = sctp_find_alternate_net(stcb, cookie->whoTo); + if (alt != cookie->whoTo) { + sctp_free_remote_addr(cookie->whoTo); + cookie->whoTo = alt; + alt->ref_count++; + } + /* Now mark the retran info */ + if (cookie->sent != SCTP_DATAGRAM_RESEND) { + stcb->asoc.sent_queue_retran_cnt++; + } + cookie->sent = SCTP_DATAGRAM_RESEND; + /* + * Now call the output routine to kick out the cookie again, Note we + * don't mark any chunks for retran so that FR will need to kick in + * to move these (or a send timer). + */ + return (0); +} + +int sctp_strreset_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_nets *alt; + struct sctp_tmit_chunk *strrst, *chk; + struct sctp_stream_reset_req *strreq; + /* find the existing STRRESET */ + TAILQ_FOREACH(strrst, &stcb->asoc.control_send_queue, + sctp_next) { + if (strrst->rec.chunk_id == SCTP_STREAM_RESET) { + /* is it what we want */ + strreq = mtod(strrst->data, struct sctp_stream_reset_req *); + if (strreq->sr_req.ph.param_type == ntohs(SCTP_STR_RESET_REQUEST)) { + break; + } + } + } + if (strrst == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("Strange, strreset timer fires, but I can't find an str-reset?\n"); + } +#endif /* SCTP_DEBUG */ + return (0); + } + /* do threshold management */ + if (sctp_threshold_management(inp, stcb, strrst->whoTo, + stcb->asoc.max_send_times)) { + /* Assoc is over */ + return (1); + } + + /* + * cleared theshold management + * now lets backoff the address & select an alternate + */ + sctp_backoff_on_timeout(stcb, strrst->whoTo, 1, 0); + alt = sctp_find_alternate_net(stcb, strrst->whoTo); + sctp_free_remote_addr(strrst->whoTo); + strrst->whoTo = alt; + alt->ref_count++; + + /* See if a ECN Echo is also stranded */ + TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) { + if ((chk->whoTo == net) && + (chk->rec.chunk_id == SCTP_ECN_ECHO)) { + sctp_free_remote_addr(chk->whoTo); + if (chk->sent != SCTP_DATAGRAM_RESEND) { + chk->sent = SCTP_DATAGRAM_RESEND; + stcb->asoc.sent_queue_retran_cnt++; + } + chk->whoTo = alt; + alt->ref_count++; + } + } + if (net->dest_state & SCTP_ADDR_NOT_REACHABLE) { + /* + * If the address went un-reachable, we need to move + * to alternates for ALL chk's in queue + */ + sctp_move_all_chunks_to_alt(stcb, net, alt); + } + /* mark the retran info */ + if (strrst->sent != SCTP_DATAGRAM_RESEND) + stcb->asoc.sent_queue_retran_cnt++; + strrst->sent = SCTP_DATAGRAM_RESEND; + + /* restart the timer */ + sctp_timer_start(SCTP_TIMER_TYPE_STRRESET, inp, stcb, strrst->whoTo); + return (0); +} + +int sctp_asconf_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_nets *alt; + struct sctp_tmit_chunk *asconf, *chk; + + /* is this the first send, or a retransmission? */ + if (stcb->asoc.asconf_sent == 0) { + /* compose a new ASCONF chunk and send it */ + sctp_send_asconf(stcb, net); + } else { + /* Retransmission of the existing ASCONF needed... */ + + /* find the existing ASCONF */ + TAILQ_FOREACH(asconf, &stcb->asoc.control_send_queue, + sctp_next) { + if (asconf->rec.chunk_id == SCTP_ASCONF) { + break; + } + } + if (asconf == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("Strange, asconf timer fires, but I can't find an asconf?\n"); + } +#endif /* SCTP_DEBUG */ + return (0); + } + /* do threshold management */ + if (sctp_threshold_management(inp, stcb, asconf->whoTo, + stcb->asoc.max_send_times)) { + /* Assoc is over */ + return (1); + } + + /* PETER? FIX? How will the following code ever run? If + * the max_send_times is hit, threshold managment will + * blow away the association? + */ + if (asconf->snd_count > stcb->asoc.max_send_times) { + /* + * Something is rotten, peer is not responding to + * ASCONFs but maybe is to data etc. e.g. it is not + * properly handling the chunk type upper bits + * Mark this peer as ASCONF incapable and cleanup + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("asconf_timer: Peer has not responded to our repeated ASCONFs\n"); + } +#endif /* SCTP_DEBUG */ + sctp_asconf_cleanup(stcb, net); + return (0); + } + /* + * cleared theshold management + * now lets backoff the address & select an alternate + */ + sctp_backoff_on_timeout(stcb, asconf->whoTo, 1, 0); + alt = sctp_find_alternate_net(stcb, asconf->whoTo); + sctp_free_remote_addr(asconf->whoTo); + asconf->whoTo = alt; + alt->ref_count++; + + /* See if a ECN Echo is also stranded */ + TAILQ_FOREACH(chk, &stcb->asoc.control_send_queue, sctp_next) { + if ((chk->whoTo == net) && + (chk->rec.chunk_id == SCTP_ECN_ECHO)) { + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = alt; + if (chk->sent != SCTP_DATAGRAM_RESEND) { + chk->sent = SCTP_DATAGRAM_RESEND; + stcb->asoc.sent_queue_retran_cnt++; + } + alt->ref_count++; + + } + } + if (net->dest_state & SCTP_ADDR_NOT_REACHABLE) { + /* + * If the address went un-reachable, we need to move + * to alternates for ALL chk's in queue + */ + sctp_move_all_chunks_to_alt(stcb, net, alt); + } + /* mark the retran info */ + if (asconf->sent != SCTP_DATAGRAM_RESEND) + stcb->asoc.sent_queue_retran_cnt++; + asconf->sent = SCTP_DATAGRAM_RESEND; + } + return (0); +} + +/* + * For the shutdown and shutdown-ack, we do not keep one around on the + * control queue. This means we must generate a new one and call the general + * chunk output routine, AFTER having done threshold + * management. + */ +int +sctp_shutdown_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_nets *alt; + /* first threshold managment */ + if (sctp_threshold_management(inp, stcb, net, stcb->asoc.max_send_times)) { + /* Assoc is over */ + return (1); + } + /* second select an alternative */ + alt = sctp_find_alternate_net(stcb, net); + + /* third generate a shutdown into the queue for out net */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("%s:%d sends a shutdown\n", + __FILE__, + __LINE__ + ); + } +#endif + if (alt) { + sctp_send_shutdown(stcb, alt); + } else { + /* if alt is NULL, there is no dest + * to send to?? + */ + return (0); + } + /* fourth restart timer */ + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, inp, stcb, alt); + return (0); +} + +int sctp_shutdownack_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_nets *alt; + /* first threshold managment */ + if (sctp_threshold_management(inp, stcb, net, stcb->asoc.max_send_times)) { + /* Assoc is over */ + return (1); + } + /* second select an alternative */ + alt = sctp_find_alternate_net(stcb, net); + + /* third generate a shutdown into the queue for out net */ + sctp_send_shutdown_ack(stcb, alt); + + /* fourth restart timer */ + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNACK, inp, stcb, alt); + return (0); +} + +static void +sctp_audit_stream_queues_for_size(struct sctp_inpcb *inp, + struct sctp_tcb *stcb) +{ + struct sctp_stream_out *outs; + struct sctp_tmit_chunk *chk; + unsigned int chks_in_queue=0; + + if ((stcb == NULL) || (inp == NULL)) + return; + if (TAILQ_EMPTY(&stcb->asoc.out_wheel)) { + printf("Strange, out_wheel empty nothing on sent/send and tot=%lu?\n", + (u_long)stcb->asoc.total_output_queue_size); + stcb->asoc.total_output_queue_size = 0; + return; + } + if (stcb->asoc.sent_queue_retran_cnt) { + printf("Hmm, sent_queue_retran_cnt is non-zero %d\n", + stcb->asoc.sent_queue_retran_cnt); + stcb->asoc.sent_queue_retran_cnt = 0; + } + /* Check to see if some data queued, if so report it */ + TAILQ_FOREACH(outs, &stcb->asoc.out_wheel, next_spoke) { + if (!TAILQ_EMPTY(&outs->outqueue)) { + TAILQ_FOREACH(chk, &outs->outqueue, sctp_next) { + chks_in_queue++; + } + } + } + if (chks_in_queue != stcb->asoc.stream_queue_cnt) { + printf("Hmm, stream queue cnt at %d I counted %d in stream out wheel\n", + stcb->asoc.stream_queue_cnt, chks_in_queue); + } + if (chks_in_queue) { + /* call the output queue function */ + sctp_chunk_output(inp, stcb, 1); + if ((TAILQ_EMPTY(&stcb->asoc.send_queue)) && + (TAILQ_EMPTY(&stcb->asoc.sent_queue))) { + /* Probably should go in and make it go back through and add fragments allowed */ + printf("Still nothing moved %d chunks are stuck\n", chks_in_queue); + } + } else { + printf("Found no chunks on any queue tot:%lu\n", + (u_long)stcb->asoc.total_output_queue_size); + stcb->asoc.total_output_queue_size = 0; + } +} + +int +sctp_heartbeat_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + int cnt_of_unconf=0; + + if (net) { + if (net->hb_responded == 0) { + sctp_backoff_on_timeout(stcb, net, 1, 0); + } + /* Zero PBA, if it needs it */ + if (net->partial_bytes_acked) { + net->partial_bytes_acked = 0; + } + } + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + if ((net->dest_state & SCTP_ADDR_UNCONFIRMED) && + (net->dest_state & SCTP_ADDR_REACHABLE)) { + cnt_of_unconf++; + } + } + if ((stcb->asoc.total_output_queue_size > 0) && + (TAILQ_EMPTY(&stcb->asoc.send_queue)) && + (TAILQ_EMPTY(&stcb->asoc.sent_queue))) { + sctp_audit_stream_queues_for_size(inp, stcb); + } + /* Send a new HB, this will do threshold managment, pick a new dest */ + if (sctp_send_hb(stcb, 0, NULL) < 0) { + return (1); + } + if (cnt_of_unconf > 1) { + /* + * this will send out extra hb's up to maxburst if + * there are any unconfirmed addresses. + */ + int cnt_sent = 1; + while ((cnt_sent < stcb->asoc.max_burst) && (cnt_of_unconf > 1)) { + if (sctp_send_hb(stcb, 0, NULL) == 0) + break; + cnt_of_unconf--; + cnt_sent++; + } + } + return (0); +} + +#define SCTP_NUMBER_OF_MTU_SIZES 18 +static u_int32_t mtu_sizes[]={ + 68, + 296, + 508, + 512, + 544, + 576, + 1006, + 1492, + 1500, + 1536, + 2002, + 2048, + 4352, + 4464, + 8166, + 17914, + 32000, + 65535 +}; + + +static u_int32_t +sctp_getnext_mtu(struct sctp_inpcb *inp, u_int32_t cur_mtu) +{ + /* select another MTU that is just bigger than this one */ + int i; + + for (i = 0; i < SCTP_NUMBER_OF_MTU_SIZES; i++) { + if (cur_mtu < mtu_sizes[i]) { + /* no max_mtu is bigger than this one */ + return (mtu_sizes[i]); + } + } + /* here return the highest allowable */ + return (cur_mtu); +} + + +void sctp_pathmtu_timer(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + u_int32_t next_mtu; + + /* restart the timer in any case */ + next_mtu = sctp_getnext_mtu(inp, net->mtu); + if (next_mtu <= net->mtu) { + /* nothing to do */ + return; + } + if (net->ro.ro_rt != NULL) { + /* only if we have a route and interface do we + * set anything. Note we always restart + * the timer though just in case it is updated + * (i.e. the ifp) or route/ifp is populated. + */ + if (net->ro.ro_rt->rt_ifp != NULL) { + if (net->ro.ro_rt->rt_ifp->if_mtu > next_mtu) { + /* ok it will fit out the door */ + net->mtu = next_mtu; + } + } + } + /* restart the timer */ + sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net); +} + +void sctp_autoclose_timer(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct timeval tn, *tim_touse; + struct sctp_association *asoc; + int ticks_gone_by; + + SCTP_GETTIME_TIMEVAL(&tn); + if (stcb->asoc.sctp_autoclose_ticks && + (inp->sctp_flags & SCTP_PCB_FLAGS_AUTOCLOSE)) { + /* Auto close is on */ + asoc = &stcb->asoc; + /* pick the time to use */ + if (asoc->time_last_rcvd.tv_sec > + asoc->time_last_sent.tv_sec) { + tim_touse = &asoc->time_last_rcvd; + } else { + tim_touse = &asoc->time_last_sent; + } + /* Now has long enough transpired to autoclose? */ + ticks_gone_by = ((tn.tv_sec - tim_touse->tv_sec) * hz); + if ((ticks_gone_by > 0) && + (ticks_gone_by >= (int)asoc->sctp_autoclose_ticks)) { + /* + * autoclose time has hit, call the output routine, + * which should do nothing just to be SURE we don't + * have hanging data. We can then safely check the + * queues and know that we are clear to send shutdown + */ + sctp_chunk_output(inp, stcb, 9); + /* Are we clean? */ + if (TAILQ_EMPTY(&asoc->send_queue) && + TAILQ_EMPTY(&asoc->sent_queue)) { + /* + * there is nothing queued to send, + * so I'm done... + */ + if (SCTP_GET_STATE(asoc) != + SCTP_STATE_SHUTDOWN_SENT) { + /* only send SHUTDOWN 1st time thru */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("%s:%d sends a shutdown\n", + __FILE__, + __LINE__ + ); + } +#endif + sctp_send_shutdown(stcb, stcb->asoc.primary_destination); + asoc->state = SCTP_STATE_SHUTDOWN_SENT; + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, + stcb->sctp_ep, stcb, + asoc->primary_destination); + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, + stcb->sctp_ep, stcb, + asoc->primary_destination); + } + } + } else { + /* + * No auto close at this time, reset t-o to + * check later + */ + int tmp; + /* fool the timer startup to use the time left */ + tmp = asoc->sctp_autoclose_ticks; + asoc->sctp_autoclose_ticks -= ticks_gone_by; + sctp_timer_start(SCTP_TIMER_TYPE_AUTOCLOSE, inp, stcb, + net); + /* restore the real tick value */ + asoc->sctp_autoclose_ticks = tmp; + } + } +} + +void +sctp_iterator_timer(struct sctp_iterator *it) +{ + int cnt= 0; + /* only one iterator can run at a + * time. This is the only way we + * can cleanly pull ep's from underneath + * all the running interators when a + * ep is freed. + */ + SCTP_ITERATOR_LOCK(); + if (it->inp == NULL) { + /* iterator is complete */ + done_with_iterator: + SCTP_ITERATOR_UNLOCK(); + SCTP_INP_INFO_WLOCK(); + LIST_REMOVE(it, sctp_nxt_itr); + /* stopping the callout is not needed, in theory, + * but I am paranoid. + */ + SCTP_INP_INFO_WUNLOCK(); + callout_stop(&it->tmr.timer); + if (it->function_atend != NULL) { + (*it->function_atend)(it->pointer, it->val); + } + FREE(it, M_PCB); + return; + } + select_a_new_ep: + SCTP_INP_WLOCK(it->inp); + while ((it->pcb_flags) && ((it->inp->sctp_flags & it->pcb_flags) != it->pcb_flags)) { + /* we do not like this ep */ + if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { + SCTP_INP_WUNLOCK(it->inp); + goto done_with_iterator; + } + SCTP_INP_WUNLOCK(it->inp); + it->inp = LIST_NEXT(it->inp, sctp_list); + if (it->inp == NULL) { + goto done_with_iterator; + } + SCTP_INP_WLOCK(it->inp); + } + if ((it->inp->inp_starting_point_for_iterator != NULL) && + (it->inp->inp_starting_point_for_iterator != it)) { + printf("Iterator collision, we must wait for other iterator at %x\n", + (u_int)it->inp); + SCTP_INP_WUNLOCK(it->inp); + goto start_timer_return; + } + /* now we do the actual write to this guy */ + it->inp->inp_starting_point_for_iterator = it; + SCTP_INP_WUNLOCK(it->inp); + SCTP_INP_RLOCK(it->inp); + /* if we reach here we found a inp acceptable, now through each + * one that has the association in the right state + */ + if (it->stcb == NULL) { + it->stcb = LIST_FIRST(&it->inp->sctp_asoc_list); + } + if (it->stcb->asoc.stcb_starting_point_for_iterator == it) { + it->stcb->asoc.stcb_starting_point_for_iterator = NULL; + } + while (it->stcb) { + SCTP_TCB_LOCK(it->stcb); + if (it->asoc_state && ((it->stcb->asoc.state & it->asoc_state) != it->asoc_state)) { + SCTP_TCB_UNLOCK(it->stcb); + it->stcb = LIST_NEXT(it->stcb, sctp_tcblist); + continue; + } + cnt++; + /* run function on this one */ + SCTP_INP_RUNLOCK(it->inp); + (*it->function_toapply)(it->inp, it->stcb, it->pointer, it->val); + sctp_chunk_output(it->inp, it->stcb, 1); + SCTP_TCB_UNLOCK(it->stcb); + /* see if we have limited out */ + if (cnt > SCTP_MAX_ITERATOR_AT_ONCE) { + it->stcb->asoc.stcb_starting_point_for_iterator = it; + start_timer_return: + SCTP_ITERATOR_UNLOCK(); + sctp_timer_start(SCTP_TIMER_TYPE_ITERATOR, (struct sctp_inpcb *)it, NULL, NULL); + return; + } + SCTP_INP_RLOCK(it->inp); + it->stcb = LIST_NEXT(it->stcb, sctp_tcblist); + } + /* if we reach here, we ran out of stcb's in the inp we are looking at */ + SCTP_INP_RUNLOCK(it->inp); + SCTP_INP_WLOCK(it->inp); + it->inp->inp_starting_point_for_iterator = NULL; + SCTP_INP_WUNLOCK(it->inp); + if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { + it->inp = NULL; + } else { + SCTP_INP_INFO_RLOCK(); + it->inp = LIST_NEXT(it->inp, sctp_list); + SCTP_INP_INFO_RUNLOCK(); + } + if (it->inp == NULL) { + goto done_with_iterator; + } + goto select_a_new_ep; +} diff --git a/sys/netinet/sctp_timer.h b/sys/netinet/sctp_timer.h new file mode 100644 index 0000000000..206de44b9d --- /dev/null +++ b/sys/netinet/sctp_timer.h @@ -0,0 +1,74 @@ +/* $KAME: sctp_timer.h,v 1.5 2004/08/17 04:06:20 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_timer.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +#ifndef __sctp_timer_h__ +#define __sctp_timer_h__ + +/* + * Copyright (C) 2002, 2004 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) +struct sctp_nets *sctp_find_alternate_net(struct sctp_tcb *, + struct sctp_nets *); + +int sctp_threshold_management(struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *, uint16_t); + +int sctp_t3rxt_timer(struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); +int sctp_t1init_timer(struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); +int sctp_shutdown_timer(struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); +int sctp_heartbeat_timer(struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); + +int sctp_cookie_timer(struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); + +void sctp_pathmtu_timer(struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); + +int sctp_shutdownack_timer(struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); +int sctp_strreset_timer(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct sctp_nets *net); + +int sctp_asconf_timer(struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); + +void sctp_autoclose_timer(struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *net); + +void sctp_audit_retranmission_queue(struct sctp_association *); + +void sctp_iterator_timer(struct sctp_iterator *it); + +#endif +#endif diff --git a/sys/netinet/sctp_uio.h b/sys/netinet/sctp_uio.h new file mode 100644 index 0000000000..5b573ebe91 --- /dev/null +++ b/sys/netinet/sctp_uio.h @@ -0,0 +1,524 @@ +/* $KAME: sctp_uio.h,v 1.10 2004/08/17 04:06:20 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_uio.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +#ifndef __sctp_uio_h__ +#define __sctp_uio_h__ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include + +typedef caddr_t sctp_assoc_t; + +/* On/Off setup for subscription to events */ +struct sctp_event_subscribe { + u_int8_t sctp_data_io_event; + u_int8_t sctp_association_event; + u_int8_t sctp_address_event; + u_int8_t sctp_send_failure_event; + u_int8_t sctp_peer_error_event; + u_int8_t sctp_shutdown_event; + u_int8_t sctp_partial_delivery_event; + u_int8_t sctp_adaption_layer_event; + u_int8_t sctp_stream_reset_events; +}; + +/* ancillary data types */ +#define SCTP_INIT 0x0001 +#define SCTP_SNDRCV 0x0002 + +/* + * ancillary data structures + */ +struct sctp_initmsg { + u_int32_t sinit_num_ostreams; + u_int32_t sinit_max_instreams; + u_int16_t sinit_max_attempts; + u_int16_t sinit_max_init_timeo; +}; + +struct sctp_sndrcvinfo { + u_int16_t sinfo_stream; + u_int16_t sinfo_ssn; + u_int16_t sinfo_flags; + u_int32_t sinfo_ppid; + u_int32_t sinfo_context; + u_int32_t sinfo_timetolive; + u_int32_t sinfo_tsn; + u_int32_t sinfo_cumtsn; + sctp_assoc_t sinfo_assoc_id; +}; + +struct sctp_snd_all_completes { + u_int16_t sall_stream; + u_int16_t sall_flags; + u_int32_t sall_ppid; + u_int32_t sall_context; + u_int32_t sall_num_sent; + u_int32_t sall_num_failed; +}; + +/* send/recv flags */ +/* MSG_EOF (0x0100) is reused from sys/socket.h */ +#define MSG_SENDALL 0x0200 +#define MSG_PR_SCTP_TTL 0x0400 /* Partial Reliable on this msg */ +#define MSG_PR_SCTP_BUF 0x0800 /* Buffer based PR-SCTP */ +#ifndef MSG_EOF +#define MSG_EOF 0x1000 /* Start shutdown procedures */ +#endif +#define MSG_UNORDERED 0x2000 /* Message is un-ordered */ +#define MSG_ADDR_OVER 0x4000 /* Override the primary-address */ +#define MSG_ABORT 0x8000 /* Send an ABORT to peer */ + +/* Stat's */ +struct sctp_pcbinfo { + u_int32_t ep_count; + u_int32_t asoc_count; + u_int32_t laddr_count; + u_int32_t raddr_count; + u_int32_t chk_count; + u_int32_t sockq_count; + u_int32_t mbuf_track; +}; + +struct sctp_sockstat { + sctp_assoc_t ss_assoc_id; + u_int32_t ss_total_sndbuf; + u_int32_t ss_total_mbuf_sndbuf; + u_int32_t ss_total_recv_buf; +}; + +/* + * notification event structures + */ + + +/* association change events */ + +struct sctp_assoc_change { + u_int16_t sac_type; + u_int16_t sac_flags; + u_int32_t sac_length; + u_int16_t sac_state; + u_int16_t sac_error; + u_int16_t sac_outbound_streams; + u_int16_t sac_inbound_streams; + sctp_assoc_t sac_assoc_id; +}; +/* sac_state values */ + +#define SCTP_COMM_UP 0x0001 +#define SCTP_COMM_LOST 0x0002 +#define SCTP_RESTART 0x0003 +#define SCTP_SHUTDOWN_COMP 0x0004 +#define SCTP_CANT_STR_ASSOC 0x0005 + + +/* Address events */ +struct sctp_paddr_change { + u_int16_t spc_type; + u_int16_t spc_flags; + u_int32_t spc_length; + struct sockaddr_storage spc_aaddr; + u_int32_t spc_state; + u_int32_t spc_error; + sctp_assoc_t spc_assoc_id; +}; +/* paddr state values */ +#define SCTP_ADDR_AVAILABLE 0x0001 +#define SCTP_ADDR_UNREACHABLE 0x0002 +#define SCTP_ADDR_REMOVED 0x0003 +#define SCTP_ADDR_ADDED 0x0004 +#define SCTP_ADDR_MADE_PRIM 0x0005 +#define SCTP_ADDR_CONFIRMED 0x0006 + +/* + * CAUTION: these are user exposed SCTP addr reachability states + * must be compatible with SCTP_ADDR states in sctp_constants.h + */ +#ifdef SCTP_ACTIVE +#undef SCTP_ACTIVE +#endif +#define SCTP_ACTIVE 0x0001 /* SCTP_ADDR_REACHABLE */ + +#ifdef SCTP_INACTIVE +#undef SCTP_INACTIVE +#endif +#define SCTP_INACTIVE 0x0002 /* SCTP_ADDR_NOT_REACHABLE */ + + +#ifdef SCTP_UNCONFIRMED +#undef SCTP_UNCONFIRMED +#endif +#define SCTP_UNCONFIRMED 0x0200 /* SCTP_ADDR_UNCONFIRMED */ + +#ifdef SCTP_NOHEARTBEAT +#undef SCTP_NOHEARTBEAT +#endif +#define SCTP_NOHEARTBEAT 0x0040 /* SCTP_ADDR_NOHB */ + + + + +/* remote error events */ +struct sctp_remote_error { + u_int16_t sre_type; + u_int16_t sre_flags; + u_int32_t sre_length; + u_int16_t sre_error; + sctp_assoc_t sre_assoc_id; + u_int8_t sre_data[4]; +}; + +/* data send failure event */ +struct sctp_send_failed { + u_int16_t ssf_type; + u_int16_t ssf_flags; + u_int32_t ssf_length; + u_int32_t ssf_error; + struct sctp_sndrcvinfo ssf_info; + sctp_assoc_t ssf_assoc_id; + u_int8_t ssf_data[4]; +}; + +/* flag that indicates state of data */ +#define SCTP_DATA_UNSENT 0x0001 /* inqueue never on wire */ +#define SCTP_DATA_SENT 0x0002 /* on wire at failure */ + +/* shutdown event */ +struct sctp_shutdown_event { + u_int16_t sse_type; + u_int16_t sse_flags; + u_int32_t sse_length; + sctp_assoc_t sse_assoc_id; +}; + +/* Adaption layer indication stuff */ +struct sctp_adaption_event { + u_int16_t sai_type; + u_int16_t sai_flags; + u_int32_t sai_length; + u_int32_t sai_adaption_ind; + sctp_assoc_t sai_assoc_id; +}; + +struct sctp_setadaption { + u_int32_t ssb_adaption_ind; +}; + +/* pdapi indications */ +struct sctp_pdapi_event { + u_int16_t pdapi_type; + u_int16_t pdapi_flags; + u_int32_t pdapi_length; + u_int32_t pdapi_indication; + sctp_assoc_t pdapi_assoc_id; +}; + + +#define SCTP_PARTIAL_DELIVERY_ABORTED 0x0001 + +/* stream reset stuff */ + +struct sctp_stream_reset_event { + u_int16_t strreset_type; + u_int16_t strreset_flags; + u_int32_t strreset_length; + sctp_assoc_t strreset_assoc_id; + u_int16_t strreset_list[0]; +}; + +/* flags in strreset_flags filed */ +#define SCTP_STRRESET_INBOUND_STR 0x0001 +#define SCTP_STRRESET_OUTBOUND_STR 0x0002 +#define SCTP_STRRESET_ALL_STREAMS 0x0004 +#define SCTP_STRRESET_STREAM_LIST 0x0008 + +#define MAX_ASOC_IDS_RET 255 + +struct sctp_assoc_ids { + u_int16_t asls_assoc_start; /* array of index's start at 0 */ + u_int8_t asls_numb_present; + u_int8_t asls_more_to_get; + sctp_assoc_t asls_assoc_id[MAX_ASOC_IDS_RET]; +}; + +/* notification types */ +#define SCTP_ASSOC_CHANGE 0x0001 +#define SCTP_PEER_ADDR_CHANGE 0x0002 +#define SCTP_REMOTE_ERROR 0x0003 +#define SCTP_SEND_FAILED 0x0004 +#define SCTP_SHUTDOWN_EVENT 0x0005 +#define SCTP_ADAPTION_INDICATION 0x0006 +#define SCTP_PARTIAL_DELIVERY_EVENT 0x0007 +#define SCTP_STREAM_RESET_EVENT 0x0008 + + + +struct sctp_tlv { + u_int16_t sn_type; + u_int16_t sn_flags; + u_int32_t sn_length; +}; + + +/* notification event */ +union sctp_notification { + struct sctp_tlv sn_header; + struct sctp_assoc_change sn_assoc_change; + struct sctp_paddr_change sn_paddr_change; + struct sctp_remote_error sn_remote_error; + struct sctp_send_failed sn_send_failed; + struct sctp_shutdown_event sn_shutdown_event; + struct sctp_adaption_event sn_adaption_event; + struct sctp_pdapi_event sn_pdapi_event; + struct sctp_stream_reset_event sn_strreset_event; +}; + +/* + * socket option structs + */ +#define SCTP_ISSUE_HB 0xffffffff /* get a on-demand hb */ +#define SCTP_NO_HB 0x0 /* turn off hb's */ + +struct sctp_paddrparams { + sctp_assoc_t spp_assoc_id; + struct sockaddr_storage spp_address; + u_int32_t spp_hbinterval; + u_int16_t spp_pathmaxrxt; +}; + +struct sctp_paddrinfo { + sctp_assoc_t spinfo_assoc_id; + struct sockaddr_storage spinfo_address; + int32_t spinfo_state; + u_int32_t spinfo_cwnd; + u_int32_t spinfo_srtt; + u_int32_t spinfo_rto; + u_int32_t spinfo_mtu; +}; + +struct sctp_rtoinfo { + sctp_assoc_t srto_assoc_id; + u_int32_t srto_initial; + u_int32_t srto_max; + u_int32_t srto_min; +}; + +struct sctp_assocparams { + sctp_assoc_t sasoc_assoc_id; + u_int16_t sasoc_asocmaxrxt; + u_int16_t sasoc_number_peer_destinations; + u_int32_t sasoc_peer_rwnd; + u_int32_t sasoc_local_rwnd; + u_int32_t sasoc_cookie_life; +}; + +struct sctp_setprim { + sctp_assoc_t ssp_assoc_id; + struct sockaddr_storage ssp_addr; +}; + +struct sctp_setpeerprim { + sctp_assoc_t sspp_assoc_id; + struct sockaddr_storage sspp_addr; +}; + +struct sctp_getaddresses { + sctp_assoc_t sget_assoc_id; + /* addr is filled in for N * sockaddr_storage */ + struct sockaddr addr[1]; +}; + +struct sctp_setstrm_timeout { + sctp_assoc_t ssto_assoc_id; + u_int32_t ssto_timeout; + u_int32_t ssto_streamid_start; + u_int32_t ssto_streamid_end; +}; + +struct sctp_status { + sctp_assoc_t sstat_assoc_id; + int32_t sstat_state; + u_int32_t sstat_rwnd; + u_int16_t sstat_unackdata; + u_int16_t sstat_penddata; + u_int16_t sstat_instrms; + u_int16_t sstat_outstrms; + u_int32_t sstat_fragmentation_point; + struct sctp_paddrinfo sstat_primary; +}; + +struct sctp_cwnd_args { + struct sctp_nets *net; /* network to */ + u_int32_t cwnd_new_value; /* cwnd in k */ + u_int32_t inflight; /* flightsize in k */ + int cwnd_augment; /* increment to it */ +}; + +struct sctp_blk_args { + u_int32_t onmb; /* in 1k bytes */ + u_int32_t onsb; /* in 1k bytes */ + u_int16_t maxmb; /* in 1k bytes */ + u_int16_t maxsb; /* in 1k bytes */ + u_int16_t send_sent_qcnt; /* chnk cnt */ + u_int16_t stream_qcnt; /* chnk cnt */ +}; + +/* + * Max we can reset in one setting, note this is dictated not by the + * define but the size of a mbuf cluster so don't change this define + * and think you can specify more. You must do multiple resets if you + * want to reset more than SCTP_MAX_EXPLICIT_STR_RESET. + */ +#define SCTP_MAX_EXPLICT_STR_RESET 1000 + +#define SCTP_RESET_LOCAL_RECV 0x0001 +#define SCTP_RESET_LOCAL_SEND 0x0002 +#define SCTP_RESET_BOTH 0x0003 + +struct sctp_stream_reset { + sctp_assoc_t strrst_assoc_id; + u_int16_t strrst_flags; + u_int16_t strrst_num_streams; /* 0 == ALL */ + u_int16_t strrst_list[0]; /* list if strrst_num_streams is not 0*/ +}; + + +struct sctp_get_nonce_values { + sctp_assoc_t gn_assoc_id; + u_int32_t gn_peers_tag; + u_int32_t gn_local_tag; +}; + +/* Debugging logs */ +struct sctp_str_log{ + u_int32_t n_tsn; + u_int32_t e_tsn; + u_int16_t n_sseq; + u_int16_t e_sseq; +}; + +struct sctp_fr_log { + u_int32_t largest_tsn; + u_int32_t largest_new_tsn; + u_int32_t tsn; +}; + +struct sctp_fr_map { + u_int32_t base; + u_int32_t cum; + u_int32_t high; +}; + +struct sctp_rwnd_log { + u_int32_t rwnd; + u_int32_t send_size; + u_int32_t overhead; + u_int32_t new_rwnd; +}; + +struct sctp_mbcnt_log { + u_int32_t total_queue_size; + u_int32_t size_change; + u_int32_t total_queue_mb_size; + u_int32_t mbcnt_change; +}; + +struct sctp_cwnd_log{ + union { + struct sctp_blk_args blk; + struct sctp_cwnd_args cwnd; + struct sctp_str_log strlog; + struct sctp_fr_log fr; + struct sctp_fr_map map; + struct sctp_rwnd_log rwnd; + struct sctp_mbcnt_log mbcnt; + }x; + u_int8_t from; + u_int8_t event_type; + +}; + +struct sctp_cwnd_log_req{ + int num_in_log; /* Number in log */ + int num_ret; /* Number returned */ + int start_at; /* start at this one */ + int end_at; /* end at this one */ + struct sctp_cwnd_log log[0]; +}; + + + +/* + * API system calls + */ +#if !(defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL))) + +__BEGIN_DECLS +int sctp_peeloff __P((int, sctp_assoc_t)); +int sctp_bindx __P((int, struct sockaddr *, int, int)); +int sctp_connectx __P((int, struct sockaddr *, int)); +int sctp_getpaddrs __P((int, sctp_assoc_t, struct sockaddr **)); +void sctp_freepaddrs __P((struct sockaddr *)); +int sctp_getladdrs __P((int, sctp_assoc_t, struct sockaddr **)); +void sctp_freeladdrs __P((struct sockaddr *)); +int sctp_opt_info __P((int, sctp_assoc_t, int, void *, size_t *)); + +ssize_t sctp_sendmsg __P((int, const void *, size_t, + const struct sockaddr *, + socklen_t, u_int32_t, u_int32_t, u_int16_t, u_int32_t, u_int32_t)); + +ssize_t sctp_send __P((int sd, const void *msg, size_t len, + const struct sctp_sndrcvinfo *sinfo,int flags)); + +ssize_t +sctp_sendx __P((int sd, const void *msg, size_t len, + struct sockaddr *addrs, int addrcnt, + struct sctp_sndrcvinfo *sinfo, int flags)); +ssize_t +sctp_sendmsgx __P((int sd, const void *, size_t, + struct sockaddr *, int, + u_int32_t, u_int32_t, u_int16_t, u_int32_t, u_int32_t)); + +sctp_assoc_t +sctp_getassocid __P((int sd, struct sockaddr *sa)); + +ssize_t sctp_recvmsg __P((int, void *, size_t, struct sockaddr *, + socklen_t *, struct sctp_sndrcvinfo *, int *)); + +__END_DECLS + +#endif /* !_KERNEL */ +#endif /* !__sctp_uio_h__ */ diff --git a/sys/netinet/sctp_usrreq.c b/sys/netinet/sctp_usrreq.c new file mode 100644 index 0000000000..dc6ffdf6bd --- /dev/null +++ b/sys/netinet/sctp_usrreq.c @@ -0,0 +1,4815 @@ +/* $KAME: sctp_usrreq.c,v 1.47 2005/03/06 16:04:18 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_usrreq.c,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#if !(defined(__OpenBSD__) || defined(__APPLE__)) +#include "opt_ipsec.h" +#endif +#if defined(__FreeBSD__) +#include "opt_inet6.h" +#include "opt_inet.h" +#endif +#if defined(__NetBSD__) +#include "opt_inet.h" +#endif + +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__FreeBSD__) || defined(__APPLE__) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef IPSEC +#ifndef __OpenBSD__ +#include +#include +#else +#undef IPSEC +#endif +#endif /* IPSEC */ + +#include + +#if defined(HAVE_NRL_INPCB) || defined(__FreeBSD__) +#ifndef in6pcb +#define in6pcb inpcb +#endif +#ifndef sotoin6pcb +#define sotoin6pcb sotoinpcb +#endif +#endif + +#ifdef SCTP_DEBUG +extern u_int32_t sctp_debug_on; +#endif /* SCTP_DEBUG */ + +/* + * sysctl tunable variables + */ +int sctp_auto_asconf = SCTP_DEFAULT_AUTO_ASCONF; +int sctp_max_burst_default = SCTP_DEF_MAX_BURST; +int sctp_peer_chunk_oh = sizeof(struct mbuf); +int sctp_strict_init = 1; +int sctp_no_csum_on_loopback = 1; +unsigned int sctp_max_chunks_on_queue = SCTP_ASOC_MAX_CHUNKS_ON_QUEUE; +int sctp_sendspace = (128 * 1024); +int sctp_recvspace = 128 * (1024 + +#ifdef INET6 + sizeof(struct sockaddr_in6) +#else + sizeof(struct sockaddr_in) +#endif + ); +int sctp_strict_sacks = 0; +int sctp_ecn = 1; +int sctp_ecn_nonce = 0; + +unsigned int sctp_delayed_sack_time_default = SCTP_RECV_MSEC; +unsigned int sctp_heartbeat_interval_default = SCTP_HB_DEFAULT_MSEC; +unsigned int sctp_pmtu_raise_time_default = SCTP_DEF_PMTU_RAISE_SEC; +unsigned int sctp_shutdown_guard_time_default = SCTP_DEF_MAX_SHUTDOWN_SEC; +unsigned int sctp_secret_lifetime_default = SCTP_DEFAULT_SECRET_LIFE_SEC; +unsigned int sctp_rto_max_default = SCTP_RTO_UPPER_BOUND; +unsigned int sctp_rto_min_default = SCTP_RTO_LOWER_BOUND; +unsigned int sctp_rto_initial_default = SCTP_RTO_INITIAL; +unsigned int sctp_init_rto_max_default = SCTP_RTO_UPPER_BOUND; +unsigned int sctp_valid_cookie_life_default = SCTP_DEFAULT_COOKIE_LIFE; +unsigned int sctp_init_rtx_max_default = SCTP_DEF_MAX_INIT; +unsigned int sctp_assoc_rtx_max_default = SCTP_DEF_MAX_SEND; +unsigned int sctp_path_rtx_max_default = SCTP_DEF_MAX_SEND/2; +unsigned int sctp_nr_outgoing_streams_default = SCTP_OSTREAM_INITIAL; + +void +sctp_init(void) +{ +#ifdef __OpenBSD__ +#define nmbclusters nmbclust +#endif + /* Init the SCTP pcb in sctp_pcb.c */ + u_long sb_max_adj; + + sctp_pcb_init(); + +#ifndef __OpenBSD__ + if (nmbclusters > SCTP_ASOC_MAX_CHUNKS_ON_QUEUE) + sctp_max_chunks_on_queue = nmbclusters; +#else +/* if (nmbclust > SCTP_ASOC_MAX_CHUNKS_ON_QUEUE) + sctp_max_chunks_on_queue = nmbclust; FIX ME */ + sctp_max_chunks_on_queue = nmbclust * 2; +#endif + /* + * Allow a user to take no more than 1/2 the number of clusters + * or the SB_MAX whichever is smaller for the send window. + */ + sb_max_adj = (u_long)((u_quad_t)(SB_MAX) * MCLBYTES / (MSIZE + MCLBYTES)); + sctp_sendspace = min((min(SB_MAX, sb_max_adj)), +#ifndef __OpenBSD__ + ((nmbclusters/2) * SCTP_DEFAULT_MAXSEGMENT)); +#else + ((nmbclust/2) * SCTP_DEFAULT_MAXSEGMENT)); +#endif + /* + * Now for the recv window, should we take the same amount? + * or should I do 1/2 the SB_MAX instead in the SB_MAX min above. + * For now I will just copy. + */ + sctp_recvspace = sctp_sendspace; +#ifdef __OpenBSD__ +#undef nmbclusters +#endif +} + +#ifdef INET6 +void +ip_2_ip6_hdr(struct ip6_hdr *ip6, struct ip *ip) +{ + bzero(ip6, sizeof(*ip6)); + + ip6->ip6_vfc = IPV6_VERSION; + ip6->ip6_plen = ip->ip_len; + ip6->ip6_nxt = ip->ip_p; + ip6->ip6_hlim = ip->ip_ttl; + ip6->ip6_src.s6_addr32[2] = ip6->ip6_dst.s6_addr32[2] = + IPV6_ADDR_INT32_SMP; + ip6->ip6_src.s6_addr32[3] = ip->ip_src.s_addr; + ip6->ip6_dst.s6_addr32[3] = ip->ip_dst.s_addr; +} +#endif /* INET6 */ + +static void +sctp_split_chunks(struct sctp_association *asoc, + struct sctp_stream_out *strm, + struct sctp_tmit_chunk *chk) +{ + struct sctp_tmit_chunk *new_chk; + + /* First we need a chunk */ + new_chk = (struct sctp_tmit_chunk *)SCTP_ZONE_GET(sctppcbinfo.ipi_zone_chunk); + if (new_chk == NULL) { + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + return; + } + sctppcbinfo.ipi_count_chunk++; + sctppcbinfo.ipi_gencnt_chunk++; + /* Copy it all */ + *new_chk = *chk; + /* split the data */ + new_chk->data = m_split(chk->data, (chk->send_size>>1), M_DONTWAIT); + if (new_chk->data == NULL) { + /* Can't split */ + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, new_chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + return; + + } + /* Data is now split adjust sizes */ + chk->send_size >>= 1; + new_chk->send_size >>= 1; + + chk->book_size >>= 1; + new_chk->book_size >>= 1; + + /* now adjust the marks */ + chk->rec.data.rcv_flags |= SCTP_DATA_FIRST_FRAG; + chk->rec.data.rcv_flags &= ~SCTP_DATA_LAST_FRAG; + + new_chk->rec.data.rcv_flags &= ~SCTP_DATA_FIRST_FRAG; + new_chk->rec.data.rcv_flags |= SCTP_DATA_LAST_FRAG; + + /* Increase ref count if dest is set */ + if (chk->whoTo) { + new_chk->whoTo->ref_count++; + } + /* now drop it on the end of the list*/ + asoc->stream_queue_cnt++; + TAILQ_INSERT_AFTER(&strm->outqueue, chk, new_chk, sctp_next); +} + +static void +sctp_notify_mbuf(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + struct sctp_nets *net, + struct ip *ip, + struct sctphdr *sh) + +{ + struct icmp *icmph; + int totsz; + uint16_t nxtsz; + + /* protection */ + if ((inp == NULL) || (stcb == NULL) || (net == NULL) || + (ip == NULL) || (sh == NULL)) { + if (stcb != NULL) + SCTP_TCB_UNLOCK(stcb); + return; + } + /* First job is to verify the vtag matches what I would send */ + if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag)) { + SCTP_TCB_UNLOCK(stcb); + return; + } + icmph = (struct icmp *)((caddr_t)ip - (sizeof(struct icmp) - + sizeof(struct ip))); + if (icmph->icmp_type != ICMP_UNREACH) { + /* We only care about unreachable */ + SCTP_TCB_UNLOCK(stcb); + return; + } + if (icmph->icmp_code != ICMP_UNREACH_NEEDFRAG) { + /* not a unreachable message due to frag. */ + SCTP_TCB_UNLOCK(stcb); + return; + } + totsz = ip->ip_len; + nxtsz = ntohs(icmph->icmp_seq); + if (nxtsz == 0) { + /* + * old type router that does not tell us what the next size + * mtu is. Rats we will have to guess (in a educated fashion + * of course) + */ + nxtsz = find_next_best_mtu(totsz); + } + + /* Stop any PMTU timer */ + sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, NULL); + + /* Adjust destination size limit */ + if (net->mtu > nxtsz) { + net->mtu = nxtsz; + } + /* now what about the ep? */ + if (stcb->asoc.smallest_mtu > nxtsz) { + struct sctp_tmit_chunk *chk, *nchk; + struct sctp_stream_out *strm; + /* Adjust that too */ + stcb->asoc.smallest_mtu = nxtsz; + /* now off to subtract IP_DF flag if needed */ + + TAILQ_FOREACH(chk, &stcb->asoc.send_queue, sctp_next) { + if ((chk->send_size+IP_HDR_SIZE) > nxtsz) { + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + } + } + TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { + if ((chk->send_size+IP_HDR_SIZE) > nxtsz) { + /* + * For this guy we also mark for immediate + * resend since we sent to big of chunk + */ + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + if (chk->sent != SCTP_DATAGRAM_RESEND) { + stcb->asoc.sent_queue_retran_cnt++; + } + chk->sent = SCTP_DATAGRAM_RESEND; + chk->rec.data.doing_fast_retransmit = 0; + + /* Clear any time so NO RTT is being done */ + chk->do_rtt = 0; + stcb->asoc.total_flight -= chk->book_size; + if (stcb->asoc.total_flight < 0) { + stcb->asoc.total_flight = 0; + } + stcb->asoc.total_flight_count--; + if (stcb->asoc.total_flight_count < 0) { + stcb->asoc.total_flight_count = 0; + } + net->flight_size -= chk->book_size; + if (net->flight_size < 0) { + net->flight_size = 0; + } + } + } + TAILQ_FOREACH(strm, &stcb->asoc.out_wheel, next_spoke) { + chk = TAILQ_FIRST(&strm->outqueue); + while (chk) { + nchk = TAILQ_NEXT(chk, sctp_next); + if ((chk->send_size+SCTP_MED_OVERHEAD) > nxtsz) { + sctp_split_chunks(&stcb->asoc, strm, chk); + } + chk = nchk; + } + } + } + sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, NULL); + SCTP_TCB_UNLOCK(stcb); +} + + +void +sctp_notify(struct sctp_inpcb *inp, + int errno, + struct sctphdr *sh, + struct sockaddr *to, + struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + /* protection */ + if ((inp == NULL) || (stcb == NULL) || (net == NULL) || + (sh == NULL) || (to == NULL)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("sctp-notify, bad call\n"); + } +#endif /* SCTP_DEBUG */ + return; + } + /* First job is to verify the vtag matches what I would send */ + if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag)) { + return; + } + +/* FIX ME FIX ME PROTOPT i.e. no SCTP should ALWAYS be an ABORT */ + + if ((errno == EHOSTUNREACH) || /* Host is not reachable */ + (errno == EHOSTDOWN) || /* Host is down */ + (errno == ECONNREFUSED) || /* Host refused the connection, (not an abort?) */ + (errno == ENOPROTOOPT) /* SCTP is not present on host */ + ) { + /* + * Hmm reachablity problems we must examine closely. + * If its not reachable, we may have lost a network. + * Or if there is NO protocol at the other end named SCTP. + * well we consider it a OOTB abort. + */ + if ((errno == EHOSTUNREACH) || (errno == EHOSTDOWN)) { + if (net->dest_state & SCTP_ADDR_REACHABLE) { + /* Ok that destination is NOT reachable */ + net->dest_state &= ~SCTP_ADDR_REACHABLE; + net->dest_state |= SCTP_ADDR_NOT_REACHABLE; + net->error_count = net->failure_threshold + 1; + sctp_ulp_notify(SCTP_NOTIFY_INTERFACE_DOWN, + stcb, SCTP_FAILED_THRESHOLD, + (void *)net); + } + if (stcb) + SCTP_TCB_UNLOCK(stcb); + } else { + /* + * Here the peer is either playing tricks on us, + * including an address that belongs to someone who + * does not support SCTP OR was a userland + * implementation that shutdown and now is dead. In + * either case treat it like a OOTB abort with no TCB + */ + sctp_abort_notification(stcb, SCTP_PEER_FAULTY); + sctp_free_assoc(inp, stcb); + /* no need to unlock here, since the TCB is gone */ + } + } else { + /* Send all others to the app */ + if (inp->sctp_socket) { + SOCK_LOCK(inp->sctp_socket); + inp->sctp_socket->so_error = errno; + sctp_sowwakeup(inp, inp->sctp_socket); + SOCK_UNLOCK(inp->sctp_socket); + } + if (stcb) + SCTP_TCB_UNLOCK(stcb); + } +} + +#if defined(__FreeBSD__) || defined(__APPLE__) +void +#else +void * +#endif +sctp_ctlinput(cmd, sa, vip) + int cmd; + struct sockaddr *sa; + void *vip; +{ + struct ip *ip = vip; + struct sctphdr *sh; + int s; + + + if (sa->sa_family != AF_INET || + ((struct sockaddr_in *)sa)->sin_addr.s_addr == INADDR_ANY) { +#if defined(__FreeBSD__) || defined(__APPLE__) + return; +#else + return (NULL); +#endif + } + + if (PRC_IS_REDIRECT(cmd)) { + ip = 0; + } else if ((unsigned)cmd >= PRC_NCMDS || inetctlerrmap[cmd] == 0) { +#if defined(__FreeBSD__) || defined(__APPLE__) + return; +#else + return (NULL); +#endif + } + if (ip) { + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + struct sctp_nets *net; + struct sockaddr_in to, from; + + sh = (struct sctphdr *)((caddr_t)ip + (ip->ip_hl << 2)); + bzero(&to, sizeof(to)); + bzero(&from, sizeof(from)); + from.sin_family = to.sin_family = AF_INET; + from.sin_len = to.sin_len = sizeof(to); + from.sin_port = sh->src_port; + from.sin_addr = ip->ip_src; + to.sin_port = sh->dest_port; + to.sin_addr = ip->ip_dst; + + /* + * 'to' holds the dest of the packet that failed to be sent. + * 'from' holds our local endpoint address. + * Thus we reverse the to and the from in the lookup. + */ +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + stcb = sctp_findassociation_addr_sa((struct sockaddr *)&from, + (struct sockaddr *)&to, + &inp, &net, 1); + if (stcb != NULL && inp && (inp->sctp_socket != NULL)) { + if (cmd != PRC_MSGSIZE) { + int cm; + if (cmd == PRC_HOSTDEAD) { + cm = EHOSTUNREACH; + } else { + cm = inetctlerrmap[cmd]; + } + sctp_notify(inp, cm, sh, + (struct sockaddr *)&to, stcb, + net); + } else { + /* handle possible ICMP size messages */ + sctp_notify_mbuf(inp, stcb, net, ip, sh); + } + } else { +#if defined(__FreeBSD__) && __FreeBSD_version < 500000 + /* XXX must be fixed for 5.x and higher, leave for 4.x */ + if (PRC_IS_REDIRECT(cmd) && inp) { + in_rtchange((struct inpcb *)inp, + inetctlerrmap[cmd]); + } +#endif + if ((stcb == NULL) && (inp != NULL)) { + /* reduce ref-count */ + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + + } + splx(s); + } +#if defined(__FreeBSD__) || defined(__APPLE__) + return; +#else + return (NULL); +#endif +} + +#if defined(__FreeBSD__) +static int +sctp_getcred(SYSCTL_HANDLER_ARGS) +{ + struct sockaddr_in addrs[2]; + struct sctp_inpcb *inp; + struct sctp_nets *net; + struct sctp_tcb *stcb; + int error, s; + +#if __FreeBSD_version >= 500000 + error = suser(req->td); +#else + error = suser(req->p); +#endif + if (error) + return (error); + error = SYSCTL_IN(req, addrs, sizeof(addrs)); + if (error) + return (error); + + s = splnet(); + stcb = sctp_findassociation_addr_sa(sintosa(&addrs[0]), + sintosa(&addrs[1]), + &inp, &net, 1); + if (stcb == NULL || inp == NULL || inp->sctp_socket == NULL) { + if ((inp != NULL) && (stcb == NULL)) { + /* reduce ref-count */ + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + error = ENOENT; + goto out; + } + error = SYSCTL_OUT(req, inp->sctp_socket->so_cred, sizeof(struct ucred)); + SCTP_TCB_UNLOCK(stcb); + out: + splx(s); + return (error); +} + +SYSCTL_PROC(_net_inet_sctp, OID_AUTO, getcred, CTLTYPE_OPAQUE|CTLFLAG_RW, + 0, 0, sctp_getcred, "S,ucred", "Get the ucred of a SCTP connection"); +#endif /* #if defined(__FreeBSD__) */ + +/* + * sysctl definitions + */ +#if defined(__FreeBSD__) || defined (__APPLE__) + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, maxdgram, CTLFLAG_RW, + &sctp_sendspace, 0, "Maximum outgoing SCTP buffer size"); + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, recvspace, CTLFLAG_RW, + &sctp_recvspace, 0, "Maximum incoming SCTP buffer size"); + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, auto_asconf, CTLFLAG_RW, + &sctp_auto_asconf, 0, "Enable SCTP Auto-ASCONF"); + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, ecn_enable, CTLFLAG_RW, + &sctp_ecn, 0, "Enable SCTP ECN"); + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, ecn_nonce, CTLFLAG_RW, + &sctp_ecn_nonce, 0, "Enable SCTP ECN Nonce"); + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, strict_sacks, CTLFLAG_RW, + &sctp_strict_sacks, 0, "Enable SCTP Strict SACK checking"); + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, loopback_nocsum, CTLFLAG_RW, + &sctp_no_csum_on_loopback, 0, + "Enable NO Csum on packets sent on loopback"); + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, strict_init, CTLFLAG_RW, + &sctp_strict_init, 0, + "Enable strict INIT/INIT-ACK singleton enforcement"); + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, peer_chkoh, CTLFLAG_RW, + &sctp_peer_chunk_oh, 0, + "Amount to debit peers rwnd per chunk sent"); + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, maxburst, CTLFLAG_RW, + &sctp_max_burst_default, 0, + "Default max burst for sctp endpoints"); + +SYSCTL_INT(_net_inet_sctp, OID_AUTO, maxchunks, CTLFLAG_RW, + &sctp_max_chunks_on_queue, 0, + "Default max chunks on queue per asoc"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, delayed_sack_time, CTLFLAG_RW, + &sctp_delayed_sack_time_default, 0, + "Default delayed SACK timer in msec"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, heartbeat_interval, CTLFLAG_RW, + &sctp_heartbeat_interval_default, 0, + "Default heartbeat interval in msec"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, pmtu_raise_time, CTLFLAG_RW, + &sctp_pmtu_raise_time_default, 0, + "Default PMTU raise timer in sec"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, shutdown_guard_time, CTLFLAG_RW, + &sctp_shutdown_guard_time_default, 0, + "Default shutdown guard timer in sec"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, secret_lifetime, CTLFLAG_RW, + &sctp_secret_lifetime_default, 0, + "Default secret lifetime in sec"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, rto_max, CTLFLAG_RW, + &sctp_rto_max_default, 0, + "Default maximum retransmission timeout in msec"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, rto_min, CTLFLAG_RW, + &sctp_rto_min_default, 0, + "Default minimum retransmission timeout in msec"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, rto_initial, CTLFLAG_RW, + &sctp_rto_initial_default, 0, + "Default initial retransmission timeout in msec"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, init_rto_max, CTLFLAG_RW, + &sctp_init_rto_max_default, 0, + "Default maximum retransmission timeout during association setup in msec"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, valid_cookie_life, CTLFLAG_RW, + &sctp_valid_cookie_life_default, 0, + "Default cookie lifetime in sec"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, init_rtx_max, CTLFLAG_RW, + &sctp_init_rtx_max_default, 0, + "Default maximum number of retransmission for INIT chunks"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, assoc_rtx_max, CTLFLAG_RW, + &sctp_assoc_rtx_max_default, 0, + "Default maximum number of retransmissions per association"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, path_rtx_max, CTLFLAG_RW, + &sctp_path_rtx_max_default, 0, + "Default maximum of retransmissions per path"); + +SYSCTL_UINT(_net_inet_sctp, OID_AUTO, nr_outgoing_streams, CTLFLAG_RW, + &sctp_nr_outgoing_streams_default, 0, + "Default number of outgoing streams"); + +#ifdef SCTP_DEBUG +SYSCTL_INT(_net_inet_sctp, OID_AUTO, debug, CTLFLAG_RW, + &sctp_debug_on, 0, "Configure debug output"); +#endif /* SCTP_DEBUG */ +#endif + +static int +sctp_abort(struct socket *so) +{ + struct sctp_inpcb *inp; + int s; + + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) + return EINVAL; /* ??? possible? panic instead? */ + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + sctp_inpcb_free(inp, 1); + splx(s); + return 0; +} + +static int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp_attach(struct socket *so, int proto, struct thread *p) +#else +sctp_attach(struct socket *so, int proto, struct proc *p) +#endif +{ + struct sctp_inpcb *inp; + struct inpcb *ip_inp; + int s, error; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp != 0) { + splx(s); + return EINVAL; + } + error = soreserve(so, sctp_sendspace, sctp_recvspace); + if (error) { + splx(s); + return error; + } + error = sctp_inpcb_alloc(so); + if (error) { + splx(s); + return error; + } + inp = (struct sctp_inpcb *)so->so_pcb; + SCTP_INP_WLOCK(inp); + + inp->sctp_flags &= ~SCTP_PCB_FLAGS_BOUND_V6; /* I'm not v6! */ + ip_inp = &inp->ip_inp.inp; +#if defined(__FreeBSD__) || defined(__APPLE__) + ip_inp->inp_vflag |= INP_IPV4; + ip_inp->inp_ip_ttl = ip_defttl; +#else + inp->inp_vflag |= INP_IPV4; + inp->inp_ip_ttl = ip_defttl; +#endif + +#ifdef IPSEC +#if !(defined(__OpenBSD__) || defined(__APPLE__)) + error = ipsec_init_pcbpolicy(so, &ip_inp->inp_sp); + if (error != 0) { + sctp_inpcb_free(inp, 1); + return error; + } +#endif +#endif /*IPSEC*/ + SCTP_INP_WUNLOCK(inp); +#if defined(__NetBSD__) + so->so_send = sctp_sosend; +#endif + splx(s); + return 0; +} + +static int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp_bind(struct socket *so, struct sockaddr *addr, struct thread *p) +{ +#elif defined(__FreeBSD__) || defined(__APPLE__) +sctp_bind(struct socket *so, struct sockaddr *addr, struct proc *p) +{ +#else +sctp_bind(struct socket *so, struct mbuf *nam, struct proc *p) +{ + struct sockaddr *addr = nam ? mtod(nam, struct sockaddr *) : NULL; +#endif + struct sctp_inpcb *inp; + int s, error; + +#ifdef INET6 + if (addr && addr->sa_family != AF_INET) + /* must be a v4 address! */ + return EINVAL; +#endif /* INET6 */ + + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) + return EINVAL; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + error = sctp_inpcb_bind(so, addr, p); + splx(s); + return error; +} + + +static int +sctp_detach(struct socket *so) +{ + struct sctp_inpcb *inp; + int s; + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) + return EINVAL; +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) || + (so->so_rcv.sb_cc > 0)) { + sctp_inpcb_free(inp, 1); + } else { + sctp_inpcb_free(inp, 0); + } + splx(s); + return 0; +} + +int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct thread *p); +#else +sctp_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct proc *p); +#endif + +int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct thread *p) +{ +#else +sctp_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct proc *p) +{ +#endif + struct sctp_inpcb *inp; + int error; + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) { + if (control) { + sctp_m_freem(control); + control = NULL; + } + sctp_m_freem(m); + return EINVAL; + } + /* Got to have an to address if we are NOT a connected socket */ + if ((addr == NULL) && + ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) || + (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE)) + ) { + goto connected_type; + } else if (addr == NULL) { + error = EDESTADDRREQ; + sctp_m_freem(m); + if (control) { + sctp_m_freem(control); + control = NULL; + } + return (error); + } +#ifdef INET6 + if (addr->sa_family != AF_INET) { + /* must be a v4 address! */ + sctp_m_freem(m); + if (control) { + sctp_m_freem(control); + control = NULL; + } + error = EDESTADDRREQ; + return EINVAL; + } +#endif /* INET6 */ + connected_type: + /* now what about control */ + if (control) { + if (inp->control) { + printf("huh? control set?\n"); + sctp_m_freem(inp->control); + inp->control = NULL; + } + inp->control = control; + } + /* add it in possibly */ + if ((inp->pkt) && (inp->pkt->m_flags & M_PKTHDR)) { + struct mbuf *x; + int c_len; + + c_len = 0; + /* How big is it */ + for (x=m;x;x = x->m_next) { + c_len += x->m_len; + } + inp->pkt->m_pkthdr.len += c_len; + } + /* Place the data */ + if (inp->pkt) { + inp->pkt_last->m_next = m; + inp->pkt_last = m; + } else { + inp->pkt_last = inp->pkt = m; + } + if ( +#if defined (__FreeBSD__) || defined(__APPLE__) + /* FreeBSD uses a flag passed */ + ((flags & PRUS_MORETOCOME) == 0) +#elif defined( __NetBSD__) + /* NetBSD uses the so_state field */ + ((so->so_state & SS_MORETOCOME) == 0) +#else + 1 /* Open BSD does not have any "more to come" indication */ +#endif + ) { + /* + * note with the current version this code will only be used + * by OpenBSD-- NetBSD, FreeBSD, and MacOS have methods for + * re-defining sosend to use the sctp_sosend. One can + * optionally switch back to this code (by changing back the + * definitions) but this is not advisable. + */ + int ret; + ret = sctp_output(inp, inp->pkt, addr, inp->control, p, flags); + inp->pkt = NULL; + inp->control = NULL; + return (ret); + } else { + return (0); + } +} + +static int +sctp_disconnect(struct socket *so) +{ + struct sctp_inpcb *inp; + int s; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == NULL) { + splx(s); + return (ENOTCONN); + } + SCTP_INP_RLOCK(inp); + if (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { + if (LIST_EMPTY(&inp->sctp_asoc_list)) { + /* No connection */ + splx(s); + SCTP_INP_RUNLOCK(inp); + return (0); + } else { + int some_on_streamwheel = 0; + struct sctp_association *asoc; + struct sctp_tcb *stcb; + + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { + splx(s); + SCTP_INP_RUNLOCK(inp); + return (EINVAL); + } + asoc = &stcb->asoc; + SCTP_TCB_LOCK(stcb); + if (((so->so_options & SO_LINGER) && + (so->so_linger == 0)) || + (so->so_rcv.sb_cc > 0)) { + if (SCTP_GET_STATE(asoc) != + SCTP_STATE_COOKIE_WAIT) { + /* Left with Data unread */ + struct mbuf *err; + err = NULL; + MGET(err, M_DONTWAIT, MT_DATA); + if (err) { + /* Fill in the user initiated abort */ + struct sctp_paramhdr *ph; + ph = mtod(err, struct sctp_paramhdr *); + err->m_len = sizeof(struct sctp_paramhdr); + ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT); + ph->param_length = htons(err->m_len); + } + sctp_send_abort_tcb(stcb, err); + } + SCTP_INP_RUNLOCK(inp); + sctp_free_assoc(inp, stcb); + /* No unlock tcb assoc is gone */ + splx(s); + return (0); + } + if (!TAILQ_EMPTY(&asoc->out_wheel)) { + /* Check to see if some data queued */ + struct sctp_stream_out *outs; + TAILQ_FOREACH(outs, &asoc->out_wheel, + next_spoke) { + if (!TAILQ_EMPTY(&outs->outqueue)) { + some_on_streamwheel = 1; + break; + } + } + } + + if (TAILQ_EMPTY(&asoc->send_queue) && + TAILQ_EMPTY(&asoc->sent_queue) && + (some_on_streamwheel == 0)) { + /* there is nothing queued to send, so done */ + if ((SCTP_GET_STATE(asoc) != + SCTP_STATE_SHUTDOWN_SENT) && + (SCTP_GET_STATE(asoc) != + SCTP_STATE_SHUTDOWN_ACK_SENT)) { + /* only send SHUTDOWN 1st time thru */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("%s:%d sends a shutdown\n", + __FILE__, + __LINE__ + ); + } +#endif + sctp_send_shutdown(stcb, + stcb->asoc.primary_destination); + sctp_chunk_output(stcb->sctp_ep, stcb, 1); + asoc->state = SCTP_STATE_SHUTDOWN_SENT; + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, + stcb->sctp_ep, stcb, + asoc->primary_destination); + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, + stcb->sctp_ep, stcb, + asoc->primary_destination); + } + } else { + /* + * we still got (or just got) data to send, + * so set SHUTDOWN_PENDING + */ + /* + * XXX sockets draft says that MSG_EOF should + * be sent with no data. + * currently, we will allow user data to be + * sent first and move to SHUTDOWN-PENDING + */ + asoc->state |= SCTP_STATE_SHUTDOWN_PENDING; + } + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_RUNLOCK(inp); + splx(s); + return (0); + } + /* not reached */ + } else { + /* UDP model does not support this */ + SCTP_INP_RUNLOCK(inp); + splx(s); + return EOPNOTSUPP; + } +} + +int +sctp_shutdown(struct socket *so) +{ + struct sctp_inpcb *inp; + int s; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) { + splx(s); + return EINVAL; + } + SCTP_INP_RLOCK(inp); + /* For UDP model this is a invalid call */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) { + /* Restore the flags that the soshutdown took away. */ +#if defined(__FreeBSD__) && __FreeBSD_version >= 502115 + so->so_rcv.sb_state &= ~SBS_CANTRCVMORE; +#else + so->so_state &= ~SS_CANTRCVMORE; +#endif + /* This proc will wakeup for read and do nothing (I hope) */ + splx(s); + SCTP_INP_RUNLOCK(inp); + return (EOPNOTSUPP); + } + /* + * Ok if we reach here its the TCP model and it is either a SHUT_WR + * or SHUT_RDWR. This means we put the shutdown flag against it. + */ + { + int some_on_streamwheel = 0; + struct sctp_tcb *stcb; + struct sctp_association *asoc; + socantsendmore(so); + + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { + /* + * Ok we hit the case that the shutdown call was made + * after an abort or something. Nothing to do now. + */ + splx(s); + return (0); + } + SCTP_TCB_LOCK(stcb); + asoc = &stcb->asoc; + + if (!TAILQ_EMPTY(&asoc->out_wheel)) { + /* Check to see if some data queued */ + struct sctp_stream_out *outs; + TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) { + if (!TAILQ_EMPTY(&outs->outqueue)) { + some_on_streamwheel = 1; + break; + } + } + } + if (TAILQ_EMPTY(&asoc->send_queue) && + TAILQ_EMPTY(&asoc->sent_queue) && + (some_on_streamwheel == 0)) { + /* there is nothing queued to send, so I'm done... */ + if (SCTP_GET_STATE(asoc) != SCTP_STATE_SHUTDOWN_SENT) { + /* only send SHUTDOWN the first time through */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("%s:%d sends a shutdown\n", + __FILE__, + __LINE__ + ); + } +#endif + sctp_send_shutdown(stcb, + stcb->asoc.primary_destination); + sctp_chunk_output(stcb->sctp_ep, stcb, 1); + asoc->state = SCTP_STATE_SHUTDOWN_SENT; + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, + stcb->sctp_ep, stcb, + asoc->primary_destination); + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, + stcb->sctp_ep, stcb, + asoc->primary_destination); + } + } else { + /* + * we still got (or just got) data to send, so + * set SHUTDOWN_PENDING + */ + asoc->state |= SCTP_STATE_SHUTDOWN_PENDING; + } + SCTP_TCB_UNLOCK(stcb); + } + SCTP_INP_RUNLOCK(inp); + splx(s); + return 0; +} + +/* + * copies a "user" presentable address and removes embedded scope, etc. + * returns 0 on success, 1 on error + */ +static uint32_t +sctp_fill_user_address(struct sockaddr_storage *ss, struct sockaddr *sa) +{ + struct sockaddr_in6 lsa6; + sa = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)sa, + &lsa6); + memcpy(ss, sa, sa->sa_len); + return (0); +} + + +#if defined(__NetBSD__) || defined(__OpenBSD__) +/* + * On NetBSD and OpenBSD in6_sin_2_v4mapsin6() not used and not exported, + * so we have to export it here. + */ +void in6_sin_2_v4mapsin6 __P((struct sockaddr_in *sin, + struct sockaddr_in6 *sin6)); +#endif + +static int +sctp_fill_up_addresses(struct sctp_inpcb *inp, + struct sctp_tcb *stcb, + int limit, + struct sockaddr_storage *sas) +{ + struct ifnet *ifn; + struct ifaddr *ifa; + int loopback_scope, ipv4_local_scope, local_scope, site_scope, actual; + int ipv4_addr_legal, ipv6_addr_legal; + actual = 0; + if (limit <= 0) + return (actual); + + if (stcb) { + /* Turn on all the appropriate scope */ + loopback_scope = stcb->asoc.loopback_scope; + ipv4_local_scope = stcb->asoc.ipv4_local_scope; + local_scope = stcb->asoc.local_scope; + site_scope = stcb->asoc.site_scope; + } else { + /* Turn on ALL scope, since we look at the EP */ + loopback_scope = ipv4_local_scope = local_scope = + site_scope = 1; + } + ipv4_addr_legal = ipv6_addr_legal = 0; + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + ipv6_addr_legal = 1; + if ( +#if defined(__OpenBSD__) + (0) /* we always do dual bind */ +#elif defined (__NetBSD__) + (((struct in6pcb *)inp)->in6p_flags & IN6P_IPV6_V6ONLY) +#else + (((struct in6pcb *)inp)->inp_flags & IN6P_IPV6_V6ONLY) +#endif + == 0) { + ipv4_addr_legal = 1; + } + } else { + ipv4_addr_legal = 1; + } + + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + TAILQ_FOREACH(ifn, &ifnet, if_list) { + if ((loopback_scope == 0) && + (ifn->if_type == IFT_LOOP)) { + /* Skip loopback if loopback_scope not set */ + continue; + } + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (stcb) { + /* + * For the BOUND-ALL case, the list + * associated with a TCB is Always + * considered a reverse list.. i.e. + * it lists addresses that are NOT + * part of the association. If this + * is one of those we must skip it. + */ + if (sctp_is_addr_restricted(stcb, + ifa->ifa_addr)) { + continue; + } + } + if ((ifa->ifa_addr->sa_family == AF_INET) && + (ipv4_addr_legal)) { + struct sockaddr_in *sin; + sin = (struct sockaddr_in *)ifa->ifa_addr; + if (sin->sin_addr.s_addr == 0) { + /* we skip unspecifed addresses */ + continue; + } + if ((ipv4_local_scope == 0) && + (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) { + continue; + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) { + in6_sin_2_v4mapsin6(sin, (struct sockaddr_in6 *)sas); + ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport; + sas = (struct sockaddr_storage *)((caddr_t)sas + sizeof(struct sockaddr_in6)); + actual += sizeof(sizeof(struct sockaddr_in6)); + } else { + memcpy(sas, sin, sizeof(*sin)); + ((struct sockaddr_in *)sas)->sin_port = inp->sctp_lport; + sas = (struct sockaddr_storage *)((caddr_t)sas + sizeof(*sin)); + actual += sizeof(*sin); + } + if (actual >= limit) { + return (actual); + } + } else if ((ifa->ifa_addr->sa_family == AF_INET6) && + (ipv6_addr_legal)) { + struct sockaddr_in6 *sin6, lsa6; + sin6 = (struct sockaddr_in6 *)ifa->ifa_addr; + if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { + /* we skip unspecifed addresses */ + continue; + } + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { + if (local_scope == 0) + continue; + if (sin6->sin6_scope_id == 0) { + lsa6 = *sin6; + if (in6_recoverscope(&lsa6, + &lsa6.sin6_addr, + NULL)) + /* bad link local address */ + continue; + sin6 = &lsa6; + } + } + if ((site_scope == 0) && + (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) { + continue; + } + memcpy(sas, sin6, sizeof(*sin6)); + ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport; + sas = (struct sockaddr_storage *)((caddr_t)sas + sizeof(*sin6)); + actual += sizeof(*sin6); + if (actual >= limit) { + return (actual); + } + } + } + } + } else { + struct sctp_laddr *laddr; + /* + * If we have a TCB and we do NOT support ASCONF (it's + * turned off or otherwise) then the list is always the + * true list of addresses (the else case below). Otherwise + * the list on the association is a list of addresses that + * are NOT part of the association. + */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_DO_ASCONF) { + /* The list is a NEGATIVE list */ + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (stcb) { + if (sctp_is_addr_restricted(stcb, laddr->ifa->ifa_addr)) { + continue; + } + } + if (sctp_fill_user_address(sas, laddr->ifa->ifa_addr)) + continue; + + ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport; + sas = (struct sockaddr_storage *)((caddr_t)sas + + laddr->ifa->ifa_addr->sa_len); + actual += laddr->ifa->ifa_addr->sa_len; + if (actual >= limit) { + return (actual); + } + } + } else { + /* The list is a positive list if present */ + if (stcb) { + /* Must use the specific association list */ + LIST_FOREACH(laddr, &stcb->asoc.sctp_local_addr_list, + sctp_nxt_addr) { + if (sctp_fill_user_address(sas, + laddr->ifa->ifa_addr)) + continue; + ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport; + sas = (struct sockaddr_storage *)((caddr_t)sas + + laddr->ifa->ifa_addr->sa_len); + actual += laddr->ifa->ifa_addr->sa_len; + if (actual >= limit) { + return (actual); + } + } + } else { + /* No endpoint so use the endpoints individual list */ + LIST_FOREACH(laddr, &inp->sctp_addr_list, + sctp_nxt_addr) { + if (sctp_fill_user_address(sas, + laddr->ifa->ifa_addr)) + continue; + ((struct sockaddr_in6 *)sas)->sin6_port = inp->sctp_lport; + sas = (struct sockaddr_storage *)((caddr_t)sas + + laddr->ifa->ifa_addr->sa_len); + actual += laddr->ifa->ifa_addr->sa_len; + if (actual >= limit) { + return (actual); + } + } + } + } + } + return (actual); +} + +static int +sctp_count_max_addresses(struct sctp_inpcb *inp) +{ + int cnt = 0; + /* + * In both sub-set bound an bound_all cases we return the MAXIMUM + * number of addresses that you COULD get. In reality the sub-set + * bound may have an exclusion list for a given TCB OR in the + * bound-all case a TCB may NOT include the loopback or other + * addresses as well. + */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + struct ifnet *ifn; + struct ifaddr *ifa; + + TAILQ_FOREACH(ifn, &ifnet, if_list) { + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + /* Count them if they are the right type */ + if (ifa->ifa_addr->sa_family == AF_INET) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) + cnt += sizeof(struct sockaddr_in6); + else + cnt += sizeof(struct sockaddr_in); + + } else if (ifa->ifa_addr->sa_family == AF_INET6) + cnt += sizeof(struct sockaddr_in6); + } + } + } else { + struct sctp_laddr *laddr; + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa->ifa_addr->sa_family == AF_INET) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) + cnt += sizeof(struct sockaddr_in6); + else + cnt += sizeof(struct sockaddr_in); + + } else if (laddr->ifa->ifa_addr->sa_family == AF_INET6) + cnt += sizeof(struct sockaddr_in6); + } + } + return (cnt); +} + +static int +sctp_do_connect_x(struct socket *so, + struct sctp_inpcb *inp, + struct mbuf *m, +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + struct thread *p, +#else + struct proc *p, +#endif + int delay + ) +{ +#if defined(__NetBSD__) || defined(__OpenBSD__) + int s = splsoftnet(); +#else + int s = splnet(); +#endif + int error = 0; + struct sctp_tcb *stcb = NULL; + struct sockaddr *sa; + int num_v6=0, num_v4=0, *totaddrp, totaddr, i, incr, at; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Connectx called\n"); + } +#endif /* SCTP_DEBUG */ + + if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && + (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) { + /* We are already connected AND the TCP model */ + splx(s); + return (EADDRINUSE); + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + SCTP_INP_RUNLOCK(inp); + } + if (stcb) { + splx(s); + return (EALREADY); + + } + SCTP_ASOC_CREATE_LOCK(inp); + if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || + (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return (EFAULT); + } + + totaddrp = mtod(m, int *); + totaddr = *totaddrp; + sa = (struct sockaddr *)(totaddrp + 1); + at = incr = 0; + /* account and validate addresses */ + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + for (i = 0; i < totaddr; i++) { + if (sa->sa_family == AF_INET) { + num_v4++; + incr = sizeof(struct sockaddr_in); + } else if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)sa; + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + /* Must be non-mapped for connectx */ + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return EINVAL; + } + num_v6++; + incr = sizeof(struct sockaddr_in6); + } else { + totaddr = i; + break; + } + stcb = sctp_findassociation_ep_addr(&inp, sa, NULL, NULL, NULL); + if (stcb != NULL) { + /* Already have or am bring up an association */ + SCTP_ASOC_CREATE_UNLOCK(inp); + SCTP_TCB_UNLOCK(stcb); + splx(s); + return (EALREADY); + } + if ((at + incr) > m->m_len) { + totaddr = i; + break; + } + sa = (struct sockaddr *)((caddr_t)sa + incr); + } + sa = (struct sockaddr *)(totaddrp + 1); + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); +#ifdef INET6 + if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) && + (num_v6 > 0)) { + splx(s); + SCTP_INP_WUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + return (EINVAL); + } + if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) && + (num_v4 > 0)) { + struct in6pcb *inp6; + inp6 = (struct in6pcb *)inp; + if ( +#if defined(__OpenBSD__) + (0) /* we always do dual bind */ +#elif defined (__NetBSD__) + (inp6->in6p_flags & IN6P_IPV6_V6ONLY) +#else + (inp6->inp_flags & IN6P_IPV6_V6ONLY) +#endif + ) { + /* + * if IPV6_V6ONLY flag, ignore connections + * destined to a v4 addr or v4-mapped addr + */ + SCTP_INP_WUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return EINVAL; + } + } +#endif /* INET6 */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == + SCTP_PCB_FLAGS_UNBOUND) { + /* Bind a ephemeral port */ + SCTP_INP_WUNLOCK(inp); + error = sctp_inpcb_bind(so, NULL, p); + if (error) { + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return (error); + } + } else { + SCTP_INP_WUNLOCK(inp); + } + /* We are GOOD to go */ + stcb = sctp_aloc_assoc(inp, sa, 1, &error, 0); + if (stcb == NULL) { + /* Gak! no memory */ + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return (error); + } + /* move to second address */ + if (sa->sa_family == AF_INET) + sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in)); + else + sa = (struct sockaddr *)((caddr_t)sa + sizeof(struct sockaddr_in6)); + + for (i = 1; i < totaddr; i++) { + if (sa->sa_family == AF_INET) { + incr = sizeof(struct sockaddr_in); + if (sctp_add_remote_addr(stcb, sa, 0, 8)) { + /* assoc gone no un-lock */ + sctp_free_assoc(inp, stcb); + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return (ENOBUFS); + } + + } else if (sa->sa_family == AF_INET6) { + incr = sizeof(struct sockaddr_in6); + if (sctp_add_remote_addr(stcb, sa, 0, 8)) { + /* assoc gone no un-lock */ + sctp_free_assoc(inp, stcb); + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return (ENOBUFS); + } + } + sa = (struct sockaddr *)((caddr_t)sa + incr); + } + stcb->asoc.state = SCTP_STATE_COOKIE_WAIT; + if (delay) { + /* doing delayed connection */ + stcb->asoc.delayed_connection = 1; + sctp_timer_start(SCTP_TIMER_TYPE_INIT, inp, stcb, stcb->asoc.primary_destination); + } else { + SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); + sctp_send_initiate(inp, stcb); + } + SCTP_TCB_UNLOCK(stcb); + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { + stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; + /* Set the connected flag so we can queue data */ + soisconnecting(so); + } + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return error; +} + + +static int +sctp_optsget(struct socket *so, + int opt, + struct mbuf **mp, +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + struct thread *p +#else + struct proc *p +#endif + ) +{ + struct sctp_inpcb *inp; + struct mbuf *m; + int error, optval=0; + struct sctp_tcb *stcb = NULL; + + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) + return EINVAL; + error = 0; + + if (mp == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("optsget:MP is NULL EINVAL\n"); + } +#endif /* SCTP_DEBUG */ + return (EINVAL); + } + m = *mp; + if (m == NULL) { + /* Got to have a mbuf */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("Huh no mbuf\n"); + } +#endif /* SCTP_DEBUG */ + return (EINVAL); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) { + printf("optsget opt:%lxx sz:%u\n", (unsigned long)opt, + m->m_len); + } +#endif /* SCTP_DEBUG */ + + switch (opt) { + case SCTP_NODELAY: + case SCTP_AUTOCLOSE: + case SCTP_AUTO_ASCONF: + case SCTP_DISABLE_FRAGMENTS: + case SCTP_I_WANT_MAPPED_V4_ADDR: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) { + printf("other stuff\n"); + } +#endif /* SCTP_DEBUG */ + SCTP_INP_RLOCK(inp); + switch (opt) { + case SCTP_DISABLE_FRAGMENTS: + optval = inp->sctp_flags & SCTP_PCB_FLAGS_NO_FRAGMENT; + break; + case SCTP_I_WANT_MAPPED_V4_ADDR: + optval = inp->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4; + break; + case SCTP_AUTO_ASCONF: + optval = inp->sctp_flags & SCTP_PCB_FLAGS_AUTO_ASCONF; + break; + case SCTP_NODELAY: + optval = inp->sctp_flags & SCTP_PCB_FLAGS_NODELAY; + break; + case SCTP_AUTOCLOSE: + if ((inp->sctp_flags & SCTP_PCB_FLAGS_AUTOCLOSE) == + SCTP_PCB_FLAGS_AUTOCLOSE) + optval = inp->sctp_ep.auto_close_time; + else + optval = 0; + break; + + default: + error = ENOPROTOOPT; + } /* end switch (sopt->sopt_name) */ + if (opt != SCTP_AUTOCLOSE) { + /* make it an "on/off" value */ + optval = (optval != 0); + } + if ((size_t)m->m_len < sizeof(int)) { + error = EINVAL; + } + SCTP_INP_RUNLOCK(inp); + if (error == 0) { + /* return the option value */ + *mtod(m, int *) = optval; + m->m_len = sizeof(optval); + } + break; + case SCTP_GET_ASOC_ID_LIST: + { + struct sctp_assoc_ids *ids; + int cnt, at; + u_int16_t orig; + + if ((size_t)m->m_len < sizeof(struct sctp_assoc_ids)) { + error = EINVAL; + break; + } + ids = mtod(m, struct sctp_assoc_ids *); + cnt = 0; + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { + none_out_now: + ids->asls_numb_present = 0; + ids->asls_more_to_get = 0; + SCTP_INP_RUNLOCK(inp); + break; + } + orig = ids->asls_assoc_start; + stcb = LIST_FIRST(&inp->sctp_asoc_list); + while( orig ) { + stcb = LIST_NEXT(stcb , sctp_tcblist); + orig--; + cnt--; + } + if ( stcb == NULL) + goto none_out_now; + + at = 0; + ids->asls_numb_present = 0; + ids->asls_more_to_get = 1; + while(at < MAX_ASOC_IDS_RET) { + ids->asls_assoc_id[at] = sctp_get_associd(stcb); + at++; + ids->asls_numb_present++; + stcb = LIST_NEXT(stcb , sctp_tcblist); + if (stcb == NULL) { + ids->asls_more_to_get = 0; + break; + } + } + SCTP_INP_RUNLOCK(inp); + } + break; + case SCTP_GET_NONCE_VALUES: + { + struct sctp_get_nonce_values *gnv; + if ((size_t)m->m_len < sizeof(struct sctp_get_nonce_values)) { + error = EINVAL; + break; + } + gnv = mtod(m, struct sctp_get_nonce_values *); + stcb = sctp_findassociation_ep_asocid(inp, gnv->gn_assoc_id); + if (stcb == NULL) { + error = ENOTCONN; + } else { + gnv->gn_peers_tag = stcb->asoc.peer_vtag; + gnv->gn_local_tag = stcb->asoc.my_vtag; + SCTP_TCB_UNLOCK(stcb); + } + + } + break; + case SCTP_PEER_PUBLIC_KEY: + case SCTP_MY_PUBLIC_KEY: + case SCTP_SET_AUTH_CHUNKS: + case SCTP_SET_AUTH_SECRET: + /* not supported yet and until we refine the draft */ + error = EOPNOTSUPP; + break; + + case SCTP_DELAYED_ACK_TIME: + { + int32_t *tm; + if ((size_t)m->m_len < sizeof(int32_t)) { + error = EINVAL; + break; + } + tm = mtod(m, int32_t *); + + *tm = TICKS_TO_MSEC(inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV]); + } + break; + + case SCTP_GET_SNDBUF_USE: + if ((size_t)m->m_len < sizeof(struct sctp_sockstat)) { + error = EINVAL; + } else { + struct sctp_sockstat *ss; + struct sctp_tcb *stcb; + struct sctp_association *asoc; + ss = mtod(m, struct sctp_sockstat *); + stcb = sctp_findassociation_ep_asocid(inp, ss->ss_assoc_id); + if (stcb == NULL) { + error = ENOTCONN; + } else { + asoc = &stcb->asoc; + ss->ss_total_sndbuf = (u_int32_t)asoc->total_output_queue_size; + ss->ss_total_mbuf_sndbuf = (u_int32_t)asoc->total_output_mbuf_queue_size; + ss->ss_total_recv_buf = (u_int32_t)(asoc->size_on_delivery_queue + + asoc->size_on_reasm_queue + + asoc->size_on_all_streams); + SCTP_TCB_UNLOCK(stcb); + error = 0; + m->m_len = sizeof(struct sctp_sockstat); + } + } + break; + case SCTP_MAXBURST: + { + u_int8_t *burst; + burst = mtod(m, u_int8_t *); + SCTP_INP_RLOCK(inp); + *burst = inp->sctp_ep.max_burst; + SCTP_INP_RUNLOCK(inp); + m->m_len = sizeof(u_int8_t); + } + break; + case SCTP_MAXSEG: + { + u_int32_t *segsize; + sctp_assoc_t *assoc_id; + int ovh; + + if ((size_t)m->m_len < sizeof(u_int32_t)) { + error = EINVAL; + break; + } + if ((size_t)m->m_len < sizeof(sctp_assoc_t)) { + error = EINVAL; + break; + } + assoc_id = mtod(m, sctp_assoc_t *); + segsize = mtod(m, u_int32_t *); + m->m_len = sizeof(u_int32_t); + + if (((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && + (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) || + (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + struct sctp_tcb *stcb; + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) { + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + *segsize = sctp_get_frag_point(stcb, &stcb->asoc); + SCTP_TCB_UNLOCK(stcb); + } else { + SCTP_INP_RUNLOCK(inp); + goto skipit; + } + } else { + stcb = sctp_findassociation_ep_asocid(inp, *assoc_id); + if (stcb) { + *segsize = sctp_get_frag_point(stcb, &stcb->asoc); + SCTP_TCB_UNLOCK(stcb); + break; + } + skipit: + /* default is to get the max, if I + * can't calculate from an existing association. + */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + ovh = SCTP_MED_OVERHEAD; + } else { + ovh = SCTP_MED_V4_OVERHEAD; + } + *segsize = inp->sctp_frag_point - ovh; + } + } + break; + + case SCTP_SET_DEBUG_LEVEL: +#ifdef SCTP_DEBUG + { + u_int32_t *level; + if ((size_t)m->m_len < sizeof(u_int32_t)) { + error = EINVAL; + break; + } + level = mtod(m, u_int32_t *); + error = 0; + *level = sctp_debug_on; + m->m_len = sizeof(u_int32_t); + printf("Returning DEBUG LEVEL %x is set\n", + (u_int)sctp_debug_on); + } +#else /* SCTP_DEBUG */ + error = EOPNOTSUPP; +#endif + break; + case SCTP_GET_STAT_LOG: +#ifdef SCTP_STAT_LOGGING + error = sctp_fill_stat_log(m); +#else /* SCTP_DEBUG */ + error = EOPNOTSUPP; +#endif + break; + case SCTP_GET_PEGS: + { + u_int32_t *pt; + if ((size_t)m->m_len < sizeof(sctp_pegs)) { + error = EINVAL; + break; + } + pt = mtod(m, u_int32_t *); + memcpy(pt, sctp_pegs, sizeof(sctp_pegs)); + m->m_len = sizeof(sctp_pegs); + } + break; + case SCTP_EVENTS: + { + struct sctp_event_subscribe *events; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) { + printf("get events\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(struct sctp_event_subscribe)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) { + printf("M->M_LEN is %d not %d\n", + (int)m->m_len, + (int)sizeof(struct sctp_event_subscribe)); + } +#endif /* SCTP_DEBUG */ + error = EINVAL; + break; + } + events = mtod(m, struct sctp_event_subscribe *); + memset(events, 0, sizeof(events)); + SCTP_INP_RLOCK(inp); + if (inp->sctp_flags & SCTP_PCB_FLAGS_RECVDATAIOEVNT) + events->sctp_data_io_event = 1; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_RECVASSOCEVNT) + events->sctp_association_event = 1; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_RECVPADDREVNT) + events->sctp_address_event = 1; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_RECVSENDFAILEVNT) + events->sctp_send_failure_event = 1; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_RECVPEERERR) + events->sctp_peer_error_event = 1; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT) + events->sctp_shutdown_event = 1; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_PDAPIEVNT) + events->sctp_partial_delivery_event = 1; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_ADAPTIONEVNT) + events->sctp_adaption_layer_event = 1; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_STREAM_RESETEVNT) + events->sctp_stream_reset_events = 1; + SCTP_INP_RUNLOCK(inp); + m->m_len = sizeof(struct sctp_event_subscribe); + + } + break; + + case SCTP_ADAPTION_LAYER: + if ((size_t)m->m_len < sizeof(int)) { + error = EINVAL; + break; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("getadaption ind\n"); + } +#endif /* SCTP_DEBUG */ + SCTP_INP_RLOCK(inp); + *mtod(m, int *) = inp->sctp_ep.adaption_layer_indicator; + SCTP_INP_RUNLOCK(inp); + m->m_len = sizeof(int); + break; + case SCTP_SET_INITIAL_DBG_SEQ: + if ((size_t)m->m_len < sizeof(int)) { + error = EINVAL; + break; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("get initial dbg seq\n"); + } +#endif /* SCTP_DEBUG */ + SCTP_INP_RLOCK(inp); + *mtod(m, int *) = inp->sctp_ep.initial_sequence_debug; + SCTP_INP_RUNLOCK(inp); + m->m_len = sizeof(int); + break; + case SCTP_GET_LOCAL_ADDR_SIZE: + if ((size_t)m->m_len < sizeof(int)) { + error = EINVAL; + break; + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("get local sizes\n"); + } +#endif /* SCTP_DEBUG */ + SCTP_INP_RLOCK(inp); + *mtod(m, int *) = sctp_count_max_addresses(inp); + SCTP_INP_RUNLOCK(inp); + m->m_len = sizeof(int); + break; + case SCTP_GET_REMOTE_ADDR_SIZE: + { + sctp_assoc_t *assoc_id; + u_int32_t *val, sz; + struct sctp_nets *net; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("get remote size\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(sctp_assoc_t)) { +#ifdef SCTP_DEBUG + printf("m->m_len:%d not %d\n", + m->m_len, sizeof(sctp_assoc_t)); +#endif /* SCTP_DEBUG */ + error = EINVAL; + break; + } + stcb = NULL; + val = mtod(m, u_int32_t *); + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } + if (stcb == NULL) { + assoc_id = mtod(m, sctp_assoc_t *); + stcb = sctp_findassociation_ep_asocid(inp, *assoc_id); + } + + if (stcb == NULL) { + error = EINVAL; + break; + } + *val = 0; + sz = 0; + /* Count the sizes */ + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) || + (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET6)) { + sz += sizeof(struct sockaddr_in6); + } else if (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET) { + sz += sizeof(struct sockaddr_in); + } else { + /* huh */ + break; + } + } + SCTP_TCB_UNLOCK(stcb); + *val = sz; + m->m_len = sizeof(u_int32_t); + } + break; + case SCTP_GET_PEER_ADDRESSES: + /* + * Get the address information, an array + * is passed in to fill up we pack it. + */ + { + int cpsz, left; + struct sockaddr_storage *sas; + struct sctp_nets *net; + struct sctp_getaddresses *saddr; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("get peer addresses\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(struct sctp_getaddresses)) { + error = EINVAL; + break; + } + left = m->m_len - sizeof(struct sctp_getaddresses); + saddr = mtod(m, struct sctp_getaddresses *); + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, + saddr->sget_assoc_id); + if (stcb == NULL) { + error = ENOENT; + break; + } + m->m_len = sizeof(struct sctp_getaddresses); + sas = (struct sockaddr_storage *)&saddr->addr[0]; + + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) || + (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET6)) { + cpsz = sizeof(struct sockaddr_in6); + } else if (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET) { + cpsz = sizeof(struct sockaddr_in); + } else { + /* huh */ + break; + } + if (left < cpsz) { + /* not enough room. */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("Out of room\n"); + } +#endif /* SCTP_DEBUG */ + break; + } + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + (((struct sockaddr *)&net->ro._l_addr)->sa_family == AF_INET)) { + /* Must map the address */ + in6_sin_2_v4mapsin6((struct sockaddr_in *)&net->ro._l_addr, + (struct sockaddr_in6 *)sas); + } else { + memcpy(sas, &net->ro._l_addr, cpsz); + } + ((struct sockaddr_in *)sas)->sin_port = stcb->rport; + + sas = (struct sockaddr_storage *)((caddr_t)sas + cpsz); + left -= cpsz; + m->m_len += cpsz; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) { + printf("left now:%d mlen:%d\n", + left, m->m_len); + } +#endif /* SCTP_DEBUG */ + } + SCTP_TCB_UNLOCK(stcb); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("All done\n"); + } +#endif /* SCTP_DEBUG */ + break; + case SCTP_GET_LOCAL_ADDRESSES: + { + int limit, actual; + struct sockaddr_storage *sas; + struct sctp_getaddresses *saddr; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("get local addresses\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(struct sctp_getaddresses)) { + error = EINVAL; + break; + } + saddr = mtod(m, struct sctp_getaddresses *); + + if (saddr->sget_assoc_id) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, saddr->sget_assoc_id); + + } else { + stcb = NULL; + } + /* + * assure that the TCP model does not need a assoc id + * once connected. + */ + if ( (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) && + (stcb == NULL) ) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } + sas = (struct sockaddr_storage *)&saddr->addr[0]; + limit = m->m_len - sizeof(sctp_assoc_t); + actual = sctp_fill_up_addresses(inp, stcb, limit, sas); + SCTP_TCB_UNLOCK(stcb); + m->m_len = sizeof(struct sockaddr_storage) + actual; + } + break; + case SCTP_PEER_ADDR_PARAMS: + { + struct sctp_paddrparams *paddrp; + struct sctp_nets *net; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("Getting peer_addr_params\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(struct sctp_paddrparams)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) { + printf("Hmm m->m_len:%d is to small\n", + m->m_len); + } +#endif /* SCTP_DEBUG */ + error = EINVAL; + break; + } + paddrp = mtod(m, struct sctp_paddrparams *); + + net = NULL; + if (paddrp->spp_assoc_id) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("In spp_assoc_id find type\n"); + } +#endif /* SCTP_DEBUG */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) { + SCTP_TCB_LOCK(stcb); + net = sctp_findnet(stcb, (struct sockaddr *)&paddrp->spp_address); + } + SCTP_INP_RLOCK(inp); + } else { + stcb = sctp_findassociation_ep_asocid(inp, paddrp->spp_assoc_id); + } + if (stcb == NULL) { + error = ENOENT; + break; + } + } + if ( (stcb == NULL) && + ((((struct sockaddr *)&paddrp->spp_address)->sa_family == AF_INET) || + (((struct sockaddr *)&paddrp->spp_address)->sa_family == AF_INET6))) { + /* Lookup via address */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("Ok we need to lookup a param\n"); + } +#endif /* SCTP_DEBUG */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) { + SCTP_TCB_LOCK(stcb); + net = sctp_findnet(stcb, (struct sockaddr *)&paddrp->spp_address); + } + SCTP_INP_RUNLOCK(inp); + } else { + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + stcb = sctp_findassociation_ep_addr(&inp, + (struct sockaddr *)&paddrp->spp_address, + &net, NULL, NULL); + if (stcb == NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + } + + if (stcb == NULL) { + error = ENOENT; + break; + } + } else { + /* Effects the Endpoint */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("User wants EP level info\n"); + } +#endif /* SCTP_DEBUG */ + stcb = NULL; + } + if (stcb) { + /* Applys to the specific association */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("In TCB side\n"); + } +#endif /* SCTP_DEBUG */ + if (net) { + paddrp->spp_pathmaxrxt = net->failure_threshold; + } else { + /* No destination so return default value */ + paddrp->spp_pathmaxrxt = stcb->asoc.def_net_failure; + } + paddrp->spp_hbinterval = stcb->asoc.heart_beat_delay; + paddrp->spp_assoc_id = sctp_get_associd(stcb); + SCTP_TCB_UNLOCK(stcb); + } else { + /* Use endpoint defaults */ + SCTP_INP_RLOCK(inp); +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("In EP levle info\n"); + } +#endif /* SCTP_DEBUG */ + paddrp->spp_pathmaxrxt = inp->sctp_ep.def_net_failure; + paddrp->spp_hbinterval = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT]; + paddrp->spp_assoc_id = (sctp_assoc_t)0; + SCTP_INP_RUNLOCK(inp); + } + m->m_len = sizeof(struct sctp_paddrparams); + } + break; + case SCTP_GET_PEER_ADDR_INFO: + { + struct sctp_paddrinfo *paddri; + struct sctp_nets *net; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("GetPEER ADDR_INFO\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(struct sctp_paddrinfo)) { + error = EINVAL; + break; + } + paddri = mtod(m, struct sctp_paddrinfo *); + net = NULL; + if ((((struct sockaddr *)&paddri->spinfo_address)->sa_family == AF_INET) || + (((struct sockaddr *)&paddri->spinfo_address)->sa_family == AF_INET6)) { + /* Lookup via address */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) { + SCTP_TCB_LOCK(stcb); + net = sctp_findnet(stcb, + (struct sockaddr *)&paddri->spinfo_address); + } + SCTP_INP_RUNLOCK(inp); + } else { + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + stcb = sctp_findassociation_ep_addr(&inp, + (struct sockaddr *)&paddri->spinfo_address, + &net, NULL, NULL); + if (stcb == NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + } + + } else { + stcb = NULL; + } + if ((stcb == NULL) || (net == NULL)) { + error = ENOENT; + break; + } + m->m_len = sizeof(struct sctp_paddrinfo); + paddri->spinfo_state = net->dest_state & (SCTP_REACHABLE_MASK|SCTP_ADDR_NOHB); + paddri->spinfo_cwnd = net->cwnd; + paddri->spinfo_srtt = ((net->lastsa >> 2) + net->lastsv) >> 1; + paddri->spinfo_rto = net->RTO; + paddri->spinfo_assoc_id = sctp_get_associd(stcb); + SCTP_TCB_UNLOCK(stcb); + } + break; + case SCTP_PCB_STATUS: + { + struct sctp_pcbinfo *spcb; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("PCB status\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(struct sctp_pcbinfo)) { + error = EINVAL; + break; + } + spcb = mtod(m, struct sctp_pcbinfo *); + sctp_fill_pcbinfo(spcb); + m->m_len = sizeof(struct sctp_pcbinfo); + } + break; + case SCTP_STATUS: + { + struct sctp_nets *net; + struct sctp_status *sstat; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("SCTP status\n"); + } +#endif /* SCTP_DEBUG */ + + if ((size_t)m->m_len < sizeof(struct sctp_status)) { + error = EINVAL; + break; + } + sstat = mtod(m, struct sctp_status *); + + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, sstat->sstat_assoc_id); + + if (stcb == NULL) { + error = EINVAL; + break; + } + /* + * I think passing the state is fine since + * sctp_constants.h will be available to the user + * land. + */ + sstat->sstat_state = stcb->asoc.state; + sstat->sstat_rwnd = stcb->asoc.peers_rwnd; + sstat->sstat_unackdata = stcb->asoc.sent_queue_cnt; + /* + * We can't include chunks that have been passed + * to the socket layer. Only things in queue. + */ + sstat->sstat_penddata = (stcb->asoc.cnt_on_delivery_queue + + stcb->asoc.cnt_on_reasm_queue + + stcb->asoc.cnt_on_all_streams); + + + sstat->sstat_instrms = stcb->asoc.streamincnt; + sstat->sstat_outstrms = stcb->asoc.streamoutcnt; + sstat->sstat_fragmentation_point = sctp_get_frag_point(stcb, &stcb->asoc); + memcpy(&sstat->sstat_primary.spinfo_address, + &stcb->asoc.primary_destination->ro._l_addr, + ((struct sockaddr *)(&stcb->asoc.primary_destination->ro._l_addr))->sa_len); + net = stcb->asoc.primary_destination; + ((struct sockaddr_in *)&sstat->sstat_primary.spinfo_address)->sin_port = stcb->rport; + /* + * Again the user can get info from sctp_constants.h + * for what the state of the network is. + */ + sstat->sstat_primary.spinfo_state = net->dest_state & SCTP_REACHABLE_MASK; + sstat->sstat_primary.spinfo_cwnd = net->cwnd; + sstat->sstat_primary.spinfo_srtt = net->lastsa; + sstat->sstat_primary.spinfo_rto = net->RTO; + sstat->sstat_primary.spinfo_mtu = net->mtu; + sstat->sstat_primary.spinfo_assoc_id = sctp_get_associd(stcb); + SCTP_TCB_UNLOCK(stcb); + m->m_len = sizeof(*sstat); + } + break; + case SCTP_RTOINFO: + { + struct sctp_rtoinfo *srto; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("RTO Info\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(struct sctp_rtoinfo)) { + error = EINVAL; + break; + } + srto = mtod(m, struct sctp_rtoinfo *); + if (srto->srto_assoc_id == 0) { + /* Endpoint only please */ + SCTP_INP_RLOCK(inp); + srto->srto_initial = inp->sctp_ep.initial_rto; + srto->srto_max = inp->sctp_ep.sctp_maxrto; + srto->srto_min = inp->sctp_ep.sctp_minrto; + SCTP_INP_RUNLOCK(inp); + break; + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, srto->srto_assoc_id); + + if (stcb == NULL) { + error = EINVAL; + break; + } + srto->srto_initial = stcb->asoc.initial_rto; + srto->srto_max = stcb->asoc.maxrto; + srto->srto_min = stcb->asoc.minrto; + SCTP_TCB_UNLOCK(stcb); + m->m_len = sizeof(*srto); + } + break; + case SCTP_ASSOCINFO: + { + struct sctp_assocparams *sasoc; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("Associnfo\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(struct sctp_assocparams)) { + error = EINVAL; + break; + } + sasoc = mtod(m, struct sctp_assocparams *); + stcb = NULL; + + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } + if ((sasoc->sasoc_assoc_id) && (stcb == NULL)) { + stcb = sctp_findassociation_ep_asocid(inp, + sasoc->sasoc_assoc_id); + if (stcb == NULL) { + error = ENOENT; + break; + } + } else { + stcb = NULL; + } + + if (stcb) { + sasoc->sasoc_asocmaxrxt = stcb->asoc.max_send_times; + sasoc->sasoc_number_peer_destinations = stcb->asoc.numnets; + sasoc->sasoc_peer_rwnd = stcb->asoc.peers_rwnd; + sasoc->sasoc_local_rwnd = stcb->asoc.my_rwnd; + sasoc->sasoc_cookie_life = stcb->asoc.cookie_life; + SCTP_TCB_UNLOCK(stcb); + } else { + SCTP_INP_RLOCK(inp); + sasoc->sasoc_asocmaxrxt = inp->sctp_ep.max_send_times; + sasoc->sasoc_number_peer_destinations = 0; + sasoc->sasoc_peer_rwnd = 0; + sasoc->sasoc_local_rwnd = sbspace(&inp->sctp_socket->so_rcv); + sasoc->sasoc_cookie_life = inp->sctp_ep.def_cookie_life; + SCTP_INP_RUNLOCK(inp); + } + m->m_len = sizeof(*sasoc); + } + break; + case SCTP_DEFAULT_SEND_PARAM: + { + struct sctp_sndrcvinfo *s_info; + + if (m->m_len != sizeof(struct sctp_sndrcvinfo)) { + error = EINVAL; + break; + } + s_info = mtod(m, struct sctp_sndrcvinfo *); + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, s_info->sinfo_assoc_id); + + if (stcb == NULL) { + error = ENOENT; + break; + } + /* Copy it out */ + *s_info = stcb->asoc.def_send; + SCTP_TCB_UNLOCK(stcb); + m->m_len = sizeof(*s_info); + } + case SCTP_INITMSG: + { + struct sctp_initmsg *sinit; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("initmsg\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(struct sctp_initmsg)) { + error = EINVAL; + break; + } + sinit = mtod(m, struct sctp_initmsg *); + SCTP_INP_RLOCK(inp); + sinit->sinit_num_ostreams = inp->sctp_ep.pre_open_stream_count; + sinit->sinit_max_instreams = inp->sctp_ep.max_open_streams_intome; + sinit->sinit_max_attempts = inp->sctp_ep.max_init_times; + sinit->sinit_max_init_timeo = inp->sctp_ep.initial_init_rto_max; + SCTP_INP_RUNLOCK(inp); + m->m_len = sizeof(*sinit); + } + break; + case SCTP_PRIMARY_ADDR: + /* we allow a "get" operation on this */ + { + struct sctp_setprim *ssp; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("setprimary\n"); + } +#endif /* SCTP_DEBUG */ + if ((size_t)m->m_len < sizeof(struct sctp_setprim)) { + error = EINVAL; + break; + } + ssp = mtod(m, struct sctp_setprim *); + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else { + stcb = sctp_findassociation_ep_asocid(inp, ssp->ssp_assoc_id); + if (stcb == NULL) { + /* one last shot, try it by the address in */ + struct sctp_nets *net; + + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + stcb = sctp_findassociation_ep_addr(&inp, + (struct sockaddr *)&ssp->ssp_addr, + &net, NULL, NULL); + if (stcb == NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + } + if (stcb == NULL) { + error = EINVAL; + break; + } + } + /* simply copy out the sockaddr_storage... */ + memcpy(&ssp->ssp_addr, + &stcb->asoc.primary_destination->ro._l_addr, + ((struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr)->sa_len); + SCTP_TCB_UNLOCK(stcb); + m->m_len = sizeof(*ssp); + } + break; + default: + error = ENOPROTOOPT; + m->m_len = 0; + break; + } /* end switch (sopt->sopt_name) */ + return (error); +} + +static int +sctp_optsset(struct socket *so, + int opt, + struct mbuf **mp, +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + struct thread *p +#else + struct proc *p +#endif + ) +{ + int error, *mopt, set_opt, s; + struct mbuf *m; + struct sctp_tcb *stcb = NULL; + struct sctp_inpcb *inp; + + if (mp == NULL) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("optsset:MP is NULL EINVAL\n"); + } +#endif /* SCTP_DEBUG */ + return (EINVAL); + } + m = *mp; + if (m == NULL) + return (EINVAL); + + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) + return EINVAL; + + error = 0; + switch (opt) { + case SCTP_NODELAY: + case SCTP_AUTOCLOSE: + case SCTP_AUTO_ASCONF: + case SCTP_DISABLE_FRAGMENTS: + case SCTP_I_WANT_MAPPED_V4_ADDR: + /* copy in the option value */ + if ((size_t)m->m_len < sizeof(int)) { + error = EINVAL; + break; + } + mopt = mtod(m, int *); + set_opt = 0; + if (error) + break; + switch (opt) { + case SCTP_DISABLE_FRAGMENTS: + set_opt = SCTP_PCB_FLAGS_NO_FRAGMENT; + break; + case SCTP_AUTO_ASCONF: + set_opt = SCTP_PCB_FLAGS_AUTO_ASCONF; + break; + + case SCTP_I_WANT_MAPPED_V4_ADDR: + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + set_opt = SCTP_PCB_FLAGS_NEEDS_MAPPED_V4; + } else { + return (EINVAL); + } + break; + case SCTP_NODELAY: + set_opt = SCTP_PCB_FLAGS_NODELAY; + break; + case SCTP_AUTOCLOSE: + set_opt = SCTP_PCB_FLAGS_AUTOCLOSE; + /* + * The value is in ticks. + * Note this does not effect old associations, only + * new ones. + */ + inp->sctp_ep.auto_close_time = (*mopt * hz); + break; + } + SCTP_INP_WLOCK(inp); + if (*mopt != 0) { + inp->sctp_flags |= set_opt; + } else { + inp->sctp_flags &= ~set_opt; + } + SCTP_INP_WUNLOCK(inp); + break; + case SCTP_MY_PUBLIC_KEY: /* set my public key */ + case SCTP_SET_AUTH_CHUNKS: /* set the authenticated chunks required */ + case SCTP_SET_AUTH_SECRET: /* set the actual secret for the endpoint */ + /* not supported yet and until we refine the draft */ + error = EOPNOTSUPP; + break; + + case SCTP_CLR_STAT_LOG: +#ifdef SCTP_STAT_LOGGING + sctp_clr_stat_log(); +#else + error = EOPNOTSUPP; +#endif + break; + case SCTP_DELAYED_ACK_TIME: + { + int32_t *tm; + if ((size_t)m->m_len < sizeof(int32_t)) { + error = EINVAL; + break; + } + tm = mtod(m, int32_t *); + + if ((*tm < 10) || (*tm > 500)) { + /* can't be smaller than 10ms */ + /* MUST NOT be larger than 500ms */ + error = EINVAL; + break; + } + inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV] = MSEC_TO_TICKS(*tm); + } + break; + case SCTP_RESET_STREAMS: + { + struct sctp_stream_reset *strrst; + uint8_t two_way, not_peer; + + if ((size_t)m->m_len < sizeof(struct sctp_stream_reset)) { + error = EINVAL; + break; + } + strrst = mtod(m, struct sctp_stream_reset *); + + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, strrst->strrst_assoc_id); + if (stcb == NULL) { + error = ENOENT; + break; + } + if (stcb->asoc.peer_supports_strreset == 0) { + /* Peer does not support it, + * we return protocol not supported since + * this is true for this feature and this + * peer, not the socket request in general. + */ + error = EPROTONOSUPPORT; + SCTP_TCB_UNLOCK(stcb); + break; + } + +/* Having re-thought this code I added as I write the I-D there + * is NO need for it. The peer, if we are requesting a stream-reset + * will send a request to us but will itself do what we do, take + * and copy off the "reset information" we send and queue TSN's + * larger than the send-next in our response message. Thus they + * will handle it. + */ +/* if (stcb->asoc.sending_seq != (stcb->asoc.last_acked_seq + 1)) {*/ + /* Must have all sending data ack'd before we + * start this procedure. This is a bit restrictive + * and we SHOULD work on changing this so ONLY the + * streams being RESET get held up. So, a reset-all + * would require this.. but a reset specific just + * needs to be sure that the ones being reset have + * nothing on the send_queue. For now we will + * skip this more detailed method and do a course + * way.. i.e. nothing pending ... for future FIX ME! + */ +/* error = EBUSY;*/ +/* break;*/ +/* }*/ + + if (stcb->asoc.stream_reset_outstanding) { + error = EALREADY; + SCTP_TCB_UNLOCK(stcb); + break; + } + if (strrst->strrst_flags == SCTP_RESET_LOCAL_RECV) { + two_way = 0; + not_peer = 0; + } else if (strrst->strrst_flags == SCTP_RESET_LOCAL_SEND) { + two_way = 1; + not_peer = 1; + } else if (strrst->strrst_flags == SCTP_RESET_BOTH) { + two_way = 1; + not_peer = 0; + } else { + error = EINVAL; + SCTP_TCB_UNLOCK(stcb); + break; + } + sctp_send_str_reset_req(stcb, strrst->strrst_num_streams, + strrst->strrst_list, two_way, not_peer); +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + sctp_chunk_output(inp, stcb, 12); + SCTP_TCB_UNLOCK(stcb); + splx(s); + + } + break; + case SCTP_RESET_PEGS: + memset(sctp_pegs, 0, sizeof(sctp_pegs)); + error = 0; + break; + case SCTP_CONNECT_X: + if ((size_t)m->m_len < (sizeof(int) + sizeof(struct sockaddr_in))) { + error = EINVAL; + break; + } + error = sctp_do_connect_x(so, inp, m, p, 0); + break; + + case SCTP_CONNECT_X_DELAYED: + if ((size_t)m->m_len < (sizeof(int) + sizeof(struct sockaddr_in))) { + error = EINVAL; + break; + } + error = sctp_do_connect_x(so, inp, m, p, 1); + break; + + case SCTP_CONNECT_X_COMPLETE: + { + struct sockaddr *sa; + struct sctp_nets *net; + if ((size_t)m->m_len < sizeof(struct sockaddr_in)) { + error = EINVAL; + break; + } + sa = mtod(m, struct sockaddr *); + /* find tcb */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) { + SCTP_TCB_LOCK(stcb); + net = sctp_findnet(stcb, sa); + } + SCTP_INP_RUNLOCK(inp); + } else { + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + stcb = sctp_findassociation_ep_addr(&inp, sa, &net, NULL, NULL); + if (stcb == NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + } + + if (stcb == NULL) { + error = ENOENT; + break; + } + if (stcb->asoc.delayed_connection == 1) { + stcb->asoc.delayed_connection = 0; + SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); + sctp_timer_stop(SCTP_TIMER_TYPE_INIT, inp, stcb, stcb->asoc.primary_destination); + sctp_send_initiate(inp, stcb); + } else { + /* already expired or did not use delayed connectx */ + error = EALREADY; + } + SCTP_TCB_UNLOCK(stcb); + } + break; + case SCTP_MAXBURST: + { + u_int8_t *burst; + SCTP_INP_WLOCK(inp); + burst = mtod(m, u_int8_t *); + if (*burst) { + inp->sctp_ep.max_burst = *burst; + } + SCTP_INP_WUNLOCK(inp); + } + break; + case SCTP_MAXSEG: + { + u_int32_t *segsize; + int ovh; + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + ovh = SCTP_MED_OVERHEAD; + } else { + ovh = SCTP_MED_V4_OVERHEAD; + } + segsize = mtod(m, u_int32_t *); + if (*segsize < 1) { + error = EINVAL; + break; + } + SCTP_INP_WLOCK(inp); + inp->sctp_frag_point = (*segsize+ovh); + if (inp->sctp_frag_point < MHLEN) { + inp->sctp_frag_point = MHLEN; + } + SCTP_INP_WUNLOCK(inp); + } + break; + case SCTP_SET_DEBUG_LEVEL: +#ifdef SCTP_DEBUG + { + u_int32_t *level; + if ((size_t)m->m_len < sizeof(u_int32_t)) { + error = EINVAL; + break; + } + level = mtod(m, u_int32_t *); + error = 0; + sctp_debug_on = (*level & (SCTP_DEBUG_ALL | + SCTP_DEBUG_NOISY)); + printf("SETTING DEBUG LEVEL to %x\n", + (u_int)sctp_debug_on); + + } +#else + error = EOPNOTSUPP; +#endif /* SCTP_DEBUG */ + break; + case SCTP_EVENTS: + { + struct sctp_event_subscribe *events; + if ((size_t)m->m_len < sizeof(struct sctp_event_subscribe)) { + error = EINVAL; + break; + } + SCTP_INP_WLOCK(inp); + events = mtod(m, struct sctp_event_subscribe *); + if (events->sctp_data_io_event) { + inp->sctp_flags |= SCTP_PCB_FLAGS_RECVDATAIOEVNT; + } else { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_RECVDATAIOEVNT; + } + + if (events->sctp_association_event) { + inp->sctp_flags |= SCTP_PCB_FLAGS_RECVASSOCEVNT; + } else { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_RECVASSOCEVNT; + } + + if (events->sctp_address_event) { + inp->sctp_flags |= SCTP_PCB_FLAGS_RECVPADDREVNT; + } else { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_RECVPADDREVNT; + } + + if (events->sctp_send_failure_event) { + inp->sctp_flags |= SCTP_PCB_FLAGS_RECVSENDFAILEVNT; + } else { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_RECVSENDFAILEVNT; + } + + if (events->sctp_peer_error_event) { + inp->sctp_flags |= SCTP_PCB_FLAGS_RECVPEERERR; + } else { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_RECVPEERERR; + } + + if (events->sctp_shutdown_event) { + inp->sctp_flags |= SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT; + } else { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT; + } + + if (events->sctp_partial_delivery_event) { + inp->sctp_flags |= SCTP_PCB_FLAGS_PDAPIEVNT; + } else { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_PDAPIEVNT; + } + + if (events->sctp_adaption_layer_event) { + inp->sctp_flags |= SCTP_PCB_FLAGS_ADAPTIONEVNT; + } else { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_ADAPTIONEVNT; + } + + if (events->sctp_stream_reset_events) { + inp->sctp_flags |= SCTP_PCB_FLAGS_STREAM_RESETEVNT; + } else { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_STREAM_RESETEVNT; + } + SCTP_INP_WUNLOCK(inp); + } + break; + + case SCTP_ADAPTION_LAYER: + { + struct sctp_setadaption *adap_bits; + if ((size_t)m->m_len < sizeof(struct sctp_setadaption)) { + error = EINVAL; + break; + } + SCTP_INP_WLOCK(inp); + adap_bits = mtod(m, struct sctp_setadaption *); + inp->sctp_ep.adaption_layer_indicator = adap_bits->ssb_adaption_ind; + SCTP_INP_WUNLOCK(inp); + } + break; + case SCTP_SET_INITIAL_DBG_SEQ: + { + u_int32_t *vvv; + if ((size_t)m->m_len < sizeof(u_int32_t)) { + error = EINVAL; + break; + } + SCTP_INP_WLOCK(inp); + vvv = mtod(m, u_int32_t *); + inp->sctp_ep.initial_sequence_debug = *vvv; + SCTP_INP_WUNLOCK(inp); + } + break; + case SCTP_DEFAULT_SEND_PARAM: + { + struct sctp_sndrcvinfo *s_info; + + if (m->m_len != sizeof(struct sctp_sndrcvinfo)) { + error = EINVAL; + break; + } + s_info = mtod(m, struct sctp_sndrcvinfo *); + + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, s_info->sinfo_assoc_id); + + if (stcb == NULL) { + error = ENOENT; + break; + } + /* Validate things */ + if (s_info->sinfo_stream > stcb->asoc.streamoutcnt) { + SCTP_TCB_UNLOCK(stcb); + error = EINVAL; + break; + } + /* Mask off the flags that are allowed */ + s_info->sinfo_flags = (s_info->sinfo_flags & + (MSG_UNORDERED | MSG_ADDR_OVER | + MSG_PR_SCTP_TTL | MSG_PR_SCTP_BUF)); + /* Copy it in */ + stcb->asoc.def_send = *s_info; + SCTP_TCB_UNLOCK(stcb); + } + break; + case SCTP_PEER_ADDR_PARAMS: + { + struct sctp_paddrparams *paddrp; + struct sctp_nets *net; + if ((size_t)m->m_len < sizeof(struct sctp_paddrparams)) { + error = EINVAL; + break; + } + paddrp = mtod(m, struct sctp_paddrparams *); + net = NULL; + if (paddrp->spp_assoc_id) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) { + SCTP_TCB_LOCK(stcb); + net = sctp_findnet(stcb, (struct sockaddr *)&paddrp->spp_address); + } + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, paddrp->spp_assoc_id); + if (stcb == NULL) { + error = ENOENT; + break; + } + + } + if ((stcb == NULL) && + ((((struct sockaddr *)&paddrp->spp_address)->sa_family == AF_INET) || + (((struct sockaddr *)&paddrp->spp_address)->sa_family == AF_INET6))) { + /* Lookup via address */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) { + SCTP_TCB_LOCK(stcb); + net = sctp_findnet(stcb, + (struct sockaddr *)&paddrp->spp_address); + } + SCTP_INP_RUNLOCK(inp); + } else { + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + stcb = sctp_findassociation_ep_addr(&inp, + (struct sockaddr *)&paddrp->spp_address, + &net, NULL, NULL); + if (stcb == NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + } + } else { + /* Effects the Endpoint */ + stcb = NULL; + } + if (stcb) { + /* Applies to the specific association */ + if (paddrp->spp_pathmaxrxt) { + if (net) { + if (paddrp->spp_pathmaxrxt) + net->failure_threshold = paddrp->spp_pathmaxrxt; + } else { + if (paddrp->spp_pathmaxrxt) + stcb->asoc.def_net_failure = paddrp->spp_pathmaxrxt; + } + } + if ((paddrp->spp_hbinterval != 0) && (paddrp->spp_hbinterval != 0xffffffff)) { + /* Just a set */ + int old; + if (net) { + net->dest_state &= ~SCTP_ADDR_NOHB; + } else { + old = stcb->asoc.heart_beat_delay; + stcb->asoc.heart_beat_delay = paddrp->spp_hbinterval; + if (old == 0) { + /* Turn back on the timer */ + sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net); + } + } + } else if (paddrp->spp_hbinterval == 0xffffffff) { + /* on demand HB */ + sctp_send_hb(stcb, 1, net); + } else { + if (net == NULL) { + /* off on association */ + if (stcb->asoc.heart_beat_delay) { + int cnt_of_unconf = 0; + struct sctp_nets *lnet; + TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { + if (lnet->dest_state & SCTP_ADDR_UNCONFIRMED) { + cnt_of_unconf++; + } + } + /* stop the timer ONLY if we have no unconfirmed addresses + */ + if (cnt_of_unconf == 0) + sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net); + } + stcb->asoc.heart_beat_delay = 0; + } else { + net->dest_state |= SCTP_ADDR_NOHB; + } + } + SCTP_TCB_UNLOCK(stcb); + } else { + /* Use endpoint defaults */ + SCTP_INP_WLOCK(inp); + if (paddrp->spp_pathmaxrxt) + inp->sctp_ep.def_net_failure = paddrp->spp_pathmaxrxt; + if (paddrp->spp_hbinterval != SCTP_ISSUE_HB) + inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT] = paddrp->spp_hbinterval; + SCTP_INP_WUNLOCK(inp); + } + } + break; + case SCTP_RTOINFO: + { + struct sctp_rtoinfo *srto; + if ((size_t)m->m_len < sizeof(struct sctp_rtoinfo)) { + error = EINVAL; + break; + } + srto = mtod(m, struct sctp_rtoinfo *); + if (srto->srto_assoc_id == 0) { + SCTP_INP_WLOCK(inp); + /* If we have a null asoc, its default for the endpoint */ + if (srto->srto_initial > 10) + inp->sctp_ep.initial_rto = srto->srto_initial; + if (srto->srto_max > 10) + inp->sctp_ep.sctp_maxrto = srto->srto_max; + if (srto->srto_min > 10) + inp->sctp_ep.sctp_minrto = srto->srto_min; + SCTP_INP_WUNLOCK(inp); + break; + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, srto->srto_assoc_id); + if (stcb == NULL) { + error = EINVAL; + break; + } + /* Set in ms we hope :-) */ + if (srto->srto_initial > 10) + stcb->asoc.initial_rto = srto->srto_initial; + if (srto->srto_max > 10) + stcb->asoc.maxrto = srto->srto_max; + if (srto->srto_min > 10) + stcb->asoc.minrto = srto->srto_min; + SCTP_TCB_UNLOCK(stcb); + } + break; + case SCTP_ASSOCINFO: + { + struct sctp_assocparams *sasoc; + + if ((size_t)m->m_len < sizeof(struct sctp_assocparams)) { + error = EINVAL; + break; + } + sasoc = mtod(m, struct sctp_assocparams *); + if (sasoc->sasoc_assoc_id) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, + sasoc->sasoc_assoc_id); + if (stcb == NULL) { + error = ENOENT; + break; + } + + } else { + stcb = NULL; + } + if (stcb) { + if (sasoc->sasoc_asocmaxrxt) + stcb->asoc.max_send_times = sasoc->sasoc_asocmaxrxt; + sasoc->sasoc_number_peer_destinations = stcb->asoc.numnets; + sasoc->sasoc_peer_rwnd = 0; + sasoc->sasoc_local_rwnd = 0; + if (stcb->asoc.cookie_life) + stcb->asoc.cookie_life = sasoc->sasoc_cookie_life; + SCTP_TCB_UNLOCK(stcb); + } else { + SCTP_INP_WLOCK(inp); + if (sasoc->sasoc_asocmaxrxt) + inp->sctp_ep.max_send_times = sasoc->sasoc_asocmaxrxt; + sasoc->sasoc_number_peer_destinations = 0; + sasoc->sasoc_peer_rwnd = 0; + sasoc->sasoc_local_rwnd = 0; + if (sasoc->sasoc_cookie_life) + inp->sctp_ep.def_cookie_life = sasoc->sasoc_cookie_life; + SCTP_INP_WUNLOCK(inp); + } + } + break; + case SCTP_INITMSG: + { + struct sctp_initmsg *sinit; + + if ((size_t)m->m_len < sizeof(struct sctp_initmsg)) { + error = EINVAL; + break; + } + sinit = mtod(m, struct sctp_initmsg *); + SCTP_INP_WLOCK(inp); + if (sinit->sinit_num_ostreams) + inp->sctp_ep.pre_open_stream_count = sinit->sinit_num_ostreams; + + if (sinit->sinit_max_instreams) + inp->sctp_ep.max_open_streams_intome = sinit->sinit_max_instreams; + + if (sinit->sinit_max_attempts) + inp->sctp_ep.max_init_times = sinit->sinit_max_attempts; + + if (sinit->sinit_max_init_timeo > 10) + /* We must be at least a 100ms (we set in ticks) */ + inp->sctp_ep.initial_init_rto_max = sinit->sinit_max_init_timeo; + SCTP_INP_WUNLOCK(inp); + } + break; + case SCTP_PRIMARY_ADDR: + { + struct sctp_setprim *spa; + struct sctp_nets *net, *lnet; + if ((size_t)m->m_len < sizeof(struct sctp_setprim)) { + error = EINVAL; + break; + } + spa = mtod(m, struct sctp_setprim *); + + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) { + SCTP_TCB_LOCK(stcb); + } else { + error = EINVAL; + break; + } + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, spa->ssp_assoc_id); + if (stcb == NULL) { + /* One last shot */ + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + stcb = sctp_findassociation_ep_addr(&inp, + (struct sockaddr *)&spa->ssp_addr, + &net, NULL, NULL); + if (stcb == NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + error = EINVAL; + break; + } + } else { + /* find the net, associd or connected lookup type */ + net = sctp_findnet(stcb, (struct sockaddr *)&spa->ssp_addr); + if (net == NULL) { + SCTP_TCB_UNLOCK(stcb); + error = EINVAL; + break; + } + } + if ((net != stcb->asoc.primary_destination) && + (!(net->dest_state & SCTP_ADDR_UNCONFIRMED))) { + /* Ok we need to set it */ + lnet = stcb->asoc.primary_destination; + lnet->next_tsn_at_change = net->next_tsn_at_change = stcb->asoc.sending_seq; + if (sctp_set_primary_addr(stcb, + (struct sockaddr *)NULL, + net) == 0) { + if (net->dest_state & SCTP_ADDR_SWITCH_PRIMARY) { + net->dest_state |= SCTP_ADDR_DOUBLE_SWITCH; + } + net->dest_state |= SCTP_ADDR_SWITCH_PRIMARY; + } + } + SCTP_TCB_UNLOCK(stcb); + } + break; + + case SCTP_SET_PEER_PRIMARY_ADDR: + { + struct sctp_setpeerprim *sspp; + if ((size_t)m->m_len < sizeof(struct sctp_setpeerprim)) { + error = EINVAL; + break; + } + sspp = mtod(m, struct sctp_setpeerprim *); + + + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_RUNLOCK(inp); + } else + stcb = sctp_findassociation_ep_asocid(inp, sspp->sspp_assoc_id); + if (stcb == NULL) { + error = EINVAL; + break; + } + if (sctp_set_primary_ip_address_sa(stcb, (struct sockaddr *)&sspp->sspp_addr) != 0) { + error = EINVAL; + } + SCTP_TCB_UNLOCK(stcb); + } + break; + case SCTP_BINDX_ADD_ADDR: + { + struct sctp_getaddresses *addrs; + struct sockaddr *addr_touse; + struct sockaddr_in sin; + /* see if we're bound all already! */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + error = EINVAL; + break; + } + if ((size_t)m->m_len < sizeof(struct sctp_getaddresses)) { + error = EINVAL; + break; + } + addrs = mtod(m, struct sctp_getaddresses *); + addr_touse = addrs->addr; + if (addrs->addr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)addr_touse; + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + in6_sin6_2_sin(&sin, sin6); + addr_touse = (struct sockaddr *)&sin; + } + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) { + if (p == NULL) { + /* Can't get proc for Net/Open BSD */ + error = EINVAL; + break; + } + error = sctp_inpcb_bind(so, addr_touse, p); + break; + } + /* No locks required here since bind and mgmt_ep_sa all + * do their own locking. If we do something for the FIX: + * below we may need to lock in that case. + */ + if (addrs->sget_assoc_id == 0) { + /* add the address */ + struct sctp_inpcb *lep; + ((struct sockaddr_in *)addr_touse)->sin_port = inp->sctp_lport; + lep = sctp_pcb_findep(addr_touse, 1, 0); + if (lep != NULL) { + /* We must decrement the refcount + * since we have the ep already and + * are binding. No remove going on + * here. + */ + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + if (lep == inp) { + /* already bound to it.. ok */ + break; + } else if (lep == NULL) { + ((struct sockaddr_in *)addr_touse)->sin_port = 0; + error = sctp_addr_mgmt_ep_sa(inp, addr_touse, + SCTP_ADD_IP_ADDRESS); + } else { + error = EADDRNOTAVAIL; + } + if (error) + break; + + } else { + /* FIX: decide whether we allow assoc based bindx */ + } + } + break; + case SCTP_BINDX_REM_ADDR: + { + struct sctp_getaddresses *addrs; + struct sockaddr *addr_touse; + struct sockaddr_in sin; + /* see if we're bound all already! */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + error = EINVAL; + break; + } + if ((size_t)m->m_len < sizeof(struct sctp_getaddresses)) { + error = EINVAL; + break; + } + addrs = mtod(m, struct sctp_getaddresses *); + addr_touse = addrs->addr; + if (addrs->addr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)addr_touse; + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + in6_sin6_2_sin(&sin, sin6); + addr_touse = (struct sockaddr *)&sin; + } + } + /* No lock required mgmt_ep_sa does its own locking. If + * the FIX: below is ever changed we may need to + * lock before calling association level binding. + */ + if (addrs->sget_assoc_id == 0) { + /* delete the address */ + sctp_addr_mgmt_ep_sa(inp, addr_touse, + SCTP_DEL_IP_ADDRESS); + } else { + /* FIX: decide whether we allow assoc based bindx */ + } + } + break; + default: + error = ENOPROTOOPT; + break; + } /* end switch (opt) */ + return (error); +} + + +#if defined(__FreeBSD__) || defined(__APPLE__) +int +sctp_ctloutput(struct socket *so, struct sockopt *sopt) +{ + struct mbuf *m = NULL; + struct sctp_inpcb *inp; + int s, error; + + inp = (struct sctp_inpcb *)so->so_pcb; +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + if (inp == 0) { + splx(s); + /* I made the same as TCP since we are not setup? */ + return (ECONNRESET); + } + if (sopt->sopt_level != IPPROTO_SCTP) { + /* wrong proto level... send back up to IP */ +#ifdef INET6 + if (INP_CHECK_SOCKAF(so, AF_INET6)) + error = ip6_ctloutput(so, sopt); + else +#endif /* INET6 */ + error = ip_ctloutput(so, sopt); + splx(s); + return (error); + } + if (sopt->sopt_valsize > MCLBYTES) { + /* + * Restrict us down to a cluster size, that's all we can + * pass either way... + */ + sopt->sopt_valsize = MCLBYTES; + } + if (sopt->sopt_valsize) { + + m = m_get(M_WAIT, MT_DATA); + if (sopt->sopt_valsize > MLEN) { + MCLGET(m, M_DONTWAIT); + if ((m->m_flags & M_EXT) == 0) { + sctp_m_freem(m); + splx(s); + return (ENOBUFS); + } + } + error = sooptcopyin(sopt, mtod(m, caddr_t), sopt->sopt_valsize, + sopt->sopt_valsize); + if (error) { + (void) m_free(m); + goto out; + } + m->m_len = sopt->sopt_valsize; + } + if (sopt->sopt_dir == SOPT_SET) { +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + error = sctp_optsset(so, sopt->sopt_name, &m, sopt->sopt_td); +#else + error = sctp_optsset(so, sopt->sopt_name, &m, sopt->sopt_p); +#endif + } else if (sopt->sopt_dir == SOPT_GET) { +#if defined (__FreeBSD__) && __FreeBSD_version >= 500000 + error = sctp_optsget(so, sopt->sopt_name, &m, sopt->sopt_td); +#else + error = sctp_optsget(so, sopt->sopt_name, &m, sopt->sopt_p); +#endif + } else { + error = EINVAL; + } + if ( (error == 0) && (m != NULL)) { + error = sooptcopyout(sopt, mtod(m, caddr_t), m->m_len); + sctp_m_freem(m); + } else if (m != NULL) { + sctp_m_freem(m); + } + out: + splx(s); + return (error); +} + +#else +/* NetBSD and OpenBSD */ +int +sctp_ctloutput(op, so, level, optname, mp) + int op; + struct socket *so; + int level, optname; + struct mbuf **mp; +{ + int s, error; + struct inpcb *inp; +#ifdef INET6 + struct in6pcb *in6p; +#endif + int family; /* family of the socket */ + + family = so->so_proto->pr_domain->dom_family; + error = 0; +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + switch (family) { + case PF_INET: + inp = sotoinpcb(so); +#ifdef INET6 + in6p = NULL; +#endif + break; +#ifdef INET6 + case PF_INET6: + inp = NULL; + in6p = sotoin6pcb(so); + break; +#endif + default: + splx(s); + return EAFNOSUPPORT; + } +#ifndef INET6 + if (inp == NULL) +#else + if (inp == NULL && in6p == NULL) +#endif + { + splx(s); + if (op == PRCO_SETOPT && *mp) + (void) m_free(*mp); + return (ECONNRESET); + } + if (level != IPPROTO_SCTP) { + switch (family) { + case PF_INET: + error = ip_ctloutput(op, so, level, optname, mp); + break; +#ifdef INET6 + case PF_INET6: + error = ip6_ctloutput(op, so, level, optname, mp); + break; +#endif + } + splx(s); + return (error); + } + /* Ok if we reach here it is a SCTP option we hope */ + if (op == PRCO_SETOPT) { + error = sctp_optsset(so, optname, mp, (struct proc *)NULL); + if (*mp) + (void) m_free(*mp); + } else if (op == PRCO_GETOPT) { + error = sctp_optsget(so, optname, mp, (struct proc *)NULL); + } else { + error = EINVAL; + } + splx(s); + return (error); +} + +#endif + +static int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp_connect(struct socket *so, struct sockaddr *addr, struct thread *p) +{ +#else +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp_connect(struct socket *so, struct sockaddr *addr, struct proc *p) +{ +#else +sctp_connect(struct socket *so, struct mbuf *nam, struct proc *p) +{ + struct sockaddr *addr = mtod(nam, struct sockaddr *); +#endif +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) + int s = splsoftnet(); +#else + int s = splnet(); +#endif + int error = 0; + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_PCB1) { + printf("Connect called in SCTP to "); + sctp_print_address(addr); + printf("Port %d\n", ntohs(((struct sockaddr_in *)addr)->sin_port)); + } +#endif /* SCTP_DEBUG */ + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) { + splx(s); + /* I made the same as TCP since we are not setup? */ + return (ECONNRESET); + } + SCTP_ASOC_CREATE_LOCK(inp); + SCTP_INP_WLOCK(inp); + if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) || + (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { + /* Should I really unlock ? */ + SCTP_INP_WUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return (EFAULT); + } +#ifdef INET6 + if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) && + (addr->sa_family == AF_INET6)) { + SCTP_INP_WUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return (EINVAL); + } +#endif /* INET6 */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == + SCTP_PCB_FLAGS_UNBOUND) { + /* Bind a ephemeral port */ + SCTP_INP_WUNLOCK(inp); + error = sctp_inpcb_bind(so, NULL, p); + if (error) { + SCTP_ASOC_CREATE_UNLOCK(inp); + splx(s); + return (error); + } + SCTP_INP_WLOCK(inp); + } + /* Now do we connect? */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && + (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) { + /* We are already connected AND the TCP model */ + splx(s); + SCTP_INP_WUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + return (EADDRINUSE); + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WUNLOCK(inp); + } else { + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + stcb = sctp_findassociation_ep_addr(&inp, addr, NULL, NULL, NULL); + if (stcb == NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + } + if (stcb != NULL) { + /* Already have or am bring up an association */ + SCTP_ASOC_CREATE_UNLOCK(inp); + SCTP_TCB_UNLOCK(stcb); + splx(s); + return (EALREADY); + } + /* We are GOOD to go */ + stcb = sctp_aloc_assoc(inp, addr, 1, &error, 0); + if (stcb == NULL) { + /* Gak! no memory */ + splx(s); + return (error); + } + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { + stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; + /* Set the connected flag so we can queue data */ + soisconnecting(so); + } + stcb->asoc.state = SCTP_STATE_COOKIE_WAIT; + SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); + sctp_send_initiate(inp, stcb); + SCTP_ASOC_CREATE_UNLOCK(inp); + SCTP_TCB_UNLOCK(stcb); + splx(s); + return error; +} + +int +sctp_usr_recvd(struct socket *so, int flags) +{ + int s; + struct sctp_socket_q_list *sq=NULL; + /* + * The user has received some data, we may be able to stuff more + * up the socket. And we need to possibly update the rwnd. + */ + struct sctp_inpcb *inp; + struct sctp_tcb *stcb=NULL; + + inp = (struct sctp_inpcb *)so->so_pcb; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) + printf("Read for so:%x inp:%x Flags:%x\n", + (u_int)so, (u_int)inp, (u_int)flags); +#endif + + if (inp == 0) { + /* I made the same as TCP since we are not setup? */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) + printf("Nope, connection reset\n"); +#endif + return (ECONNRESET); + } +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + /* + * Grab the first one on the list. It will re-insert itself if + * it runs out of room + */ + SCTP_INP_WLOCK(inp); + if ((flags & MSG_EOR) && ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) + && ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) { + /* Ok the other part of our grubby tracking + * stuff for our horrible layer violation that + * the tsvwg thinks is ok for sctp_peeloff.. gak! + * We must update the next vtag pending on the + * socket buffer (if any). + */ + inp->sctp_vtag_first = sctp_get_first_vtag_from_sb(so); + sq = TAILQ_FIRST(&inp->sctp_queue_list); + if (sq) { + stcb = sq->tcb; + } else { + stcb = NULL; + } + } else { + stcb = LIST_FIRST(&inp->sctp_asoc_list); + } + if (stcb) + SCTP_TCB_LOCK(stcb); + if (stcb) { + long incr; + /* all code in normal stcb path assumes + * that you have a tcb_lock only. Thus + * we must release the inp write lock. + */ + if (flags & MSG_EOR) { + if (((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) + && ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) { + stcb = sctp_remove_from_socket_q(inp); + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) + printf("remove from socket queue for inp:%x tcbret:%x\n", + (u_int)inp, (u_int)stcb); +#endif + + stcb->asoc.my_rwnd_control_len = sctp_sbspace_sub(stcb->asoc.my_rwnd_control_len, + sizeof(struct mbuf)); + if (inp->sctp_flags & SCTP_PCB_FLAGS_RECVDATAIOEVNT) { + stcb->asoc.my_rwnd_control_len = sctp_sbspace_sub(stcb->asoc.my_rwnd_control_len, + CMSG_LEN(sizeof(struct sctp_sndrcvinfo))); + } + } + if ((TAILQ_EMPTY(&stcb->asoc.delivery_queue) == 0) || + (TAILQ_EMPTY(&stcb->asoc.reasmqueue) == 0)) { + /* Deliver if there is something to be delivered */ + sctp_service_queues(stcb, &stcb->asoc, 1); + } + sctp_set_rwnd(stcb, &stcb->asoc); + /* if we increase by 1 or more MTU's (smallest MTUs of all + * nets) we send a window update sack + */ + incr = stcb->asoc.my_rwnd - stcb->asoc.my_last_reported_rwnd; + if (incr < 0) { + incr = 0; + } + if (((uint32_t)incr >= (stcb->asoc.smallest_mtu * SCTP_SEG_TO_RWND_UPD)) || + ((((uint32_t)incr)*SCTP_SCALE_OF_RWND_TO_UPD) >= so->so_rcv.sb_hiwat)) { + if (callout_pending(&stcb->asoc.dack_timer.timer)) { + /* If the timer is up, stop it */ + sctp_timer_stop(SCTP_TIMER_TYPE_RECV, + stcb->sctp_ep, stcb, NULL); + } + /* Send the sack, with the new rwnd */ + sctp_send_sack(stcb); + /* Now do the output */ + sctp_chunk_output(inp, stcb, 10); + } + } else { + if ((( sq ) && (flags & MSG_EOR) && ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0)) + && ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) { + stcb = sctp_remove_from_socket_q(inp); + } + } + SOCKBUF_LOCK(&so->so_rcv); + if (( so->so_rcv.sb_mb == NULL ) && + (TAILQ_EMPTY(&inp->sctp_queue_list) == 0)) { + int sq_cnt=0; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) + printf("Something off, inp:%x so->so_rcv->sb_mb is empty and sockq is not.. cleaning\n", + (u_int)inp); +#endif + if (((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) + && ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) { + int done_yet; + done_yet = TAILQ_EMPTY(&inp->sctp_queue_list); + while (!done_yet) { + sq_cnt++; + (void)sctp_remove_from_socket_q(inp); + done_yet = TAILQ_EMPTY(&inp->sctp_queue_list); + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ2) + printf("Cleaned up %d sockq's\n", sq_cnt); +#endif + } + SOCKBUF_UNLOCK(&so->so_rcv); + if (stcb) + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WUNLOCK(inp); + splx(s); + return (0); +} + +int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp_listen(struct socket *so, struct thread *p) +#else +sctp_listen(struct socket *so, struct proc *p) +#endif +{ + /* + * Note this module depends on the protocol processing being + * called AFTER any socket level flags and backlog are applied + * to the socket. The traditional way that the socket flags are + * applied is AFTER protocol processing. We have made a change + * to the sys/kern/uipc_socket.c module to reverse this but this + * MUST be in place if the socket API for SCTP is to work properly. + */ +#if defined(__NetBSD__) || defined(__OpenBSD__) + int s = splsoftnet(); +#else + int s = splnet(); +#endif + int error = 0; + struct sctp_inpcb *inp; + + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) { + splx(s); + /* I made the same as TCP since we are not setup? */ + return (ECONNRESET); + } + SCTP_INP_RLOCK(inp); + if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && + (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) { + /* We are already connected AND the TCP model */ + splx(s); + SCTP_INP_RUNLOCK(inp); + return (EADDRINUSE); + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) { + /* We must do a bind. */ + SCTP_INP_RUNLOCK(inp); + if ((error = sctp_inpcb_bind(so, NULL, p))) { + /* bind error, probably perm */ + splx(s); + return (error); + } + } else { + SCTP_INP_RUNLOCK(inp); + } + SOCK_LOCK(so); + SCTP_INP_WLOCK(inp); + if (inp->sctp_socket->so_qlimit) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_UDPTYPE) { + /* + * For the UDP model we must TURN OFF the ACCEPT + * flags since we do NOT allow the accept() call. + * The TCP model (when present) will do accept which + * then prohibits connect(). + */ + inp->sctp_socket->so_options &= ~SO_ACCEPTCONN; + } + inp->sctp_flags |= SCTP_PCB_FLAGS_ACCEPTING; + } else { + if (inp->sctp_flags & SCTP_PCB_FLAGS_ACCEPTING) { + /* + * Turning off the listen flags if the backlog is + * set to 0 (i.e. qlimit is 0). + */ + inp->sctp_flags &= ~SCTP_PCB_FLAGS_ACCEPTING; + } + inp->sctp_socket->so_options &= ~SO_ACCEPTCONN; + } + SCTP_INP_WUNLOCK(inp); + SOCK_UNLOCK(so); + splx(s); + return (error); +} + +int +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp_accept(struct socket *so, struct sockaddr **addr) +{ +#else +sctp_accept(struct socket *so, struct mbuf *nam) +{ + struct sockaddr *addr = mtod(nam, struct sockaddr *); +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) + int s = splsoftnet(); +#else + int s = splnet(); +#endif + struct sctp_tcb *stcb; + struct sockaddr *prim; + struct sctp_inpcb *inp; + + inp = (struct sctp_inpcb *)so->so_pcb; + + if (inp == 0) { + splx(s); + return (ECONNRESET); + } + SCTP_INP_RLOCK(inp); + if (so->so_state & SS_ISDISCONNECTED) { + splx(s); + SCTP_INP_RUNLOCK(inp); + return (ECONNABORTED); + } + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { + splx(s); + SCTP_INP_RUNLOCK(inp); + return (ECONNRESET); + } + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + prim = (struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr; + if (prim->sa_family == AF_INET) { + struct sockaddr_in *sin; +#if defined(__FreeBSD__) || defined(__APPLE__) + MALLOC(sin, struct sockaddr_in *, sizeof *sin, M_SONAME, + M_WAITOK | M_ZERO); +#else + sin = (struct sockaddr_in *)addr; + bzero((caddr_t)sin, sizeof (*sin)); +#endif + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + sin->sin_port = ((struct sockaddr_in *)prim)->sin_port; + sin->sin_addr = ((struct sockaddr_in *)prim)->sin_addr; +#if defined(__FreeBSD__) || defined(__APPLE__) + *addr = (struct sockaddr *)sin; +#else + nam->m_len = sizeof(*sin); +#endif + } else { + struct sockaddr_in6 *sin6; +#if defined(__FreeBSD__) || defined(__APPLE__) + MALLOC(sin6, struct sockaddr_in6 *, sizeof *sin6, M_SONAME, + M_WAITOK | M_ZERO); +#else + sin6 = (struct sockaddr_in6 *)addr; +#endif + bzero((caddr_t)sin6, sizeof (*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + sin6->sin6_port = ((struct sockaddr_in6 *)prim)->sin6_port; + + sin6->sin6_addr = ((struct sockaddr_in6 *)prim)->sin6_addr; + if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) + /* sin6->sin6_scope_id = ntohs(sin6->sin6_addr.s6_addr16[1]);*/ + in6_recoverscope(sin6, &sin6->sin6_addr, NULL); /* skip ifp check */ + else + sin6->sin6_scope_id = 0; /*XXX*/ +#if defined(__FreeBSD__) || defined (__APPLE__) + *addr= (struct sockaddr *)sin6; +#else + nam->m_len = sizeof(*sin6); +#endif + } + /* Wake any delayed sleep action */ + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(inp); + if (inp->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE) { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_DONT_WAKE; + if (inp->sctp_flags & SCTP_PCB_FLAGS_WAKEOUTPUT) { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEOUTPUT; +#if defined(__NetBSD__) + if (sowritable(inp->sctp_socket)) + sowwakeup(inp->sctp_socket); +#else + if (sowriteable(inp->sctp_socket)) + sowwakeup(inp->sctp_socket); +#endif + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_WAKEINPUT) { + inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEINPUT; + if (soreadable(inp->sctp_socket)) + sorwakeup(inp->sctp_socket); + } + + } + SCTP_INP_WUNLOCK(inp); + splx(s); + return (0); +} + +int +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp_ingetaddr(struct socket *so, struct sockaddr **addr) +#else +sctp_ingetaddr(struct socket *so, struct mbuf *nam) +#endif +{ +#if defined(__FreeBSD__) || defined(__APPLE__) + struct sockaddr_in *sin; +#else + struct sockaddr_in *sin = mtod(nam, struct sockaddr_in *); +#endif + int s; + struct sctp_inpcb *inp; + /* + * Do the malloc first in case it blocks. + */ +#if defined(__FreeBSD__) || defined(__APPLE__) + MALLOC(sin, struct sockaddr_in *, sizeof *sin, M_SONAME, M_WAITOK | + M_ZERO); +#else + nam->m_len = sizeof(*sin); + memset(sin, 0, sizeof(*sin)); +#endif + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + inp = (struct sctp_inpcb *)so->so_pcb; + if (!inp) { + splx(s); +#if defined(__FreeBSD__) || defined(__APPLE__) + FREE(sin, M_SONAME); +#endif + return ECONNRESET; + } + SCTP_INP_RLOCK(inp); + sin->sin_port = inp->sctp_lport; + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + struct sctp_tcb *stcb; + struct sockaddr_in *sin_a; + struct sctp_nets *net; + int fnd; + + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { + goto notConn; + } + fnd = 0; + sin_a = NULL; + SCTP_TCB_LOCK(stcb); + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + sin_a = (struct sockaddr_in *)&net->ro._l_addr; + if (sin_a->sin_family == AF_INET) { + fnd = 1; + break; + } + } + if ((!fnd) || (sin_a == NULL)) { + /* punt */ + SCTP_TCB_UNLOCK(stcb); + goto notConn; + } + sin->sin_addr = sctp_ipv4_source_address_selection(inp, + stcb, (struct route *)&net->ro, net, 0); + SCTP_TCB_UNLOCK(stcb); + } else { + /* For the bound all case you get back 0 */ + notConn: + sin->sin_addr.s_addr = 0; + } + + } else { + /* Take the first IPv4 address in the list */ + struct sctp_laddr *laddr; + int fnd = 0; + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *sin_a; + sin_a = (struct sockaddr_in *)laddr->ifa->ifa_addr; + sin->sin_addr = sin_a->sin_addr; + fnd = 1; + break; + } + } + if (!fnd) { + splx(s); +#if defined(__FreeBSD__) || defined(__APPLE__) + FREE(sin, M_SONAME); +#endif + SCTP_INP_RUNLOCK(inp); + return ENOENT; + } + } + SCTP_INP_RUNLOCK(inp); + splx(s); +#if defined(__FreeBSD__) || defined(__APPLE__) + (*addr) = (struct sockaddr *)sin; +#endif + return (0); +} + +int +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp_peeraddr(struct socket *so, struct sockaddr **addr) +{ + struct sockaddr_in *sin = (struct sockaddr_in *)*addr; +#else +sctp_peeraddr(struct socket *so, struct mbuf *nam) +{ + struct sockaddr_in *sin = mtod(nam, struct sockaddr_in *); +#endif + int s, fnd; + struct sockaddr_in *sin_a; + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + struct sctp_nets *net; + + /* Do the malloc first in case it blocks. */ + inp = (struct sctp_inpcb *)so->so_pcb; + if ((inp == NULL) || + ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)) { + /* UDP type and listeners will drop out here */ + return (ENOTCONN); + } +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) + MALLOC(sin, struct sockaddr_in *, sizeof *sin, M_SONAME, M_WAITOK | + M_ZERO); +#else + nam->m_len = sizeof(*sin); + memset(sin, 0, sizeof(*sin)); +#endif + sin->sin_family = AF_INET; + sin->sin_len = sizeof(*sin); + + /* We must recapture incase we blocked */ + inp = (struct sctp_inpcb *)so->so_pcb; + if (!inp) { + splx(s); +#if defined(__FreeBSD__) || defined(__APPLE__) + FREE(sin, M_SONAME); +#endif + return ECONNRESET; + } + SCTP_INP_RLOCK(inp); + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_LOCK(stcb); + SCTP_INP_RUNLOCK(inp); + if (stcb == NULL) { + splx(s); +#if defined(__FreeBSD__) || defined(__APPLE__) + FREE(sin, M_SONAME); +#endif + return ECONNRESET; + } + fnd = 0; + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + sin_a = (struct sockaddr_in *)&net->ro._l_addr; + if (sin_a->sin_family == AF_INET) { + fnd = 1; + sin->sin_port = stcb->rport; + sin->sin_addr = sin_a->sin_addr; + break; + } + } + SCTP_TCB_UNLOCK(stcb); + if (!fnd) { + /* No IPv4 address */ + splx(s); +#if defined(__FreeBSD__) || defined(__APPLE__) + FREE(sin, M_SONAME); +#endif + return ENOENT; + } + splx(s); + return (0); +} + +#if defined(__FreeBSD__) || defined(__APPLE__) +struct pr_usrreqs sctp_usrreqs = { + sctp_abort, + sctp_accept, + sctp_attach, + sctp_bind, + sctp_connect, + pru_connect2_notsupp, + in_control, + sctp_detach, + sctp_disconnect, + sctp_listen, + sctp_peeraddr, + sctp_usr_recvd, + pru_rcvoob_notsupp, + sctp_send, + pru_sense_null, + sctp_shutdown, + sctp_ingetaddr, + sctp_sosend, + soreceive, + sopoll +}; + +#else +#if defined(__NetBSD__) +int +sctp_usrreq(so, req, m, nam, control, p) + struct socket *so; + int req; + struct mbuf *m, *nam, *control; + struct proc *p; +{ +#else +int +sctp_usrreq(so, req, m, nam, control) + struct socket *so; + int req; + struct mbuf *m, *nam, *control; +{ + struct proc *p = curproc; +#endif + int s; + int error = 0; + int family; + + family = so->so_proto->pr_domain->dom_family; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + if (req == PRU_CONTROL) { + switch (family) { + case PF_INET: + error = in_control(so, (long)m, (caddr_t)nam, + (struct ifnet *)control +#if defined(__NetBSD__) + , p +#endif + ); + break; +#ifdef INET6 + case PF_INET6: + error = in6_control(so, (long)m, (caddr_t)nam, + (struct ifnet *)control, p); + break; +#endif + default: + error = EAFNOSUPPORT; + } + splx(s); + return (error); + } +#ifdef __NetBSD__ + if (req == PRU_PURGEIF) { + struct ifnet *ifn; + struct ifaddr *ifa; + ifn = (struct ifnet *)control; + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (ifa->ifa_addr->sa_family == family) { + sctp_delete_ip_address(ifa); + } + } + switch (family) { + case PF_INET: + in_purgeif (ifn); + break; +#ifdef INET6 + case PF_INET6: + in6_purgeif (ifn); + break; +#endif /* INET6 */ + default: + splx(s); + return (EAFNOSUPPORT); + } + splx(s); + return (0); + } +#endif + switch (req) { + case PRU_ATTACH: + error = sctp_attach(so, family, p); + break; + case PRU_DETACH: + error = sctp_detach(so); + break; + case PRU_BIND: + if (nam == NULL) { + splx(s); + return (EINVAL); + } + error = sctp_bind(so, nam, p); + break; + case PRU_LISTEN: + error = sctp_listen(so, p); + break; + case PRU_CONNECT: + if (nam == NULL) { + splx(s); + return (EINVAL); + } + error = sctp_connect(so, nam, p); + break; + case PRU_DISCONNECT: + error = sctp_disconnect(so); + break; + case PRU_ACCEPT: + if (nam == NULL) { + splx(s); + return (EINVAL); + } + error = sctp_accept(so, nam); + break; + case PRU_SHUTDOWN: + error = sctp_shutdown(so); + break; + + case PRU_RCVD: + /* + * For Open and Net BSD, this is real + * ugly. The mbuf *nam that is passed + * (by soreceive()) is the int flags c + * ast as a (mbuf *) yuck! + */ + error = sctp_usr_recvd(so, (int)((long)nam)); + break; + + case PRU_SEND: + /* Flags are ignored */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_USRREQ1) { + printf("Send called on V4 side\n"); + } +#endif + { + struct sockaddr *addr; + if (nam == NULL) + addr = NULL; + else + addr = mtod(nam, struct sockaddr *); + + error = sctp_send(so, 0, m, addr, control, p); + } + break; + case PRU_ABORT: + error = sctp_abort(so); + break; + + case PRU_SENSE: + error = 0; + break; + case PRU_RCVOOB: + error = EAFNOSUPPORT; + break; + case PRU_SENDOOB: + error = EAFNOSUPPORT; + break; + case PRU_PEERADDR: + error = sctp_peeraddr(so, nam); + break; + case PRU_SOCKADDR: + error = sctp_ingetaddr(so, nam); + break; + case PRU_SLOWTIMO: + error = 0; + break; + default: + break; + } + splx(s); + return (error); +} +#endif + +/* #if defined(__NetBSD__) || defined(__OpenBSD__) */ +#if __OpenBSD__ +/* + * Sysctl for sctp variables. + */ +int +sctp_sysctl(name, namelen, oldp, oldlenp, newp, newlen) + int *name; + u_int namelen; + void *oldp; + size_t *oldlenp; + void *newp; + size_t newlen; +{ + + /* All sysctl names at this level are terminal. */ + if (namelen != 1) + return (ENOTDIR); +sysctl_int(); + + switch (name[0]) { + case SCTPCTL_MAXDGRAM: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_sendspace)); + case SCTPCTL_RECVSPACE: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_recvspace)); + case SCTPCTL_AUTOASCONF: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_auto_asconf)); + case SCTPCTL_ECN_ENABLE: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_ecn)); + case SCTPCTL_ECN_NONCE: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_ecn_nonce)); + case SCTPCTL_STRICT_SACK: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_strict_sacks)); + case SCTPCTL_NOCSUM_LO: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_no_csum_on_loopback)); + case SCTPCTL_STRICT_INIT: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_strict_init)); + case SCTPCTL_PEER_CHK_OH: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_peer_chunk_oh)); + case SCTPCTL_MAXBURST: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_max_burst_default)); + case SCTPCTL_MAXCHUNKONQ: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_max_chunks_on_queue)); + case SCTPCTL_DELAYED_SACK: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_delayed_sack_time_default)); + case SCTPCTL_HB_INTERVAL: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_heartbeat_interval_default)); + case SCTPCTL_PMTU_RAISE: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_pmtu_raise_time_default)); + case SCTPCTL_SHUTDOWN_GUARD: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_shutdown_guard_time_default)); + case SCTPCTL_SECRET_LIFETIME: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_secret_lifetime_default)); + case SCTPCTL_RTO_MAX: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_rto_max_default)); + case SCTPCTL_RTO_MIN: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_rto_min_default)); + case SCTPCTL_RTO_INITIAL: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_rto_initial_default)); + case SCTPCTL_INIT_RTO_MAX: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_init_rto_max_default)); + case SCTPCTL_COOKIE_LIFE: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_valid_cookie_life_default)); + case SCTPCTL_INIT_RTX_MAX: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_init_rtx_max_default)); + case SCTPCTL_ASSOC_RTX_MAX: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_assoc_rtx_max_default)); + case SCTPCTL_PATH_RTX_MAX: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_path_rtx_max_default)); + case SCTPCTL_NR_OUTGOING_STREAMS: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_nr_outgoing_streams_default)); +#ifdef SCTP_DEBUG + case SCTPCTL_DEBUG: + return (sysctl_int(oldp, oldlenp, newp, newlen, + &sctp_debug_on)); +#endif + default: + return (ENOPROTOOPT); + } + /* NOTREACHED */ +} +#endif +#if __NetBSD__ +/* + * Sysctl for sctp variables. + */ +SYSCTL_SETUP(sysctl_net_inet_sctp_setup, "sysctl net.inet.sctp subtree setup") +{ + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT, + CTLTYPE_NODE, "net", NULL, + NULL, 0, NULL, 0, + CTL_NET, CTL_EOL); + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT, + CTLTYPE_NODE, "inet", NULL, + NULL, 0, NULL, 0, + CTL_NET, PF_INET, CTL_EOL); + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT, + CTLTYPE_NODE, "sctp", + SYSCTL_DESCR("sctp related settings"), + NULL, 0, NULL, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "maxdgram", + SYSCTL_DESCR("Maximum outgoing SCTP buffer size"), + NULL, 0, &sctp_sendspace, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_MAXDGRAM, + CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "recvspace", + SYSCTL_DESCR("Maximum incoming SCTP buffer size"), + NULL, 0, &sctp_recvspace, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_RECVSPACE, + CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "autoasconf", + SYSCTL_DESCR("Enable SCTP Auto-ASCONF"), + NULL, 0, &sctp_auto_asconf, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_AUTOASCONF, + CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "ecn_enable", + SYSCTL_DESCR("Enable SCTP ECN"), + NULL, 0, &sctp_ecn, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_ECN_ENABLE, + CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "ecn_nonce", + SYSCTL_DESCR("Enable SCTP ECN Nonce"), + NULL, 0, &sctp_ecn_nonce, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_ECN_NONCE, + CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "strict_sack", + SYSCTL_DESCR("Enable SCTP Strict SACK checking"), + NULL, 0, &sctp_strict_sacks, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_STRICT_SACK, + CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "loopback_nocsum", + SYSCTL_DESCR("Enable NO Csum on packets sent on loopback"), + NULL, 0, &sctp_no_csum_on_loopback, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_NOCSUM_LO, + CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "strict_init", + SYSCTL_DESCR("Enable strict INIT/INIT-ACK singleton enforcement"), + NULL, 0, &sctp_strict_init, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_STRICT_INIT, + CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "peer_chkoh", + SYSCTL_DESCR("Amount to debit peers rwnd per chunk sent"), + NULL, 0, &sctp_peer_chunk_oh, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_PEER_CHK_OH, + CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "maxburst", + SYSCTL_DESCR("Default max burst for sctp endpoints"), + NULL, 0, &sctp_max_burst_default, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_MAXBURST, + CTL_EOL); + + sysctl_createv(clog, 0, NULL, NULL, + CTLFLAG_PERMANENT|CTLFLAG_READWRITE, + CTLTYPE_INT, "maxchunks", + SYSCTL_DESCR("Default max chunks on queue per asoc"), + NULL, 0, &sctp_max_chunks_on_queue, 0, + CTL_NET, PF_INET, IPPROTO_SCTP, SCTPCTL_MAXCHUNKONQ, + CTL_EOL); + +} +#endif diff --git a/sys/netinet/sctp_var.h b/sys/netinet/sctp_var.h new file mode 100644 index 0000000000..ef2e5b8af0 --- /dev/null +++ b/sys/netinet/sctp_var.h @@ -0,0 +1,275 @@ +/* $KAME: sctp_var.h,v 1.23 2004/10/27 07:57:49 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctp_var.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _NETINET_SCTP_VAR_H_ +#define _NETINET_SCTP_VAR_H_ + +#ifndef __OpenBSD__ +#include +#endif +#include + +/* SCTP Kernel structures */ + +/* + * Names for SCTP sysctl objects + */ +#ifndef __APPLE__ +#define SCTPCTL_MAXDGRAM 1 /* max datagram size */ +#define SCTPCTL_RECVSPACE 2 /* default receive buffer space */ +#define SCTPCTL_AUTOASCONF 3 /* auto asconf enable/disable flag */ +#define SCTPCTL_ECN_ENABLE 4 /* Is ecn allowed */ +#define SCTPCTL_ECN_NONCE 5 /* Is ecn nonce allowed */ +#define SCTPCTL_STRICT_SACK 6 /* strictly require sack'd TSN's to be + * smaller than sndnxt. + */ +#define SCTPCTL_NOCSUM_LO 7 /* Require that the Loopback NOT have + * the crc32 checksum on packets routed over + * it. + */ +#define SCTPCTL_STRICT_INIT 8 +#define SCTPCTL_PEER_CHK_OH 9 +#define SCTPCTL_MAXBURST 10 +#define SCTPCTL_MAXCHUNKONQ 11 +#define SCTPCTL_DELAYED_SACK 12 +#define SCTPCTL_HB_INTERVAL 13 +#define SCTPCTL_PMTU_RAISE 14 +#define SCTPCTL_SHUTDOWN_GUARD 15 +#define SCTPCTL_SECRET_LIFETIME 16 +#define SCTPCTL_RTO_MAX 17 +#define SCTPCTL_RTO_MIN 18 +#define SCTPCTL_RTO_INITIAL 19 +#define SCTPCTL_INIT_RTO_MAX 20 +#define SCTPCTL_COOKIE_LIFE 21 +#define SCTPCTL_INIT_RTX_MAX 22 +#define SCTPCTL_ASSOC_RTX_MAX 23 +#define SCTPCTL_PATH_RTX_MAX 24 +#define SCTPCTL_NR_OUTGOING_STREAMS 25 +#ifdef SCTP_DEBUG +#define SCTPCTL_DEBUG 26 +#define SCTPCTL_MAXID 27 +#else +#define SCTPCTL_MAXID 26 +#endif + +#endif + +#ifdef SCTP_DEBUG +#define SCTPCTL_NAMES { \ + { 0, 0 }, \ + { "maxdgram", CTLTYPE_INT }, \ + { "recvspace", CTLTYPE_INT }, \ + { "autoasconf", CTLTYPE_INT }, \ + { "ecn_enable", CTLTYPE_INT }, \ + { "ecn_nonce", CTLTYPE_INT }, \ + { "strict_sack", CTLTYPE_INT }, \ + { "looback_nocsum", CTLTYPE_INT }, \ + { "strict_init", CTLTYPE_INT }, \ + { "peer_chkoh", CTLTYPE_INT }, \ + { "maxburst", CTLTYPE_INT }, \ + { "maxchunks", CTLTYPE_INT }, \ + { "delayed_sack_time", CTLTYPE_INT }, \ + { "heartbeat_interval", CTLTYPE_INT }, \ + { "pmtu_raise_time", CTLTYPE_INT }, \ + { "shutdown_guard_time", CTLTYPE_INT }, \ + { "secret_lifetime", CTLTYPE_INT }, \ + { "rto_max", CTLTYPE_INT }, \ + { "rto_min", CTLTYPE_INT }, \ + { "rto_initial", CTLTYPE_INT }, \ + { "init_rto_max", CTLTYPE_INT }, \ + { "valid_cookie_life", CTLTYPE_INT }, \ + { "init_rtx_max", CTLTYPE_INT }, \ + { "assoc_rtx_max", CTLTYPE_INT }, \ + { "path_rtx_max", CTLTYPE_INT }, \ + { "nr_outgoing_streams", CTLTYPE_INT }, \ + { "debug", CTLTYPE_INT }, \ +} +#else +#define SCTPCTL_NAMES { \ + { 0, 0 }, \ + { "maxdgram", CTLTYPE_INT }, \ + { "recvspace", CTLTYPE_INT }, \ + { "autoasconf", CTLTYPE_INT }, \ + { "ecn_enable", CTLTYPE_INT }, \ + { "ecn_nonce", CTLTYPE_INT }, \ + { "strict_sack", CTLTYPE_INT }, \ + { "looback_nocsum", CTLTYPE_INT }, \ + { "strict_init", CTLTYPE_INT }, \ + { "peer_chkoh", CTLTYPE_INT }, \ + { "maxburst", CTLTYPE_INT }, \ + { "maxchunks", CTLTYPE_INT }, \ + { "delayed_sack_time", CTLTYPE_INT }, \ + { "heartbeat_interval", CTLTYPE_INT }, \ + { "pmtu_raise_time", CTLTYPE_INT }, \ + { "shutdown_guard_time", CTLTYPE_INT }, \ + { "secret_lifetime", CTLTYPE_INT }, \ + { "rto_max", CTLTYPE_INT }, \ + { "rto_min", CTLTYPE_INT }, \ + { "rto_initial", CTLTYPE_INT }, \ + { "init_rto_max", CTLTYPE_INT }, \ + { "valid_cookie_life", CTLTYPE_INT }, \ + { "init_rtx_max", CTLTYPE_INT }, \ + { "assoc_rtx_max", CTLTYPE_INT }, \ + { "path_rtx_max", CTLTYPE_INT }, \ + { "nr_outgoing_streams", CTLTYPE_INT }, \ +} +#endif + +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) + +#if defined(__FreeBSD__) || defined(__APPLE__) +#ifdef SYSCTL_DECL +SYSCTL_DECL(_net_inet_sctp); +#endif +extern struct pr_usrreqs sctp_usrreqs; +#elif defined(__NetBSD__) +int sctp_usrreq __P((struct socket *, int, struct mbuf *, struct mbuf *, + struct mbuf *, struct proc *)); +#else +int sctp_usrreq __P((struct socket *, int, struct mbuf *, struct mbuf *, + struct mbuf *)); +#endif + +#define sctp_sbspace(sb) ((long) (((sb)->sb_hiwat > (sb)->sb_cc) ? ((sb)->sb_hiwat - (sb)->sb_cc) : 0)) + +#define sctp_sbspace_sub(a,b) ((a > b) ? (a - b) : 0) + +extern int sctp_sendspace; +extern int sctp_recvspace; +extern int sctp_ecn; +extern int sctp_ecn_nonce; + +struct sctp_nets; +struct sctp_inpcb; +struct sctp_tcb; +struct sctphdr; + +#if defined(__OpenBSD__) +void sctp_fasttim(void); +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) +void sctp_ctlinput __P((int, struct sockaddr *, void *)); +int sctp_ctloutput __P((struct socket *, struct sockopt *)); +void sctp_input __P((struct mbuf *, int)); +#else +void* sctp_ctlinput __P((int, struct sockaddr *, void *)); +int sctp_ctloutput __P((int, struct socket *, int, int, struct mbuf **)); +void sctp_input __P((struct mbuf *, ... )); +#endif +void sctp_drain __P((void)); +void sctp_init __P((void)); +int sctp_shutdown __P((struct socket *)); +void sctp_notify __P((struct sctp_inpcb *, int, struct sctphdr *, + struct sockaddr *, struct sctp_tcb *, + struct sctp_nets *)); +int sctp_usr_recvd __P((struct socket *, int)); + +#if defined(INET6) +void ip_2_ip6_hdr __P((struct ip6_hdr *, struct ip *)); +#endif + +int sctp_bindx(struct socket *, int, struct sockaddr_storage *, + int, int, struct proc *); + +/* can't use sctp_assoc_t here */ +int sctp_peeloff(struct socket *, struct socket *, int, caddr_t, int *); + + +sctp_assoc_t sctp_getassocid(struct sockaddr *); + + + +int sctp_ingetaddr(struct socket *, +#if defined(__FreeBSD__) || defined(__APPLE__) + struct sockaddr ** +#else + struct mbuf * +#endif +); + +int sctp_peeraddr(struct socket *, +#if defined(__FreeBSD__) || defined(__APPLE__) + struct sockaddr ** +#else + struct mbuf * +#endif +); + +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +int sctp_listen(struct socket *, struct thread *); +#else +int sctp_listen(struct socket *, struct proc *); +#endif + +#if defined(__FreeBSD__) || defined(__APPLE__) +int sctp_accept(struct socket *, struct sockaddr **); +#else +int sctp_accept(struct socket *, struct mbuf *); +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +int sctp_sysctl(int *, u_int, void *, size_t *, void *, size_t); +#endif + +/* + * compatibility defines for OpenBSD, Apple + */ + +/* map callout into timeout for OpenBSD */ +#ifdef __OpenBSD__ +#ifndef callout_init +#define callout_init(args) +#define callout_reset(c, ticks, func, arg) \ +do { \ + timeout_set((c), (func), (arg)); \ + timeout_add((c), (ticks)); \ +} while (0) +#define callout_stop(c) timeout_del(c) +#define callout_pending(c) timeout_pending(c) +#define callout_active(c) timeout_initialized(c) +#endif +#endif + +/* XXX: Hopefully temporary until APPLE changes to newer defs like other BSDs */ +#if defined(__APPLE__) +#define if_addrlist if_addrhead +#define if_list if_link +#define ifa_list ifa_link +#endif /* __APPLE__ **/ + +#endif /* _KERNEL */ + +#endif /* !_NETINET_SCTP_VAR_H_ */ diff --git a/sys/netinet/sctputil.c b/sys/netinet/sctputil.c new file mode 100644 index 0000000000..89b267db22 --- /dev/null +++ b/sys/netinet/sctputil.c @@ -0,0 +1,3844 @@ +/* $KAME: sctputil.c,v 1.36 2005/03/06 16:04:19 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctputil.c,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if !(defined(__OpenBSD__) || defined(__APPLE__)) +#include "opt_ipsec.h" +#endif +#if defined(__FreeBSD__) +#include "opt_compat.h" +#include "opt_inet6.h" +#include "opt_inet.h" +#ifndef SCTP_BASE_FREEBSD +#include "opt_mpath.h" +#endif /* SCTP_BASE_FREEBSD */ +#endif /* FreeBSD */ +#if defined(__NetBSD__) +#include "opt_inet.h" +#endif +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __FreeBSD__ +#include +#else +#include /* for callout_active() */ +#endif + +#include +#include + +#ifdef INET6 +#ifndef __OpenBSD__ +#include +#endif +#endif + +#if (defined(__FreeBSD__) && __FreeBSD_version >= 500000) +#include +#else +#include +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#ifdef INET6 +#include +#include + +#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__) +#include +#elif defined(__OpenBSD__) +#include +#endif + +#endif /* INET6 */ + +#include + +#ifdef IPSEC +#ifndef __OpenBSD__ +#include +#include +#else +#undef IPSEC +#endif +#endif /* IPSEC */ + +#include +#include +#ifdef INET6 +#include +#endif +#include +#include +#include +#include +#include +#include +#include /* for sctp_deliver_data() */ +#define NUMBER_OF_MTU_SIZES 18 + +#ifdef SCTP_DEBUG +extern u_int32_t sctp_debug_on; +#endif + +#ifdef SCTP_STAT_LOGGING +int sctp_cwnd_log_at=0; +int sctp_cwnd_log_rolled=0; +struct sctp_cwnd_log sctp_clog[SCTP_STAT_LOG_SIZE]; + +void sctp_clr_stat_log(void) +{ + sctp_cwnd_log_at=0; + sctp_cwnd_log_rolled=0; +} + +void +sctp_log_strm_del_alt(u_int32_t tsn, u_int16_t sseq, int from) +{ + + sctp_clog[sctp_cwnd_log_at].from = (u_int8_t)from; + sctp_clog[sctp_cwnd_log_at].event_type = (u_int8_t)SCTP_LOG_EVENT_STRM; + sctp_clog[sctp_cwnd_log_at].x.strlog.n_tsn = tsn; + sctp_clog[sctp_cwnd_log_at].x.strlog.n_sseq = sseq; + sctp_clog[sctp_cwnd_log_at].x.strlog.e_tsn = 0; + sctp_clog[sctp_cwnd_log_at].x.strlog.e_sseq = 0; + sctp_cwnd_log_at++; + if (sctp_cwnd_log_at >= SCTP_STAT_LOG_SIZE) { + sctp_cwnd_log_at = 0; + sctp_cwnd_log_rolled = 1; + } + +} + +void +sctp_log_map(uint32_t map, uint32_t cum, uint32_t high, int from) +{ + + sctp_clog[sctp_cwnd_log_at].from = (u_int8_t)from; + sctp_clog[sctp_cwnd_log_at].event_type = (u_int8_t)SCTP_LOG_EVENT_MAP; + sctp_clog[sctp_cwnd_log_at].x.map.base = map; + sctp_clog[sctp_cwnd_log_at].x.map.cum = cum; + sctp_clog[sctp_cwnd_log_at].x.map.high = high; + sctp_cwnd_log_at++; + if (sctp_cwnd_log_at >= SCTP_STAT_LOG_SIZE) { + sctp_cwnd_log_at = 0; + sctp_cwnd_log_rolled = 1; + } +} + +void +sctp_log_fr(uint32_t biggest_tsn, uint32_t biggest_new_tsn, uint32_t tsn, + int from) +{ + + sctp_clog[sctp_cwnd_log_at].from = (u_int8_t)from; + sctp_clog[sctp_cwnd_log_at].event_type = (u_int8_t)SCTP_LOG_EVENT_FR; + sctp_clog[sctp_cwnd_log_at].x.fr.largest_tsn = biggest_tsn; + sctp_clog[sctp_cwnd_log_at].x.fr.largest_new_tsn = biggest_new_tsn; + sctp_clog[sctp_cwnd_log_at].x.fr.tsn = tsn; + sctp_cwnd_log_at++; + if (sctp_cwnd_log_at >= SCTP_STAT_LOG_SIZE) { + sctp_cwnd_log_at = 0; + sctp_cwnd_log_rolled = 1; + } +} + +void +sctp_log_strm_del(struct sctp_tmit_chunk *chk, struct sctp_tmit_chunk *poschk, + int from) +{ + + if (chk == NULL) { + printf("Gak log of NULL?\n"); + return; + } + sctp_clog[sctp_cwnd_log_at].from = (u_int8_t)from; + sctp_clog[sctp_cwnd_log_at].event_type = (u_int8_t)SCTP_LOG_EVENT_STRM; + sctp_clog[sctp_cwnd_log_at].x.strlog.n_tsn = chk->rec.data.TSN_seq; + sctp_clog[sctp_cwnd_log_at].x.strlog.n_sseq = chk->rec.data.stream_seq; + if (poschk != NULL) { + sctp_clog[sctp_cwnd_log_at].x.strlog.e_tsn = + poschk->rec.data.TSN_seq; + sctp_clog[sctp_cwnd_log_at].x.strlog.e_sseq = + poschk->rec.data.stream_seq; + } else { + sctp_clog[sctp_cwnd_log_at].x.strlog.e_tsn = 0; + sctp_clog[sctp_cwnd_log_at].x.strlog.e_sseq = 0; + } + sctp_cwnd_log_at++; + if (sctp_cwnd_log_at >= SCTP_STAT_LOG_SIZE) { + sctp_cwnd_log_at = 0; + sctp_cwnd_log_rolled = 1; + } +} + +void +sctp_log_cwnd(struct sctp_nets *net, int augment, uint8_t from) +{ + + sctp_clog[sctp_cwnd_log_at].from = (u_int8_t)from; + sctp_clog[sctp_cwnd_log_at].event_type = (u_int8_t)SCTP_LOG_EVENT_CWND; + sctp_clog[sctp_cwnd_log_at].x.cwnd.net = net; + sctp_clog[sctp_cwnd_log_at].x.cwnd.cwnd_new_value = net->cwnd; + sctp_clog[sctp_cwnd_log_at].x.cwnd.inflight = net->flight_size; + sctp_clog[sctp_cwnd_log_at].x.cwnd.cwnd_augment = augment; + sctp_cwnd_log_at++; + if (sctp_cwnd_log_at >= SCTP_STAT_LOG_SIZE) { + sctp_cwnd_log_at = 0; + sctp_cwnd_log_rolled = 1; + } +} + +void +sctp_log_maxburst(struct sctp_nets *net, int error, int burst, uint8_t from) +{ + sctp_clog[sctp_cwnd_log_at].from = (u_int8_t)from; + sctp_clog[sctp_cwnd_log_at].event_type = (u_int8_t)SCTP_LOG_EVENT_MAXBURST; + sctp_clog[sctp_cwnd_log_at].x.cwnd.net = net; + sctp_clog[sctp_cwnd_log_at].x.cwnd.cwnd_new_value = error; + sctp_clog[sctp_cwnd_log_at].x.cwnd.inflight = net->flight_size; + sctp_clog[sctp_cwnd_log_at].x.cwnd.cwnd_augment = burst; + sctp_cwnd_log_at++; + if (sctp_cwnd_log_at >= SCTP_STAT_LOG_SIZE) { + sctp_cwnd_log_at = 0; + sctp_cwnd_log_rolled = 1; + } +} + +void +sctp_log_rwnd(uint8_t from, u_int32_t peers_rwnd , u_int32_t snd_size, u_int32_t overhead) +{ + sctp_clog[sctp_cwnd_log_at].from = (u_int8_t)from; + sctp_clog[sctp_cwnd_log_at].event_type = (u_int8_t)SCTP_LOG_EVENT_RWND; + sctp_clog[sctp_cwnd_log_at].x.rwnd.rwnd = peers_rwnd; + sctp_clog[sctp_cwnd_log_at].x.rwnd.send_size = snd_size; + sctp_clog[sctp_cwnd_log_at].x.rwnd.overhead = overhead; + sctp_clog[sctp_cwnd_log_at].x.rwnd.new_rwnd = 0; + sctp_cwnd_log_at++; + if (sctp_cwnd_log_at >= SCTP_STAT_LOG_SIZE) { + sctp_cwnd_log_at = 0; + sctp_cwnd_log_rolled = 1; + } +} + +void +sctp_log_rwnd_set(uint8_t from, u_int32_t peers_rwnd , u_int32_t flight_size, u_int32_t overhead, u_int32_t a_rwndval) +{ + sctp_clog[sctp_cwnd_log_at].from = (u_int8_t)from; + sctp_clog[sctp_cwnd_log_at].event_type = (u_int8_t)SCTP_LOG_EVENT_RWND; + sctp_clog[sctp_cwnd_log_at].x.rwnd.rwnd = peers_rwnd; + sctp_clog[sctp_cwnd_log_at].x.rwnd.send_size = flight_size; + sctp_clog[sctp_cwnd_log_at].x.rwnd.overhead = overhead; + sctp_clog[sctp_cwnd_log_at].x.rwnd.new_rwnd = a_rwndval; + sctp_cwnd_log_at++; + if (sctp_cwnd_log_at >= SCTP_STAT_LOG_SIZE) { + sctp_cwnd_log_at = 0; + sctp_cwnd_log_rolled = 1; + } +} + +void +sctp_log_mbcnt(uint8_t from, u_int32_t total_oq , u_int32_t book, u_int32_t total_mbcnt_q, u_int32_t mbcnt) +{ + sctp_clog[sctp_cwnd_log_at].from = (u_int8_t)from; + sctp_clog[sctp_cwnd_log_at].event_type = (u_int8_t)SCTP_LOG_EVENT_MBCNT; + sctp_clog[sctp_cwnd_log_at].x.mbcnt.total_queue_size = total_oq; + sctp_clog[sctp_cwnd_log_at].x.mbcnt.size_change = book; + sctp_clog[sctp_cwnd_log_at].x.mbcnt.total_queue_mb_size = total_mbcnt_q; + sctp_clog[sctp_cwnd_log_at].x.mbcnt.mbcnt_change = mbcnt; + sctp_cwnd_log_at++; + if (sctp_cwnd_log_at >= SCTP_STAT_LOG_SIZE) { + sctp_cwnd_log_at = 0; + sctp_cwnd_log_rolled = 1; + } +} + +void +sctp_log_block(uint8_t from, struct socket *so, struct sctp_association *asoc) +{ + + sctp_clog[sctp_cwnd_log_at].from = (u_int8_t)from; + sctp_clog[sctp_cwnd_log_at].event_type = (u_int8_t)SCTP_LOG_EVENT_BLOCK; + sctp_clog[sctp_cwnd_log_at].x.blk.maxmb = (u_int16_t)(so->so_snd.sb_mbmax/1024); + sctp_clog[sctp_cwnd_log_at].x.blk.onmb = asoc->total_output_mbuf_queue_size; + sctp_clog[sctp_cwnd_log_at].x.blk.maxsb = (u_int16_t)(so->so_snd.sb_hiwat/1024); + sctp_clog[sctp_cwnd_log_at].x.blk.onsb = asoc->total_output_queue_size; + sctp_clog[sctp_cwnd_log_at].x.blk.send_sent_qcnt = (u_int16_t)(asoc->send_queue_cnt + asoc->sent_queue_cnt); + sctp_clog[sctp_cwnd_log_at].x.blk.stream_qcnt = (u_int16_t)asoc->stream_queue_cnt; + sctp_cwnd_log_at++; + if (sctp_cwnd_log_at >= SCTP_STAT_LOG_SIZE) { + sctp_cwnd_log_at = 0; + sctp_cwnd_log_rolled = 1; + } +} + +int +sctp_fill_stat_log(struct mbuf *m) +{ + struct sctp_cwnd_log_req *req; + int size_limit, num, i, at, cnt_out=0; + + if (m == NULL) + return (EINVAL); + + size_limit = (m->m_len - sizeof(struct sctp_cwnd_log_req)); + if (size_limit < sizeof(struct sctp_cwnd_log)) { + return (EINVAL); + } + req = mtod(m, struct sctp_cwnd_log_req *); + num = size_limit/sizeof(struct sctp_cwnd_log); + if (sctp_cwnd_log_rolled) { + req->num_in_log = SCTP_STAT_LOG_SIZE; + } else { + req->num_in_log = sctp_cwnd_log_at; + /* if the log has not rolled, we don't + * let you have old data. + */ + if (req->end_at > sctp_cwnd_log_at) { + req->end_at = sctp_cwnd_log_at; + } + } + if ((num < SCTP_STAT_LOG_SIZE) && + ((sctp_cwnd_log_rolled) || (sctp_cwnd_log_at > num))) { + /* we can't return all of it */ + if (((req->start_at == 0) && (req->end_at == 0)) || + (req->start_at >= SCTP_STAT_LOG_SIZE) || + (req->end_at >= SCTP_STAT_LOG_SIZE)) { + /* No user request or user is wacked. */ + req->num_ret = num; + req->end_at = sctp_cwnd_log_at - 1; + if ((sctp_cwnd_log_at - num) < 0) { + int cc; + cc = num - sctp_cwnd_log_at; + req->start_at = SCTP_STAT_LOG_SIZE - cc; + } else { + req->start_at = sctp_cwnd_log_at - num; + } + } else { + /* a user request */ + int cc; + if (req->start_at > req->end_at) { + cc = (SCTP_STAT_LOG_SIZE - req->start_at) + + (req->end_at + 1); + } else { + + cc = req->end_at - req->start_at; + } + if (cc < num) { + num = cc; + } + req->num_ret = num; + } + } else { + /* We can return all of it */ + req->start_at = 0; + req->end_at = sctp_cwnd_log_at - 1; + req->num_ret = sctp_cwnd_log_at; + } + for (i = 0, at = req->start_at; i < req->num_ret; i++) { + req->log[i] = sctp_clog[at]; + cnt_out++; + at++; + if (at >= SCTP_STAT_LOG_SIZE) + at = 0; + } + m->m_len = (cnt_out * sizeof(struct sctp_cwnd_log_req)) + sizeof(struct sctp_cwnd_log_req); + return (0); +} + +#endif + +#ifdef SCTP_AUDITING_ENABLED +u_int8_t sctp_audit_data[SCTP_AUDIT_SIZE][2]; +static int sctp_audit_indx = 0; + +static +void sctp_print_audit_report(void) +{ + int i; + int cnt; + cnt = 0; + for (i=sctp_audit_indx;i= SCTP_AUDIT_SIZE) { + sctp_audit_indx = 0; + } + if (inp == NULL) { + sctp_audit_data[sctp_audit_indx][0] = 0xAF; + sctp_audit_data[sctp_audit_indx][1] = 0x01; + sctp_audit_indx++; + if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { + sctp_audit_indx = 0; + } + return; + } + if (stcb == NULL) { + sctp_audit_data[sctp_audit_indx][0] = 0xAF; + sctp_audit_data[sctp_audit_indx][1] = 0x02; + sctp_audit_indx++; + if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { + sctp_audit_indx = 0; + } + return; + } + sctp_audit_data[sctp_audit_indx][0] = 0xA1; + sctp_audit_data[sctp_audit_indx][1] = + (0x000000ff & stcb->asoc.sent_queue_retran_cnt); + sctp_audit_indx++; + if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { + sctp_audit_indx = 0; + } + rep = 0; + tot_book_cnt = 0; + resend_cnt = tot_out = 0; + TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { + if (chk->sent == SCTP_DATAGRAM_RESEND) { + resend_cnt++; + } else if (chk->sent < SCTP_DATAGRAM_RESEND) { + tot_out += chk->book_size; + tot_book_cnt++; + } + } + if (resend_cnt != stcb->asoc.sent_queue_retran_cnt) { + sctp_audit_data[sctp_audit_indx][0] = 0xAF; + sctp_audit_data[sctp_audit_indx][1] = 0xA1; + sctp_audit_indx++; + if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { + sctp_audit_indx = 0; + } + printf("resend_cnt:%d asoc-tot:%d\n", + resend_cnt, stcb->asoc.sent_queue_retran_cnt); + rep = 1; + stcb->asoc.sent_queue_retran_cnt = resend_cnt; + sctp_audit_data[sctp_audit_indx][0] = 0xA2; + sctp_audit_data[sctp_audit_indx][1] = + (0x000000ff & stcb->asoc.sent_queue_retran_cnt); + sctp_audit_indx++; + if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { + sctp_audit_indx = 0; + } + } + if (tot_out != stcb->asoc.total_flight) { + sctp_audit_data[sctp_audit_indx][0] = 0xAF; + sctp_audit_data[sctp_audit_indx][1] = 0xA2; + sctp_audit_indx++; + if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { + sctp_audit_indx = 0; + } + rep = 1; + printf("tot_flt:%d asoc_tot:%d\n", tot_out, + (int)stcb->asoc.total_flight); + stcb->asoc.total_flight = tot_out; + } + if (tot_book_cnt != stcb->asoc.total_flight_count) { + sctp_audit_data[sctp_audit_indx][0] = 0xAF; + sctp_audit_data[sctp_audit_indx][1] = 0xA5; + sctp_audit_indx++; + if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { + sctp_audit_indx = 0; + } + rep = 1; + printf("tot_flt_book:%d\n", tot_book); + + stcb->asoc.total_flight_count = tot_book_cnt; + } + tot_out = 0; + TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { + tot_out += lnet->flight_size; + } + if (tot_out != stcb->asoc.total_flight) { + sctp_audit_data[sctp_audit_indx][0] = 0xAF; + sctp_audit_data[sctp_audit_indx][1] = 0xA3; + sctp_audit_indx++; + if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { + sctp_audit_indx = 0; + } + rep = 1; + printf("real flight:%d net total was %d\n", + stcb->asoc.total_flight, tot_out); + /* now corrective action */ + TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { + tot_out = 0; + TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { + if ((chk->whoTo == lnet) && + (chk->sent < SCTP_DATAGRAM_RESEND)) { + tot_out += chk->book_size; + } + } + if (lnet->flight_size != tot_out) { + printf("net:%x flight was %d corrected to %d\n", + (uint32_t)lnet, lnet->flight_size, tot_out); + lnet->flight_size = tot_out; + } + + } + } + + if (rep) { + sctp_print_audit_report(); + } +} + +void +sctp_audit_log(u_int8_t ev, u_int8_t fd) +{ + int s; +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + sctp_audit_data[sctp_audit_indx][0] = ev; + sctp_audit_data[sctp_audit_indx][1] = fd; + sctp_audit_indx++; + if (sctp_audit_indx >= SCTP_AUDIT_SIZE) { + sctp_audit_indx = 0; + } + splx(s); +} + +#endif + +/* + * a list of sizes based on typical mtu's, used only if next hop + * size not returned. + */ +static int sctp_mtu_sizes[] = { + 68, + 296, + 508, + 512, + 544, + 576, + 1006, + 1492, + 1500, + 1536, + 2002, + 2048, + 4352, + 4464, + 8166, + 17914, + 32000, + 65535 +}; + +int +find_next_best_mtu(int totsz) +{ + int i, perfer; + /* + * if we are in here we must find the next best fit based on the + * size of the dg that failed to be sent. + */ + perfer = 0; + for (i = 0; i < NUMBER_OF_MTU_SIZES; i++) { + if (totsz < sctp_mtu_sizes[i]) { + perfer = i - 1; + if (perfer < 0) + perfer = 0; + break; + } + } + return (sctp_mtu_sizes[perfer]); +} + +void +sctp_fill_random_store(struct sctp_pcb *m) +{ + /* + * Here we use the MD5/SHA-1 to hash with our good randomNumbers + * and our counter. The result becomes our good random numbers and + * we then setup to give these out. Note that we do no lockig + * to protect this. This is ok, since if competing folks call + * this we will get more gobbled gook in the random store whic + * is what we want. There is a danger that two guys will use + * the same random numbers, but thats ok too since that + * is random as well :-> + */ + m->store_at = 0; + sctp_hash_digest((char *)m->random_numbers, sizeof(m->random_numbers), + (char *)&m->random_counter, sizeof(m->random_counter), + (char *)m->random_store); + m->random_counter++; +} + +uint32_t +sctp_select_initial_TSN(struct sctp_pcb *m) +{ + /* + * A true implementation should use random selection process to + * get the initial stream sequence number, using RFC1750 as a + * good guideline + */ + u_long x, *xp; + uint8_t *p; + + if (m->initial_sequence_debug != 0) { + u_int32_t ret; + ret = m->initial_sequence_debug; + m->initial_sequence_debug++; + return (ret); + } + if ((m->store_at+sizeof(u_long)) > SCTP_SIGNATURE_SIZE) { + /* Refill the random store */ + sctp_fill_random_store(m); + } + p = &m->random_store[(int)m->store_at]; + xp = (u_long *)p; + x = *xp; + m->store_at += sizeof(u_long); + return (x); +} + +u_int32_t sctp_select_a_tag(struct sctp_inpcb *m) +{ + u_long x, not_done; + struct timeval now; + + SCTP_GETTIME_TIMEVAL(&now); + not_done = 1; + while (not_done) { + x = sctp_select_initial_TSN(&m->sctp_ep); + if (x == 0) { + /* we never use 0 */ + continue; + } + if (sctp_is_vtag_good(m, x, &now)) { + not_done = 0; + } + } + return (x); +} + + +int +sctp_init_asoc(struct sctp_inpcb *m, struct sctp_association *asoc, + int for_a_init, uint32_t override_tag ) +{ + /* + * Anything set to zero is taken care of by the allocation + * routine's bzero + */ + + /* + * Up front select what scoping to apply on addresses I tell my peer + * Not sure what to do with these right now, we will need to come up + * with a way to set them. We may need to pass them through from the + * caller in the sctp_aloc_assoc() function. + */ + int i; + /* init all variables to a known value.*/ + asoc->state = SCTP_STATE_INUSE; + asoc->max_burst = m->sctp_ep.max_burst; + asoc->heart_beat_delay = m->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT]; + asoc->cookie_life = m->sctp_ep.def_cookie_life; + + if (override_tag) { + asoc->my_vtag = override_tag; + } else { + asoc->my_vtag = sctp_select_a_tag(m); + } + asoc->asconf_seq_out = asoc->str_reset_seq_out = asoc->init_seq_number = asoc->sending_seq = + sctp_select_initial_TSN(&m->sctp_ep); + asoc->t3timeout_highest_marked = asoc->asconf_seq_out; + /* we are opptimisitic here */ + asoc->peer_supports_asconf = 1; + asoc->peer_supports_asconf_setprim = 1; + asoc->peer_supports_pktdrop = 1; + + asoc->sent_queue_retran_cnt = 0; + /* This will need to be adjusted */ + asoc->last_cwr_tsn = asoc->init_seq_number - 1; + asoc->last_acked_seq = asoc->init_seq_number - 1; + asoc->advanced_peer_ack_point = asoc->last_acked_seq; + asoc->asconf_seq_in = asoc->last_acked_seq; + + /* here we are different, we hold the next one we expect */ + asoc->str_reset_seq_in = asoc->last_acked_seq + 1; + + asoc->initial_init_rto_max = m->sctp_ep.initial_init_rto_max; + asoc->initial_rto = m->sctp_ep.initial_rto; + + asoc->max_init_times = m->sctp_ep.max_init_times; + asoc->max_send_times = m->sctp_ep.max_send_times; + asoc->def_net_failure = m->sctp_ep.def_net_failure; + + /* ECN Nonce initialization */ + asoc->ecn_nonce_allowed = 0; + asoc->receiver_nonce_sum = 1; + asoc->nonce_sum_expect_base = 1; + asoc->nonce_sum_check = 1; + asoc->nonce_resync_tsn = 0; + asoc->nonce_wait_for_ecne = 0; + asoc->nonce_wait_tsn = 0; + + if (m->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + struct in6pcb *inp6; + + + /* Its a V6 socket */ + inp6 = (struct in6pcb *)m; + asoc->ipv6_addr_legal = 1; + /* Now look at the binding flag to see if V4 will be legal */ + if ( +#if defined(__OpenBSD__) + (0) /* we always do dual bind */ +#elif defined (__NetBSD__) + (inp6->in6p_flags & IN6P_IPV6_V6ONLY) +#else + (inp6->inp_flags & IN6P_IPV6_V6ONLY) +#endif + == 0) { + asoc->ipv4_addr_legal = 1; + } else { + /* V4 addresses are NOT legal on the association */ + asoc->ipv4_addr_legal = 0; + } + } else { + /* Its a V4 socket, no - V6 */ + asoc->ipv4_addr_legal = 1; + asoc->ipv6_addr_legal = 0; + } + + + asoc->my_rwnd = max(m->sctp_socket->so_rcv.sb_hiwat, SCTP_MINIMAL_RWND); + asoc->peers_rwnd = m->sctp_socket->so_rcv.sb_hiwat; + + asoc->smallest_mtu = m->sctp_frag_point; + asoc->minrto = m->sctp_ep.sctp_minrto; + asoc->maxrto = m->sctp_ep.sctp_maxrto; + + LIST_INIT(&asoc->sctp_local_addr_list); + TAILQ_INIT(&asoc->nets); + TAILQ_INIT(&asoc->pending_reply_queue); + asoc->last_asconf_ack_sent = NULL; + /* Setup to fill the hb random cache at first HB */ + asoc->hb_random_idx = 4; + + asoc->sctp_autoclose_ticks = m->sctp_ep.auto_close_time; + + /* + * Now the stream parameters, here we allocate space for all + * streams that we request by default. + */ + asoc->streamoutcnt = asoc->pre_open_streams = + m->sctp_ep.pre_open_stream_count; + MALLOC(asoc->strmout, struct sctp_stream_out *, asoc->streamoutcnt * + sizeof(struct sctp_stream_out), M_PCB, M_NOWAIT); + if (asoc->strmout == NULL) { + /* big trouble no memory */ + return (ENOMEM); + } + for (i = 0; i < asoc->streamoutcnt; i++) { + /* + * inbound side must be set to 0xffff, + * also NOTE when we get the INIT-ACK back (for INIT sender) + * we MUST reduce the count (streamoutcnt) but first check + * if we sent to any of the upper streams that were dropped + * (if some were). Those that were dropped must be notified + * to the upper layer as failed to send. + */ + asoc->strmout[i].next_sequence_sent = 0x0; + TAILQ_INIT(&asoc->strmout[i].outqueue); + asoc->strmout[i].stream_no = i; + asoc->strmout[i].next_spoke.tqe_next = 0; + asoc->strmout[i].next_spoke.tqe_prev = 0; + } + /* Now the mapping array */ + asoc->mapping_array_size = SCTP_INITIAL_MAPPING_ARRAY; +#ifdef __NetBSD__ + MALLOC(asoc->mapping_array, u_int8_t *, SCTP_INITIAL_MAPPING_ARRAY, + M_PCB, M_NOWAIT); +#else + MALLOC(asoc->mapping_array, u_int8_t *, asoc->mapping_array_size, + M_PCB, M_NOWAIT); +#endif + if (asoc->mapping_array == NULL) { + FREE(asoc->strmout, M_PCB); + return (ENOMEM); + } + memset(asoc->mapping_array, 0, asoc->mapping_array_size); + /* Now the init of the other outqueues */ + TAILQ_INIT(&asoc->out_wheel); + TAILQ_INIT(&asoc->control_send_queue); + TAILQ_INIT(&asoc->send_queue); + TAILQ_INIT(&asoc->sent_queue); + TAILQ_INIT(&asoc->reasmqueue); + TAILQ_INIT(&asoc->delivery_queue); + asoc->max_inbound_streams = m->sctp_ep.max_open_streams_intome; + + TAILQ_INIT(&asoc->asconf_queue); + return (0); +} + +int +sctp_expand_mapping_array(struct sctp_association *asoc) +{ + /* mapping array needs to grow */ + u_int8_t *new_array; + uint16_t new_size; + + new_size = asoc->mapping_array_size + SCTP_MAPPING_ARRAY_INCR; +#ifdef __NetBSD__ + MALLOC(new_array, u_int8_t *, asoc->mapping_array_size + + SCTP_MAPPING_ARRAY_INCR, M_PCB, M_NOWAIT); +#else + MALLOC(new_array, u_int8_t *, new_size, M_PCB, M_NOWAIT); +#endif + if (new_array == NULL) { + /* can't get more, forget it */ + printf("No memory for expansion of SCTP mapping array %d\n", + new_size); + return (-1); + } + memset(new_array, 0, new_size); + memcpy(new_array, asoc->mapping_array, asoc->mapping_array_size); + FREE(asoc->mapping_array, M_PCB); + asoc->mapping_array = new_array; + asoc->mapping_array_size = new_size; + return (0); +} + +static void +sctp_timeout_handler(void *t) +{ + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + struct sctp_nets *net; + struct sctp_timer *tmr; + int s, did_output, typ; +#if defined(__APPLE__) + boolean_t funnel_state; + + /* get BSD kernel funnel/mutex */ + funnel_state = thread_funnel_set(network_flock, TRUE); +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + tmr = (struct sctp_timer *)t; + inp = (struct sctp_inpcb *)tmr->ep; + stcb = (struct sctp_tcb *)tmr->tcb; + net = (struct sctp_nets *)tmr->net; + did_output = 1; + + +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xF0, (u_int8_t)tmr->type); + sctp_auditing(3, inp, stcb, net); +#endif + sctp_pegs[SCTP_TIMERS_EXP]++; + + if (inp == NULL) { + return; + } + + SCTP_INP_WLOCK(inp); + if (inp->sctp_socket == 0) { + splx(s); +#if defined(__APPLE__) + /* release BSD kernel funnel/mutex */ + (void) thread_funnel_set(network_flock, FALSE); +#endif + SCTP_INP_WUNLOCK(inp); + return; + } + if (stcb) { + if (stcb->asoc.state == 0) { + splx(s); +#if defined(__APPLE__) + /* release BSD kernel funnel/mutex */ + (void) thread_funnel_set(network_flock, FALSE); +#endif + SCTP_INP_WUNLOCK(inp); + return; + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("Timer type %d goes off\n", tmr->type); + } +#endif /* SCTP_DEBUG */ +#ifndef __NetBSD__ + if (!callout_active(&tmr->timer)) { + splx(s); +#if defined(__APPLE__) + /* release BSD kernel funnel/mutex */ + (void) thread_funnel_set(network_flock, FALSE); +#endif + SCTP_INP_WUNLOCK(inp); + return; + } +#endif +#if defined(__APPLE__) + /* clear the callout pending status here */ + callout_stop(&tmr->timer); +#endif + if (stcb) { + SCTP_TCB_LOCK(stcb); + } + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + + typ = tmr->type; + switch (tmr->type) { + case SCTP_TIMER_TYPE_ITERATOR: + { + struct sctp_iterator *it; + it = (struct sctp_iterator *)inp; + sctp_iterator_timer(it); + } + break; + /* call the handler for the appropriate timer type */ + case SCTP_TIMER_TYPE_SEND: + sctp_pegs[SCTP_TMIT_TIMER]++; + stcb->asoc.num_send_timers_up--; + if (stcb->asoc.num_send_timers_up < 0) { + stcb->asoc.num_send_timers_up = 0; + } + if (sctp_t3rxt_timer(inp, stcb, net)) { + /* no need to unlock on tcb its gone */ + + goto out_decr; + } +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(4, inp, stcb, net); +#endif + sctp_chunk_output(inp, stcb, 1); + if ((stcb->asoc.num_send_timers_up == 0) && + (stcb->asoc.sent_queue_cnt > 0) + ) { + struct sctp_tmit_chunk *chk; + /* + * safeguard. If there on some on the sent queue + * somewhere but no timers running something is + * wrong... so we start a timer on the first chunk + * on the send queue on whatever net it is sent to. + */ + sctp_pegs[SCTP_T3_SAFEGRD]++; + chk = TAILQ_FIRST(&stcb->asoc.sent_queue); + sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb, + chk->whoTo); + } + break; + case SCTP_TIMER_TYPE_INIT: + if (sctp_t1init_timer(inp, stcb, net)) { + /* no need to unlock on tcb its gone */ + goto out_decr; + } + /* We do output but not here */ + did_output = 0; + break; + case SCTP_TIMER_TYPE_RECV: + sctp_pegs[SCTP_RECV_TIMER]++; + sctp_send_sack(stcb); +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(4, inp, stcb, net); +#endif + sctp_chunk_output(inp, stcb, 4); + break; + case SCTP_TIMER_TYPE_SHUTDOWN: + if (sctp_shutdown_timer(inp, stcb, net) ) { + /* no need to unlock on tcb its gone */ + goto out_decr; + } +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(4, inp, stcb, net); +#endif + sctp_chunk_output(inp, stcb, 5); + break; + case SCTP_TIMER_TYPE_HEARTBEAT: + if (sctp_heartbeat_timer(inp, stcb, net)) { + /* no need to unlock on tcb its gone */ + goto out_decr; + } +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(4, inp, stcb, net); +#endif + sctp_chunk_output(inp, stcb, 6); + break; + case SCTP_TIMER_TYPE_COOKIE: + if (sctp_cookie_timer(inp, stcb, net)) { + /* no need to unlock on tcb its gone */ + goto out_decr; + } +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(4, inp, stcb, net); +#endif + sctp_chunk_output(inp, stcb, 1); + break; + case SCTP_TIMER_TYPE_NEWCOOKIE: + { + struct timeval tv; + int i, secret; + SCTP_GETTIME_TIMEVAL(&tv); + SCTP_INP_WLOCK(inp); + inp->sctp_ep.time_of_secret_change = tv.tv_sec; + inp->sctp_ep.last_secret_number = + inp->sctp_ep.current_secret_number; + inp->sctp_ep.current_secret_number++; + if (inp->sctp_ep.current_secret_number >= + SCTP_HOW_MANY_SECRETS) { + inp->sctp_ep.current_secret_number = 0; + } + secret = (int)inp->sctp_ep.current_secret_number; + for (i = 0; i < SCTP_NUMBER_OF_SECRETS; i++) { + inp->sctp_ep.secret_key[secret][i] = + sctp_select_initial_TSN(&inp->sctp_ep); + } + SCTP_INP_WUNLOCK(inp); + sctp_timer_start(SCTP_TIMER_TYPE_NEWCOOKIE, inp, stcb, net); + } + did_output = 0; + break; + case SCTP_TIMER_TYPE_PATHMTURAISE: + sctp_pathmtu_timer(inp, stcb, net); + did_output = 0; + break; + case SCTP_TIMER_TYPE_SHUTDOWNACK: + if (sctp_shutdownack_timer(inp, stcb, net)) { + /* no need to unlock on tcb its gone */ + goto out_decr; + } +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(4, inp, stcb, net); +#endif + sctp_chunk_output(inp, stcb, 7); + break; + case SCTP_TIMER_TYPE_SHUTDOWNGUARD: + sctp_abort_an_association(inp, stcb, + SCTP_SHUTDOWN_GUARD_EXPIRES, NULL); + /* no need to unlock on tcb its gone */ + goto out_decr; + break; + + case SCTP_TIMER_TYPE_STRRESET: + if (sctp_strreset_timer(inp, stcb, net)) { + /* no need to unlock on tcb its gone */ + goto out_decr; + } + sctp_chunk_output(inp, stcb, 9); + break; + + case SCTP_TIMER_TYPE_ASCONF: + if (sctp_asconf_timer(inp, stcb, net)) { + /* no need to unlock on tcb its gone */ + goto out_decr; + } +#ifdef SCTP_AUDITING_ENABLED + sctp_auditing(4, inp, stcb, net); +#endif + sctp_chunk_output(inp, stcb, 8); + break; + + case SCTP_TIMER_TYPE_AUTOCLOSE: + sctp_autoclose_timer(inp, stcb, net); + sctp_chunk_output(inp, stcb, 10); + did_output = 0; + break; + case SCTP_TIMER_TYPE_INPKILL: + /* special case, take away our + * increment since WE are the killer + */ + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + sctp_timer_stop(SCTP_TIMER_TYPE_INPKILL, inp, NULL, NULL); + sctp_inpcb_free(inp, 1); + goto out_no_decr; + break; + default: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("sctp_timeout_handler:unknown timer %d\n", + tmr->type); + } +#endif /* SCTP_DEBUG */ + break; + }; +#ifdef SCTP_AUDITING_ENABLED + sctp_audit_log(0xF1, (u_int8_t)tmr->type); + sctp_auditing(5, inp, stcb, net); +#endif + if (did_output) { + /* + * Now we need to clean up the control chunk chain if an + * ECNE is on it. It must be marked as UNSENT again so next + * call will continue to send it until such time that we get + * a CWR, to remove it. It is, however, less likely that we + * will find a ecn echo on the chain though. + */ + sctp_fix_ecn_echo(&stcb->asoc); + } + if (stcb) { + SCTP_TCB_UNLOCK(stcb); + } + out_decr: + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + + out_no_decr: + +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("Timer now complete (type %d)\n", typ); + } +#endif /* SCTP_DEBUG */ + + splx(s); +#if defined(__APPLE__) + /* release BSD kernel funnel/mutex */ + (void) thread_funnel_set(network_flock, FALSE); +#endif +} + +int +sctp_timer_start(int t_type, struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + int to_ticks; + struct sctp_timer *tmr; + + if (inp == NULL) + return (EFAULT); + + to_ticks = 0; + + tmr = NULL; + switch (t_type) { + case SCTP_TIMER_TYPE_ITERATOR: + { + struct sctp_iterator *it; + it = (struct sctp_iterator *)inp; + tmr = &it->tmr; + to_ticks = SCTP_ITERATOR_TICKS; + } + break; + case SCTP_TIMER_TYPE_SEND: + /* Here we use the RTO timer */ + { + int rto_val; + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + tmr = &net->rxt_timer; + if (net->RTO == 0) { + rto_val = stcb->asoc.initial_rto; + } else { + rto_val = net->RTO; + } + to_ticks = MSEC_TO_TICKS(rto_val); + } + break; + case SCTP_TIMER_TYPE_INIT: + /* + * Here we use the INIT timer default + * usually about 1 minute. + */ + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + tmr = &net->rxt_timer; + if (net->RTO == 0) { + to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); + } else { + to_ticks = MSEC_TO_TICKS(net->RTO); + } + break; + case SCTP_TIMER_TYPE_RECV: + /* + * Here we use the Delayed-Ack timer value from the inp + * ususually about 200ms. + */ + if (stcb == NULL) { + return (EFAULT); + } + tmr = &stcb->asoc.dack_timer; + to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV]; + break; + case SCTP_TIMER_TYPE_SHUTDOWN: + /* Here we use the RTO of the destination. */ + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + + if (net->RTO == 0) { + to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); + } else { + to_ticks = MSEC_TO_TICKS(net->RTO); + } + tmr = &net->rxt_timer; + break; + case SCTP_TIMER_TYPE_HEARTBEAT: + /* + * the net is used here so that we can add in the RTO. + * Even though we use a different timer. We also add the + * HB timer PLUS a random jitter. + */ + if (stcb == NULL) { + return (EFAULT); + } + { + uint32_t rndval; + uint8_t this_random; + int cnt_of_unconf=0; + struct sctp_nets *lnet; + + TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { + if (lnet->dest_state & SCTP_ADDR_UNCONFIRMED) { + cnt_of_unconf++; + } + } +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("HB timer to start unconfirmed:%d hb_delay:%d\n", + cnt_of_unconf, stcb->asoc.heart_beat_delay); + } +#endif + if (stcb->asoc.hb_random_idx > 3) { + rndval = sctp_select_initial_TSN(&inp->sctp_ep); + memcpy(stcb->asoc.hb_random_values, &rndval, + sizeof(stcb->asoc.hb_random_values)); + this_random = stcb->asoc.hb_random_values[0]; + stcb->asoc.hb_random_idx = 0; + stcb->asoc.hb_ect_randombit = 0; + } else { + this_random = stcb->asoc.hb_random_values[stcb->asoc.hb_random_idx]; + stcb->asoc.hb_random_idx++; + stcb->asoc.hb_ect_randombit = 0; + } + /* + * this_random will be 0 - 256 ms + * RTO is in ms. + */ + if ((stcb->asoc.heart_beat_delay == 0) && + (cnt_of_unconf == 0)) { + /* no HB on this inp after confirmations */ + return (0); + } + if (net) { + struct sctp_nets *lnet; + int delay; + delay = stcb->asoc.heart_beat_delay; + TAILQ_FOREACH(lnet, &stcb->asoc.nets, sctp_next) { + if ((lnet->dest_state & SCTP_ADDR_UNCONFIRMED) && + ((lnet->dest_state & SCTP_ADDR_OUT_OF_SCOPE) == 0) && + (lnet->dest_state & SCTP_ADDR_REACHABLE)) { + delay = 0; + } + } + if (net->RTO == 0) { + /* Never been checked */ + to_ticks = this_random + stcb->asoc.initial_rto + delay; + } else { + /* set rto_val to the ms */ + to_ticks = delay + net->RTO + this_random; + } + } else { + if (cnt_of_unconf) { + to_ticks = this_random + stcb->asoc.initial_rto; + } else { + to_ticks = stcb->asoc.heart_beat_delay + this_random + stcb->asoc.initial_rto; + } + } + /* + * Now we must convert the to_ticks that are now in + * ms to ticks. + */ + to_ticks *= hz; + to_ticks /= 1000; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("Timer to expire in %d ticks\n", to_ticks); + } +#endif + tmr = &stcb->asoc.hb_timer; + } + break; + case SCTP_TIMER_TYPE_COOKIE: + /* + * Here we can use the RTO timer from the network since + * one RTT was compelete. If a retran happened then we will + * be using the RTO initial value. + */ + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + if (net->RTO == 0) { + to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); + } else { + to_ticks = MSEC_TO_TICKS(net->RTO); + } + tmr = &net->rxt_timer; + break; + case SCTP_TIMER_TYPE_NEWCOOKIE: + /* + * nothing needed but the endpoint here + * ususually about 60 minutes. + */ + tmr = &inp->sctp_ep.signature_change; + to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_SIGNATURE]; + break; + case SCTP_TIMER_TYPE_INPKILL: + /* + * The inp is setup to die. We re-use the + * signature_chage timer since that has + * stopped and we are in the GONE state. + */ + tmr = &inp->sctp_ep.signature_change; + to_ticks = (SCTP_INP_KILL_TIMEOUT * hz) / 1000; + break; + case SCTP_TIMER_TYPE_PATHMTURAISE: + /* + * Here we use the value found in the EP for PMTU + * ususually about 10 minutes. + */ + if (stcb == NULL) { + return (EFAULT); + } + if (net == NULL) { + return (EFAULT); + } + to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_PMTU]; + tmr = &net->pmtu_timer; + break; + case SCTP_TIMER_TYPE_SHUTDOWNACK: + /* Here we use the RTO of the destination */ + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + if (net->RTO == 0) { + to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); + } else { + to_ticks = MSEC_TO_TICKS(net->RTO); + } + tmr = &net->rxt_timer; + break; + case SCTP_TIMER_TYPE_SHUTDOWNGUARD: + /* + * Here we use the endpoints shutdown guard timer + * usually about 3 minutes. + */ + if (stcb == NULL) { + return (EFAULT); + } + to_ticks = inp->sctp_ep.sctp_timeoutticks[SCTP_TIMER_MAXSHUTDOWN]; + tmr = &stcb->asoc.shut_guard_timer; + break; + case SCTP_TIMER_TYPE_STRRESET: + /* + * Here the timer comes from the inp + * but its value is from the RTO. + */ + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + if (net->RTO == 0) { + to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); + } else { + to_ticks = MSEC_TO_TICKS(net->RTO); + } + tmr = &stcb->asoc.strreset_timer; + break; + + case SCTP_TIMER_TYPE_ASCONF: + /* + * Here the timer comes from the inp + * but its value is from the RTO. + */ + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + if (net->RTO == 0) { + to_ticks = MSEC_TO_TICKS(stcb->asoc.initial_rto); + } else { + to_ticks = MSEC_TO_TICKS(net->RTO); + } + tmr = &stcb->asoc.asconf_timer; + break; + case SCTP_TIMER_TYPE_AUTOCLOSE: + if (stcb == NULL) { + return (EFAULT); + } + if (stcb->asoc.sctp_autoclose_ticks == 0) { + /* Really an error since stcb is NOT set to autoclose */ + return (0); + } + to_ticks = stcb->asoc.sctp_autoclose_ticks; + tmr = &stcb->asoc.autoclose_timer; + break; + default: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("sctp_timer_start:Unknown timer type %d\n", + t_type); + } +#endif /* SCTP_DEBUG */ + return (EFAULT); + break; + }; + if ((to_ticks <= 0) || (tmr == NULL)) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("sctp_timer_start:%d:software error to_ticks:%d tmr:%p not set ??\n", + t_type, to_ticks, tmr); + } +#endif /* SCTP_DEBUG */ + return (EFAULT); + } + if (callout_pending(&tmr->timer)) { + /* + * we do NOT allow you to have it already running. + * if it is we leave the current one up unchanged + */ + return (EALREADY); + } + /* At this point we can proceed */ + if (t_type == SCTP_TIMER_TYPE_SEND) { + stcb->asoc.num_send_timers_up++; + } + tmr->type = t_type; + tmr->ep = (void *)inp; + tmr->tcb = (void *)stcb; + tmr->net = (void *)net; + callout_reset(&tmr->timer, to_ticks, sctp_timeout_handler, tmr); + return (0); +} + +int +sctp_timer_stop(int t_type, struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + struct sctp_timer *tmr; + + if (inp == NULL) + return (EFAULT); + + tmr = NULL; + switch (t_type) { + case SCTP_TIMER_TYPE_ITERATOR: + { + struct sctp_iterator *it; + it = (struct sctp_iterator *)inp; + tmr = &it->tmr; + } + break; + case SCTP_TIMER_TYPE_SEND: + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + tmr = &net->rxt_timer; + break; + case SCTP_TIMER_TYPE_INIT: + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + tmr = &net->rxt_timer; + break; + case SCTP_TIMER_TYPE_RECV: + if (stcb == NULL) { + return (EFAULT); + } + tmr = &stcb->asoc.dack_timer; + break; + case SCTP_TIMER_TYPE_SHUTDOWN: + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + tmr = &net->rxt_timer; + break; + case SCTP_TIMER_TYPE_HEARTBEAT: + if (stcb == NULL) { + return (EFAULT); + } + tmr = &stcb->asoc.hb_timer; + break; + case SCTP_TIMER_TYPE_COOKIE: + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + tmr = &net->rxt_timer; + break; + case SCTP_TIMER_TYPE_NEWCOOKIE: + /* nothing needed but the endpoint here */ + tmr = &inp->sctp_ep.signature_change; + /* We re-use the newcookie timer for + * the INP kill timer. We must assure + * that we do not kill it by accident. + */ + break; + case SCTP_TIMER_TYPE_INPKILL: + /* + * The inp is setup to die. We re-use the + * signature_chage timer since that has + * stopped and we are in the GONE state. + */ + tmr = &inp->sctp_ep.signature_change; + break; + case SCTP_TIMER_TYPE_PATHMTURAISE: + if (stcb == NULL) { + return (EFAULT); + } + if (net == NULL) { + return (EFAULT); + } + tmr = &net->pmtu_timer; + break; + case SCTP_TIMER_TYPE_SHUTDOWNACK: + if ((stcb == NULL) || (net == NULL)) { + return (EFAULT); + } + tmr = &net->rxt_timer; + break; + case SCTP_TIMER_TYPE_SHUTDOWNGUARD: + if (stcb == NULL) { + return (EFAULT); + } + tmr = &stcb->asoc.shut_guard_timer; + break; + case SCTP_TIMER_TYPE_STRRESET: + if (stcb == NULL) { + return (EFAULT); + } + tmr = &stcb->asoc.strreset_timer; + break; + case SCTP_TIMER_TYPE_ASCONF: + if (stcb == NULL) { + return (EFAULT); + } + tmr = &stcb->asoc.asconf_timer; + break; + case SCTP_TIMER_TYPE_AUTOCLOSE: + if (stcb == NULL) { + return (EFAULT); + } + tmr = &stcb->asoc.autoclose_timer; + break; + default: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_TIMER1) { + printf("sctp_timer_stop:Unknown timer type %d\n", + t_type); + } +#endif /* SCTP_DEBUG */ + break; + }; + if (tmr == NULL) + return (EFAULT); + + if ((tmr->type != t_type) && tmr->type) { + /* + * Ok we have a timer that is under joint use. Cookie timer + * per chance with the SEND timer. We therefore are NOT + * running the timer that the caller wants stopped. So just + * return. + */ + return (0); + } + if (t_type == SCTP_TIMER_TYPE_SEND) { + stcb->asoc.num_send_timers_up--; + if (stcb->asoc.num_send_timers_up < 0) { + stcb->asoc.num_send_timers_up = 0; + } + } + callout_stop(&tmr->timer); + return (0); +} + +#ifdef SCTP_USE_ADLER32 +static uint32_t +update_adler32(uint32_t adler, uint8_t *buf, int32_t len) +{ + u_int32_t s1 = adler & 0xffff; + u_int32_t s2 = (adler >> 16) & 0xffff; + int n; + + for (n = 0; n < len; n++, buf++) { + /* s1 = (s1 + buf[n]) % BASE */ + /* first we add */ + s1 = (s1 + *buf); + /* + * now if we need to, we do a mod by subtracting. It seems + * a bit faster since I really will only ever do one subtract + * at the MOST, since buf[n] is a max of 255. + */ + if (s1 >= SCTP_ADLER32_BASE) { + s1 -= SCTP_ADLER32_BASE; + } + /* s2 = (s2 + s1) % BASE */ + /* first we add */ + s2 = (s2 + s1); + /* + * again, it is more efficent (it seems) to subtract since + * the most s2 will ever be is (BASE-1 + BASE-1) in the worse + * case. This would then be (2 * BASE) - 2, which will still + * only do one subtract. On Intel this is much better to do + * this way and avoid the divide. Have not -pg'd on sparc. + */ + if (s2 >= SCTP_ADLER32_BASE) { + s2 -= SCTP_ADLER32_BASE; + } + } + /* Return the adler32 of the bytes buf[0..len-1] */ + return ((s2 << 16) + s1); +} + +#endif + + +u_int32_t +sctp_calculate_len(struct mbuf *m) +{ + u_int32_t tlen=0; + struct mbuf *at; + at = m; + while (at) { + tlen += at->m_len; + at = at->m_next; + } + return (tlen); +} + +#if defined(SCTP_WITH_NO_CSUM) + +uint32_t +sctp_calculate_sum(struct mbuf *m, int32_t *pktlen, uint32_t offset) +{ + /* + * given a mbuf chain with a packetheader offset by 'offset' + * pointing at a sctphdr (with csum set to 0) go through + * the chain of m_next's and calculate the SCTP checksum. + * This is currently Adler32 but will change to CRC32x + * soon. Also has a side bonus calculate the total length + * of the mbuf chain. + * Note: if offset is greater than the total mbuf length, + * checksum=1, pktlen=0 is returned (ie. no real error code) + */ + if (pktlen == NULL) + return (0); + *pktlen = sctp_calculate_len(m); + return (0); +} + +#elif defined(SCTP_USE_INCHKSUM) + +#include + +uint32_t +sctp_calculate_sum(struct mbuf *m, int32_t *pktlen, uint32_t offset) +{ + /* + * given a mbuf chain with a packetheader offset by 'offset' + * pointing at a sctphdr (with csum set to 0) go through + * the chain of m_next's and calculate the SCTP checksum. + * This is currently Adler32 but will change to CRC32x + * soon. Also has a side bonus calculate the total length + * of the mbuf chain. + * Note: if offset is greater than the total mbuf length, + * checksum=1, pktlen=0 is returned (ie. no real error code) + */ + int32_t tlen=0; + struct mbuf *at; + uint32_t the_sum, retsum; + + at = m; + while (at) { + tlen += at->m_len; + at = at->m_next; + } + the_sum = (uint32_t)(in_cksum_skip(m, tlen, offset)); + if (pktlen != NULL) + *pktlen = (tlen-offset); + retsum = htons(the_sum); + return (the_sum); +} + +#else + +uint32_t +sctp_calculate_sum(struct mbuf *m, int32_t *pktlen, uint32_t offset) +{ + /* + * given a mbuf chain with a packetheader offset by 'offset' + * pointing at a sctphdr (with csum set to 0) go through + * the chain of m_next's and calculate the SCTP checksum. + * This is currently Adler32 but will change to CRC32x + * soon. Also has a side bonus calculate the total length + * of the mbuf chain. + * Note: if offset is greater than the total mbuf length, + * checksum=1, pktlen=0 is returned (ie. no real error code) + */ + int32_t tlen=0; +#ifdef SCTP_USE_ADLER32 + uint32_t base = 1L; +#else + uint32_t base = 0xffffffff; +#endif /* SCTP_USE_ADLER32 */ + struct mbuf *at; + at = m; + /* find the correct mbuf and offset into mbuf */ + while ((at != NULL) && (offset > (uint32_t)at->m_len)) { + offset -= at->m_len; /* update remaining offset left */ + at = at->m_next; + } + + while (at != NULL) { +#ifdef SCTP_USE_ADLER32 + base = update_adler32(base, at->m_data + offset, + at->m_len - offset); +#else + base = update_crc32(base, at->m_data + offset, + at->m_len - offset); +#endif /* SCTP_USE_ADLER32 */ + tlen += at->m_len - offset; + /* we only offset once into the first mbuf */ + if (offset) { + offset = 0; + } + at = at->m_next; + } + if (pktlen != NULL) { + *pktlen = tlen; + } +#ifdef SCTP_USE_ADLER32 + /* Adler32 */ + base = htonl(base); +#else + /* CRC-32c */ + base = sctp_csum_finalize(base); +#endif + return (base); +} + + +#endif + +void +sctp_mtu_size_reset(struct sctp_inpcb *inp, + struct sctp_association *asoc, u_long mtu) +{ + /* + * Reset the P-MTU size on this association, this involves changing + * the asoc MTU, going through ANY chunk+overhead larger than mtu + * to allow the DF flag to be cleared. + */ + struct sctp_tmit_chunk *chk; + struct sctp_stream_out *strm; + unsigned int eff_mtu, ovh; + asoc->smallest_mtu = mtu; + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { + ovh = SCTP_MIN_OVERHEAD; + } else { + ovh = SCTP_MIN_V4_OVERHEAD; + } + eff_mtu = mtu - ovh; + /* Now mark any chunks that need to let IP fragment */ + TAILQ_FOREACH(strm, &asoc->out_wheel, next_spoke) { + TAILQ_FOREACH(chk, &strm->outqueue, sctp_next) { + if (chk->send_size > eff_mtu) { + chk->flags &= SCTP_DONT_FRAGMENT; + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + } + } + } + TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) { + if (chk->send_size > eff_mtu) { + chk->flags &= SCTP_DONT_FRAGMENT; + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + } + } + TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) { + if (chk->send_size > eff_mtu) { + chk->flags &= SCTP_DONT_FRAGMENT; + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + } + } +} + + +/* + * given an association and starting time of the current RTT period + * return RTO in number of usecs + * net should point to the current network + */ +u_int32_t +sctp_calculate_rto(struct sctp_tcb *stcb, + struct sctp_association *asoc, + struct sctp_nets *net, + struct timeval *old) +{ + /* + * given an association and the starting time of the current RTT + * period (in value1/value2) return RTO in number of usecs. + */ + int calc_time = 0; + int o_calctime; + unsigned int new_rto = 0; + int first_measure = 0; + struct timeval now; + + /************************/ + /* 1. calculate new RTT */ + /************************/ + /* get the current time */ + SCTP_GETTIME_TIMEVAL(&now); + /* compute the RTT value */ + if ((u_long)now.tv_sec > (u_long)old->tv_sec) { + calc_time = ((u_long)now.tv_sec - (u_long)old->tv_sec) * 1000; + if ((u_long)now.tv_usec > (u_long)old->tv_usec) { + calc_time += (((u_long)now.tv_usec - + (u_long)old->tv_usec)/1000); + } else if ((u_long)now.tv_usec < (u_long)old->tv_usec) { + /* Borrow 1,000ms from current calculation */ + calc_time -= 1000; + /* Add in the slop over */ + calc_time += ((int)now.tv_usec/1000); + /* Add in the pre-second ms's */ + calc_time += (((int)1000000 - (int)old->tv_usec)/1000); + } + } else if ((u_long)now.tv_sec == (u_long)old->tv_sec) { + if ((u_long)now.tv_usec > (u_long)old->tv_usec) { + calc_time = ((u_long)now.tv_usec - + (u_long)old->tv_usec)/1000; + } else if ((u_long)now.tv_usec < (u_long)old->tv_usec) { + /* impossible .. garbage in nothing out */ + return (((net->lastsa >> 2) + net->lastsv) >> 1); + } else { + /* impossible .. garbage in nothing out */ + return (((net->lastsa >> 2) + net->lastsv) >> 1); + } + } else { + /* Clock wrapped? */ + return (((net->lastsa >> 2) + net->lastsv) >> 1); + } + /***************************/ + /* 2. update RTTVAR & SRTT */ + /***************************/ +#if 0 + /* if (net->lastsv || net->lastsa) {*/ + /* per Section 5.3.1 C3 in SCTP */ + /* net->lastsv = (int) *//* RTTVAR */ + /* (((double)(1.0 - 0.25) * (double)net->lastsv) + + (double)(0.25 * (double)abs(net->lastsa - calc_time))); + net->lastsa = (int) */ /* SRTT */ + /*(((double)(1.0 - 0.125) * (double)net->lastsa) + + (double)(0.125 * (double)calc_time)); + } else { + *//* the first RTT calculation, per C2 Section 5.3.1 */ + /* net->lastsa = calc_time; *//* SRTT */ + /* net->lastsv = calc_time / 2; *//* RTTVAR */ + /* }*/ + /* if RTTVAR goes to 0 you set to clock grainularity */ + /* if (net->lastsv == 0) { + net->lastsv = SCTP_CLOCK_GRANULARITY; + } + new_rto = net->lastsa + 4 * net->lastsv; + */ +#endif + o_calctime = calc_time; + /* this is Van Jacobson's integer version */ + if (net->RTO) { + calc_time -= (net->lastsa >> 3); + net->lastsa += calc_time; + if (calc_time < 0) { + calc_time = -calc_time; + } + calc_time -= (net->lastsv >> 2); + net->lastsv += calc_time; + if (net->lastsv == 0) { + net->lastsv = SCTP_CLOCK_GRANULARITY; + } + } else { + /* First RTO measurment */ + net->lastsa = calc_time; + net->lastsv = calc_time >> 1; + first_measure = 1; + } + new_rto = ((net->lastsa >> 2) + net->lastsv) >> 1; + if ((new_rto > SCTP_SAT_NETWORK_MIN) && + (stcb->asoc.sat_network_lockout == 0)) { + stcb->asoc.sat_network = 1; + } else if ((!first_measure) && stcb->asoc.sat_network) { + stcb->asoc.sat_network = 0; + stcb->asoc.sat_network_lockout = 1; + } + /* bound it, per C6/C7 in Section 5.3.1 */ + if (new_rto < stcb->asoc.minrto) { + new_rto = stcb->asoc.minrto; + } + if (new_rto > stcb->asoc.maxrto) { + new_rto = stcb->asoc.maxrto; + } + /* we are now returning the RTT Smoothed */ + return ((u_int32_t)new_rto); +} + + +/* + * return a pointer to a contiguous piece of data from the given + * mbuf chain starting at 'off' for 'len' bytes. If the desired + * piece spans more than one mbuf, a copy is made at 'ptr'. + * caller must ensure that the buffer size is >= 'len' + * returns NULL if there there isn't 'len' bytes in the chain. + */ +caddr_t +sctp_m_getptr(struct mbuf *m, int off, int len, u_int8_t *in_ptr) +{ + uint32_t count; + uint8_t *ptr; + ptr = in_ptr; + if ((off < 0) || (len <= 0)) + return (NULL); + + /* find the desired start location */ + while ((m != NULL) && (off > 0)) { + if (off < m->m_len) + break; + off -= m->m_len; + m = m->m_next; + } + if (m == NULL) + return (NULL); + + /* is the current mbuf large enough (eg. contiguous)? */ + if ((m->m_len - off) >= len) { + return (mtod(m, caddr_t) + off); + } else { + /* else, it spans more than one mbuf, so save a temp copy... */ + while ((m != NULL) && (len > 0)) { + count = min(m->m_len - off, len); + bcopy(mtod(m, caddr_t) + off, ptr, count); + len -= count; + ptr += count; + off = 0; + m = m->m_next; + } + if ((m == NULL) && (len > 0)) + return (NULL); + else + return ((caddr_t)in_ptr); + } +} + + +struct sctp_paramhdr * +sctp_get_next_param(struct mbuf *m, + int offset, + struct sctp_paramhdr *pull, + int pull_limit) +{ + /* This just provides a typed signature to Peter's Pull routine */ + return ((struct sctp_paramhdr *)sctp_m_getptr(m, offset, pull_limit, + (u_int8_t *)pull)); +} + + +int +sctp_add_pad_tombuf(struct mbuf *m, int padlen) +{ + /* + * add padlen bytes of 0 filled padding to the end of the mbuf. + * If padlen is > 3 this routine will fail. + */ + u_int8_t *dp; + int i; + if (padlen > 3) { + return (ENOBUFS); + } + if (M_TRAILINGSPACE(m)) { + /* + * The easy way. + * We hope the majority of the time we hit here :) + */ + dp = (u_int8_t *)(mtod(m, caddr_t) + m->m_len); + m->m_len += padlen; + } else { + /* Hard way we must grow the mbuf */ + struct mbuf *tmp; + MGET(tmp, M_DONTWAIT, MT_DATA); + if (tmp == NULL) { + /* Out of space GAK! we are in big trouble. */ + return (ENOSPC); + } + /* setup and insert in middle */ + tmp->m_next = m->m_next; + tmp->m_len = padlen; + m->m_next = tmp; + dp = mtod(tmp, u_int8_t *); + } + /* zero out the pad */ + for (i= 0; i < padlen; i++) { + *dp = 0; + dp++; + } + return (0); +} + +int +sctp_pad_lastmbuf(struct mbuf *m, int padval) +{ + /* find the last mbuf in chain and pad it */ + struct mbuf *m_at; + m_at = m; + while (m_at) { + if (m_at->m_next == NULL) { + return (sctp_add_pad_tombuf(m_at, padval)); + } + m_at = m_at->m_next; + } + return (EFAULT); +} + +static void +sctp_notify_assoc_change(u_int32_t event, struct sctp_tcb *stcb, + u_int32_t error) +{ + struct mbuf *m_notify; + struct sctp_assoc_change *sac; + struct sockaddr *to; + struct sockaddr_in6 sin6, lsa6; + + /* + * First if we are are going down dump everything we + * can to the socket rcv queue. + */ + if ((event == SCTP_SHUTDOWN_COMP) || (event == SCTP_COMM_LOST)) { + sctp_deliver_data(stcb, &stcb->asoc, NULL, 0); + } + + /* + * For TCP model AND UDP connected sockets we will send + * an error up when an ABORT comes in. + */ + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) && + (event == SCTP_COMM_LOST)) { + stcb->sctp_socket->so_error = ECONNRESET; + /* Wake ANY sleepers */ + sowwakeup(stcb->sctp_socket); + sorwakeup(stcb->sctp_socket); + } +#if 0 + if ((event == SCTP_COMM_UP) && + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) { + soisconnected(stcb->sctp_socket); + } +#endif + if (!(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_RECVASSOCEVNT)) { + /* event not enabled */ + return; + } + MGETHDR(m_notify, M_DONTWAIT, MT_DATA); + if (m_notify == NULL) + /* no space left */ + return; + m_notify->m_len = 0; + + sac = mtod(m_notify, struct sctp_assoc_change *); + sac->sac_type = SCTP_ASSOC_CHANGE; + sac->sac_flags = 0; + sac->sac_length = sizeof(struct sctp_assoc_change); + sac->sac_state = event; + sac->sac_error = error; + /* XXX verify these stream counts */ + sac->sac_outbound_streams = stcb->asoc.streamoutcnt; + sac->sac_inbound_streams = stcb->asoc.streamincnt; + sac->sac_assoc_id = sctp_get_associd(stcb); + + m_notify->m_flags |= M_EOR | M_NOTIFICATION; + m_notify->m_pkthdr.len = sizeof(struct sctp_assoc_change); + m_notify->m_pkthdr.rcvif = 0; + m_notify->m_len = sizeof(struct sctp_assoc_change); + m_notify->m_next = NULL; + + /* append to socket */ + to = (struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + to->sa_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)to; + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr.s6_addr16[2] = 0xffff; + bcopy(&sin->sin_addr, &sin6.sin6_addr.s6_addr16[3], + sizeof(sin6.sin6_addr.s6_addr16[3])); + sin6.sin6_port = sin->sin_port; + to = (struct sockaddr *)&sin6; + } + /* check and strip embedded scope junk */ + to = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)to, + &lsa6); + /* + * We need to always notify comm changes. + * if (sctp_sbspace(&stcb->sctp_socket->so_rcv) < m_notify->m_len) { + * sctp_m_freem(m_notify); + * return; + * } + */ + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + if (!sbappendaddr_nocheck(&stcb->sctp_socket->so_rcv, + to, m_notify, NULL, stcb->asoc.my_vtag, stcb->sctp_ep)) { + /* not enough room */ + sctp_m_freem(m_notify); + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) && + ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)){ + if (sctp_add_to_socket_q(stcb->sctp_ep, stcb)) { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + } else { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + SCTP_INP_WUNLOCK(stcb->sctp_ep); + /* Wake up any sleeper */ + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); + sctp_sowwakeup(stcb->sctp_ep, stcb->sctp_socket); +} + +static void +sctp_notify_peer_addr_change(struct sctp_tcb *stcb, uint32_t state, + struct sockaddr *sa, uint32_t error) +{ + struct mbuf *m_notify; + struct sctp_paddr_change *spc; + struct sockaddr *to; + struct sockaddr_in6 sin6, lsa6; + + if (!(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_RECVPADDREVNT)) + /* event not enabled */ + return; + + MGETHDR(m_notify, M_DONTWAIT, MT_DATA); + if (m_notify == NULL) + return; + m_notify->m_len = 0; + + MCLGET(m_notify, M_DONTWAIT); + if ((m_notify->m_flags & M_EXT) != M_EXT) { + sctp_m_freem(m_notify); + return; + } + + spc = mtod(m_notify, struct sctp_paddr_change *); + spc->spc_type = SCTP_PEER_ADDR_CHANGE; + spc->spc_flags = 0; + spc->spc_length = sizeof(struct sctp_paddr_change); + if (sa->sa_family == AF_INET) { + memcpy(&spc->spc_aaddr, sa, sizeof(struct sockaddr_in)); + } else { + memcpy(&spc->spc_aaddr, sa, sizeof(struct sockaddr_in6)); + } + spc->spc_state = state; + spc->spc_error = error; + spc->spc_assoc_id = sctp_get_associd(stcb); + + m_notify->m_flags |= M_EOR | M_NOTIFICATION; + m_notify->m_pkthdr.len = sizeof(struct sctp_paddr_change); + m_notify->m_pkthdr.rcvif = 0; + m_notify->m_len = sizeof(struct sctp_paddr_change); + m_notify->m_next = NULL; + + to = (struct sockaddr *)(struct sockaddr *) + &stcb->asoc.primary_destination->ro._l_addr; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + to->sa_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)to; + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr.s6_addr16[2] = 0xffff; + bcopy(&sin->sin_addr, &sin6.sin6_addr.s6_addr16[3], + sizeof(sin6.sin6_addr.s6_addr16[3])); + sin6.sin6_port = sin->sin_port; + to = (struct sockaddr *)&sin6; + } + /* check and strip embedded scope junk */ + to = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)to, + &lsa6); + + if (sctp_sbspace(&stcb->sctp_socket->so_rcv) < m_notify->m_len) { + sctp_m_freem(m_notify); + return; + } + /* append to socket */ + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + if (!sbappendaddr_nocheck(&stcb->sctp_socket->so_rcv, to, + m_notify, NULL, stcb->asoc.my_vtag, stcb->sctp_ep)) { + /* not enough room */ + sctp_m_freem(m_notify); + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) && + ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)){ + if (sctp_add_to_socket_q(stcb->sctp_ep, stcb)) { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + } else { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + SCTP_INP_WUNLOCK(stcb->sctp_ep); + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); +} + + +static void +sctp_notify_send_failed(struct sctp_tcb *stcb, u_int32_t error, + struct sctp_tmit_chunk *chk) +{ + struct mbuf *m_notify; + struct sctp_send_failed *ssf; + struct sockaddr_in6 sin6, lsa6; + struct sockaddr *to; + int length; + + if (!(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_RECVSENDFAILEVNT)) + /* event not enabled */ + return; + + length = sizeof(struct sctp_send_failed) + chk->send_size; + MGETHDR(m_notify, M_DONTWAIT, MT_DATA); + if (m_notify == NULL) + /* no space left */ + return; + m_notify->m_len = 0; + ssf = mtod(m_notify, struct sctp_send_failed *); + ssf->ssf_type = SCTP_SEND_FAILED; + if (error == SCTP_NOTIFY_DATAGRAM_UNSENT) + ssf->ssf_flags = SCTP_DATA_UNSENT; + else + ssf->ssf_flags = SCTP_DATA_SENT; + ssf->ssf_length = length; + ssf->ssf_error = error; + /* not exactly what the user sent in, but should be close :) */ + ssf->ssf_info.sinfo_stream = chk->rec.data.stream_number; + ssf->ssf_info.sinfo_ssn = chk->rec.data.stream_seq; + ssf->ssf_info.sinfo_flags = chk->rec.data.rcv_flags; + ssf->ssf_info.sinfo_ppid = chk->rec.data.payloadtype; + ssf->ssf_info.sinfo_context = chk->rec.data.context; + ssf->ssf_info.sinfo_assoc_id = sctp_get_associd(stcb); + ssf->ssf_assoc_id = sctp_get_associd(stcb); + m_notify->m_next = chk->data; + if (m_notify->m_next == NULL) + m_notify->m_flags |= M_EOR | M_NOTIFICATION; + else { + struct mbuf *m; + m_notify->m_flags |= M_NOTIFICATION; + m = m_notify; + while (m->m_next != NULL) + m = m->m_next; + m->m_flags |= M_EOR; + } + m_notify->m_pkthdr.len = length; + m_notify->m_pkthdr.rcvif = 0; + m_notify->m_len = sizeof(struct sctp_send_failed); + + /* Steal off the mbuf */ + chk->data = NULL; + to = (struct sockaddr *)(struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + to->sa_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)to; + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr.s6_addr16[2] = 0xffff; + bcopy(&sin->sin_addr, &sin6.sin6_addr.s6_addr16[3], + sizeof(sin6.sin6_addr.s6_addr16[3])); + sin6.sin6_port = sin->sin_port; + to = (struct sockaddr *)&sin6; + } + /* check and strip embedded scope junk */ + to = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)to, + &lsa6); + + if (sctp_sbspace(&stcb->sctp_socket->so_rcv) < m_notify->m_len) { + sctp_m_freem(m_notify); + return; + } + + /* append to socket */ + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + if (!sbappendaddr_nocheck(&stcb->sctp_socket->so_rcv, to, + m_notify, NULL, stcb->asoc.my_vtag, stcb->sctp_ep)) { + /* not enough room */ + sctp_m_freem(m_notify); + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) && + ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)){ + if (sctp_add_to_socket_q(stcb->sctp_ep, stcb)) { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + } else { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + SCTP_INP_WUNLOCK(stcb->sctp_ep); + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); +} + +static void +sctp_notify_adaption_layer(struct sctp_tcb *stcb, + u_int32_t error) +{ + struct mbuf *m_notify; + struct sctp_adaption_event *sai; + struct sockaddr_in6 sin6, lsa6; + struct sockaddr *to; + + if (!(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_ADAPTIONEVNT)) + /* event not enabled */ + return; + + MGETHDR(m_notify, M_DONTWAIT, MT_DATA); + if (m_notify == NULL) + /* no space left */ + return; + m_notify->m_len = 0; + sai = mtod(m_notify, struct sctp_adaption_event *); + sai->sai_type = SCTP_ADAPTION_INDICATION; + sai->sai_flags = 0; + sai->sai_length = sizeof(struct sctp_adaption_event); + sai->sai_adaption_ind = error; + sai->sai_assoc_id = sctp_get_associd(stcb); + + m_notify->m_flags |= M_EOR | M_NOTIFICATION; + m_notify->m_pkthdr.len = sizeof(struct sctp_adaption_event); + m_notify->m_pkthdr.rcvif = 0; + m_notify->m_len = sizeof(struct sctp_adaption_event); + m_notify->m_next = NULL; + + to = (struct sockaddr *)(struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + (to->sa_family == AF_INET)) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)to; + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr.s6_addr16[2] = 0xffff; + bcopy(&sin->sin_addr, &sin6.sin6_addr.s6_addr16[3], + sizeof(sin6.sin6_addr.s6_addr16[3])); + sin6.sin6_port = sin->sin_port; + to = (struct sockaddr *)&sin6; + } + /* check and strip embedded scope junk */ + to = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)to, + &lsa6); + if (sctp_sbspace(&stcb->sctp_socket->so_rcv) < m_notify->m_len) { + sctp_m_freem(m_notify); + return; + } + /* append to socket */ + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + if (!sbappendaddr_nocheck(&stcb->sctp_socket->so_rcv, to, + m_notify, NULL, stcb->asoc.my_vtag, stcb->sctp_ep)) { + /* not enough room */ + sctp_m_freem(m_notify); + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) && + ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)){ + if (sctp_add_to_socket_q(stcb->sctp_ep, stcb)) { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + } else { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + SCTP_INP_WUNLOCK(stcb->sctp_ep); + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); +} + +static void +sctp_notify_partial_delivery_indication(struct sctp_tcb *stcb, + u_int32_t error) +{ + struct mbuf *m_notify; + struct sctp_pdapi_event *pdapi; + struct sockaddr_in6 sin6, lsa6; + struct sockaddr *to; + + if (!(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_PDAPIEVNT)) + /* event not enabled */ + return; + + MGETHDR(m_notify, M_DONTWAIT, MT_DATA); + if (m_notify == NULL) + /* no space left */ + return; + m_notify->m_len = 0; + pdapi = mtod(m_notify, struct sctp_pdapi_event *); + pdapi->pdapi_type = SCTP_PARTIAL_DELIVERY_EVENT; + pdapi->pdapi_flags = 0; + pdapi->pdapi_length = sizeof(struct sctp_pdapi_event); + pdapi->pdapi_indication = error; + pdapi->pdapi_assoc_id = sctp_get_associd(stcb); + + m_notify->m_flags |= M_EOR | M_NOTIFICATION; + m_notify->m_pkthdr.len = sizeof(struct sctp_pdapi_event); + m_notify->m_pkthdr.rcvif = 0; + m_notify->m_len = sizeof(struct sctp_pdapi_event); + m_notify->m_next = NULL; + + to = (struct sockaddr *)(struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + (to->sa_family == AF_INET)) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)to; + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr.s6_addr16[2] = 0xffff; + bcopy(&sin->sin_addr, &sin6.sin6_addr.s6_addr16[3], + sizeof(sin6.sin6_addr.s6_addr16[3])); + sin6.sin6_port = sin->sin_port; + to = (struct sockaddr *)&sin6; + } + /* check and strip embedded scope junk */ + to = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)to, + &lsa6); + if (sctp_sbspace(&stcb->sctp_socket->so_rcv) < m_notify->m_len) { + sctp_m_freem(m_notify); + return; + } + /* append to socket */ + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + if (!sbappendaddr_nocheck(&stcb->sctp_socket->so_rcv, to, + m_notify, NULL, stcb->asoc.my_vtag, stcb->sctp_ep)) { + /* not enough room */ + sctp_m_freem(m_notify); + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) && + ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)){ + if (sctp_add_to_socket_q(stcb->sctp_ep, stcb)) { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + } else { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + SCTP_INP_WUNLOCK(stcb->sctp_ep); + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); +} + +static void +sctp_notify_shutdown_event(struct sctp_tcb *stcb) +{ + struct mbuf *m_notify; + struct sctp_shutdown_event *sse; + struct sockaddr_in6 sin6, lsa6; + struct sockaddr *to; + + /* + * For TCP model AND UDP connected sockets we will send + * an error up when an SHUTDOWN completes + */ + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + /* mark socket closed for read/write and wakeup! */ + socantrcvmore(stcb->sctp_socket); + socantsendmore(stcb->sctp_socket); + } + + if (!(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT)) + /* event not enabled */ + return; + + MGETHDR(m_notify, M_DONTWAIT, MT_DATA); + if (m_notify == NULL) + /* no space left */ + return; + m_notify->m_len = 0; + sse = mtod(m_notify, struct sctp_shutdown_event *); + sse->sse_type = SCTP_SHUTDOWN_EVENT; + sse->sse_flags = 0; + sse->sse_length = sizeof(struct sctp_shutdown_event); + sse->sse_assoc_id = sctp_get_associd(stcb); + + m_notify->m_flags |= M_EOR | M_NOTIFICATION; + m_notify->m_pkthdr.len = sizeof(struct sctp_shutdown_event); + m_notify->m_pkthdr.rcvif = 0; + m_notify->m_len = sizeof(struct sctp_shutdown_event); + m_notify->m_next = NULL; + + to = (struct sockaddr *)(struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + to->sa_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)to; + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr.s6_addr16[2] = 0xffff; + bcopy(&sin->sin_addr, &sin6.sin6_addr.s6_addr16[3], + sizeof(sin6.sin6_addr.s6_addr16[3])); + sin6.sin6_port = sin->sin_port; + to = (struct sockaddr *)&sin6; + } + /* check and strip embedded scope junk */ + to = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)to, + &lsa6); + if (sctp_sbspace(&stcb->sctp_socket->so_rcv) < m_notify->m_len) { + sctp_m_freem(m_notify); + return; + } + /* append to socket */ + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + if (!sbappendaddr_nocheck(&stcb->sctp_socket->so_rcv, to, + m_notify, NULL, stcb->asoc.my_vtag, stcb->sctp_ep)) { + /* not enough room */ + sctp_m_freem(m_notify); + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) && + ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)){ + if (sctp_add_to_socket_q(stcb->sctp_ep, stcb)) { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + } else { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + SCTP_INP_WUNLOCK(stcb->sctp_ep); + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); +} + +static void +sctp_notify_stream_reset(struct sctp_tcb *stcb, + int number_entries, uint16_t *list, int flag) +{ + struct mbuf *m_notify; + struct sctp_stream_reset_event *strreset; + struct sockaddr_in6 sin6, lsa6; + struct sockaddr *to; + int len; + + if (!(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_STREAM_RESETEVNT)) + /* event not enabled */ + return; + + MGETHDR(m_notify, M_DONTWAIT, MT_DATA); + if (m_notify == NULL) + /* no space left */ + return; + m_notify->m_len = 0; + len = sizeof(struct sctp_stream_reset_event) + (number_entries * sizeof(uint16_t)); + if (len > M_TRAILINGSPACE(m_notify)) { + MCLGET(m_notify, M_WAIT); + } + if (m_notify == NULL) + /* no clusters */ + return; + + if (len > M_TRAILINGSPACE(m_notify)) { + /* never enough room */ + m_freem(m_notify); + return; + } + strreset = mtod(m_notify, struct sctp_stream_reset_event *); + strreset->strreset_type = SCTP_STREAM_RESET_EVENT; + if (number_entries == 0) { + strreset->strreset_flags = flag | SCTP_STRRESET_ALL_STREAMS; + } else { + strreset->strreset_flags = flag | SCTP_STRRESET_STREAM_LIST; + } + strreset->strreset_length = len; + strreset->strreset_assoc_id = sctp_get_associd(stcb); + if (number_entries) { + int i; + for (i=0; istrreset_list[i] = list[i]; + } + } + m_notify->m_flags |= M_EOR | M_NOTIFICATION; + m_notify->m_pkthdr.len = len; + m_notify->m_pkthdr.rcvif = 0; + m_notify->m_len = len; + m_notify->m_next = NULL; + if (sctp_sbspace(&stcb->sctp_socket->so_rcv) < m_notify->m_len) { + /* no space */ + sctp_m_freem(m_notify); + return; + } + to = (struct sockaddr *)(struct sockaddr *)&stcb->asoc.primary_destination->ro._l_addr; + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) && + to->sa_family == AF_INET) { + struct sockaddr_in *sin; + + sin = (struct sockaddr_in *)to; + bzero(&sin6, sizeof(sin6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_len = sizeof(struct sockaddr_in6); + sin6.sin6_addr.s6_addr16[2] = 0xffff; + bcopy(&sin->sin_addr, &sin6.sin6_addr.s6_addr16[3], + sizeof(sin6.sin6_addr.s6_addr16[3])); + sin6.sin6_port = sin->sin_port; + to = (struct sockaddr *)&sin6; + } + /* check and strip embedded scope junk */ + to = (struct sockaddr *)sctp_recover_scope((struct sockaddr_in6 *)to, + &lsa6); + /* append to socket */ + SCTP_TCB_UNLOCK(stcb); + SCTP_INP_WLOCK(stcb->sctp_ep); + SCTP_TCB_LOCK(stcb); + if (!sbappendaddr_nocheck(&stcb->sctp_socket->so_rcv, to, + m_notify, NULL, stcb->asoc.my_vtag, stcb->sctp_ep)) { + /* not enough room */ + sctp_m_freem(m_notify); + SCTP_INP_WUNLOCK(stcb->sctp_ep); + return; + } + if (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) && + ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0)){ + if (sctp_add_to_socket_q(stcb->sctp_ep, stcb)) { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + } else { + stcb->asoc.my_rwnd_control_len += sizeof(struct mbuf); + } + SCTP_INP_WUNLOCK(stcb->sctp_ep); + sctp_sorwakeup(stcb->sctp_ep, stcb->sctp_socket); +} + + +void +sctp_ulp_notify(u_int32_t notification, struct sctp_tcb *stcb, + u_int32_t error, void *data) +{ + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + /* No notifications up when we are in a no socket state */ + return; + } + if (stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET) { + /* Can't send up to a closed socket any notifications */ + return; + } + switch (notification) { + case SCTP_NOTIFY_ASSOC_UP: + sctp_notify_assoc_change(SCTP_COMM_UP, stcb, error); + break; + case SCTP_NOTIFY_ASSOC_DOWN: + sctp_notify_assoc_change(SCTP_SHUTDOWN_COMP, stcb, error); + break; + case SCTP_NOTIFY_INTERFACE_DOWN: + { + struct sctp_nets *net; + net = (struct sctp_nets *)data; + sctp_notify_peer_addr_change(stcb, SCTP_ADDR_UNREACHABLE, + (struct sockaddr *)&net->ro._l_addr, error); + break; + } + case SCTP_NOTIFY_INTERFACE_UP: + { + struct sctp_nets *net; + net = (struct sctp_nets *)data; + sctp_notify_peer_addr_change(stcb, SCTP_ADDR_AVAILABLE, + (struct sockaddr *)&net->ro._l_addr, error); + break; + } + case SCTP_NOTIFY_INTERFACE_CONFIRMED: + { + struct sctp_nets *net; + net = (struct sctp_nets *)data; + sctp_notify_peer_addr_change(stcb, SCTP_ADDR_CONFIRMED, + (struct sockaddr *)&net->ro._l_addr, error); + break; + } + case SCTP_NOTIFY_DG_FAIL: + sctp_notify_send_failed(stcb, error, + (struct sctp_tmit_chunk *)data); + break; + case SCTP_NOTIFY_ADAPTION_INDICATION: + /* Here the error is the adaption indication */ + sctp_notify_adaption_layer(stcb, error); + break; + case SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION: + sctp_notify_partial_delivery_indication(stcb, error); + break; + case SCTP_NOTIFY_STRDATA_ERR: + break; + case SCTP_NOTIFY_ASSOC_ABORTED: + sctp_notify_assoc_change(SCTP_COMM_LOST, stcb, error); + break; + case SCTP_NOTIFY_PEER_OPENED_STREAM: + break; + case SCTP_NOTIFY_STREAM_OPENED_OK: + break; + case SCTP_NOTIFY_ASSOC_RESTART: + sctp_notify_assoc_change(SCTP_RESTART, stcb, error); + break; + case SCTP_NOTIFY_HB_RESP: + break; + case SCTP_NOTIFY_STR_RESET_SEND: + sctp_notify_stream_reset(stcb, error, ((uint16_t *)data), SCTP_STRRESET_OUTBOUND_STR); + break; + case SCTP_NOTIFY_STR_RESET_RECV: + sctp_notify_stream_reset(stcb, error, ((uint16_t *)data), SCTP_STRRESET_INBOUND_STR); + break; + case SCTP_NOTIFY_ASCONF_ADD_IP: + sctp_notify_peer_addr_change(stcb, SCTP_ADDR_ADDED, data, + error); + break; + case SCTP_NOTIFY_ASCONF_DELETE_IP: + sctp_notify_peer_addr_change(stcb, SCTP_ADDR_REMOVED, data, + error); + break; + case SCTP_NOTIFY_ASCONF_SET_PRIMARY: + sctp_notify_peer_addr_change(stcb, SCTP_ADDR_MADE_PRIM, data, + error); + break; + case SCTP_NOTIFY_ASCONF_SUCCESS: + break; + case SCTP_NOTIFY_ASCONF_FAILED: + break; + case SCTP_NOTIFY_PEER_SHUTDOWN: + sctp_notify_shutdown_event(stcb); + break; + default: +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_UTIL1) { + printf("NOTIFY: unknown notification %xh (%u)\n", + notification, notification); + } +#endif /* SCTP_DEBUG */ + break; + } /* end switch */ +} + +void +sctp_report_all_outbound(struct sctp_tcb *stcb) +{ + struct sctp_association *asoc; + struct sctp_stream_out *outs; + struct sctp_tmit_chunk *chk; + + asoc = &stcb->asoc; + + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + return; + } + /* now through all the gunk freeing chunks */ + TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) { + /* now clean up any chunks here */ + chk = TAILQ_FIRST(&outs->outqueue); + while (chk) { + stcb->asoc.stream_queue_cnt--; + TAILQ_REMOVE(&outs->outqueue, chk, sctp_next); + sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, stcb, + SCTP_NOTIFY_DATAGRAM_UNSENT, chk); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + if (chk->whoTo) + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = NULL; + chk->asoc = NULL; + /* Free the chunk */ + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&outs->outqueue); + } + } + /* pending send queue SHOULD be empty */ + if (!TAILQ_EMPTY(&asoc->send_queue)) { + chk = TAILQ_FIRST(&asoc->send_queue); + while (chk) { + TAILQ_REMOVE(&asoc->send_queue, chk, sctp_next); + sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, stcb, SCTP_NOTIFY_DATAGRAM_UNSENT, chk); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + if (chk->whoTo) + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = NULL; + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&asoc->send_queue); + } + } + /* sent queue SHOULD be empty */ + if (!TAILQ_EMPTY(&asoc->sent_queue)) { + chk = TAILQ_FIRST(&asoc->sent_queue); + while (chk) { + TAILQ_REMOVE(&asoc->sent_queue, chk, sctp_next); + sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, stcb, + SCTP_NOTIFY_DATAGRAM_SENT, chk); + if (chk->data) { + sctp_m_freem(chk->data); + chk->data = NULL; + } + if (chk->whoTo) + sctp_free_remote_addr(chk->whoTo); + chk->whoTo = NULL; + SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_chunk, chk); + sctppcbinfo.ipi_count_chunk--; + if ((int)sctppcbinfo.ipi_count_chunk < 0) { + panic("Chunk count is negative"); + } + sctppcbinfo.ipi_gencnt_chunk++; + chk = TAILQ_FIRST(&asoc->sent_queue); + } + } +} + +void +sctp_abort_notification(struct sctp_tcb *stcb, int error) +{ + + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + return; + } + /* Tell them we lost the asoc */ + sctp_report_all_outbound(stcb); + sctp_ulp_notify(SCTP_NOTIFY_ASSOC_ABORTED, stcb, error, NULL); +} + +void +sctp_abort_association(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + struct mbuf *m, int iphlen, struct sctphdr *sh, struct mbuf *op_err) +{ + u_int32_t vtag; + + vtag = 0; + if (stcb != NULL) { + /* We have a TCB to abort, send notification too */ + vtag = stcb->asoc.peer_vtag; + sctp_abort_notification(stcb, 0); + } + sctp_send_abort(m, iphlen, sh, vtag, op_err); + if (stcb != NULL) { + /* Ok, now lets free it */ + sctp_free_assoc(inp, stcb); + } else { + if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) { + sctp_inpcb_free(inp, 1); + } + } + } +} + +void +sctp_abort_an_association(struct sctp_inpcb *inp, struct sctp_tcb *stcb, + int error, struct mbuf *op_err) +{ + u_int32_t vtag; + + if (stcb == NULL) { + /* Got to have a TCB */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { + if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) { + sctp_inpcb_free(inp, 1); + } + } + return; + } + vtag = stcb->asoc.peer_vtag; + /* notify the ulp */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) == 0) + sctp_abort_notification(stcb, error); + /* notify the peer */ + sctp_send_abort_tcb(stcb, op_err); + /* now free the asoc */ + sctp_free_assoc(inp, stcb); +} + +void +sctp_handle_ootb(struct mbuf *m, int iphlen, int offset, struct sctphdr *sh, + struct sctp_inpcb *inp, struct mbuf *op_err) +{ + struct sctp_chunkhdr *ch, chunk_buf; + unsigned int chk_length; + + /* Generate a TO address for future reference */ + if (inp && (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) { + if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) { + sctp_inpcb_free(inp, 1); + } + } + ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset, + sizeof(*ch), (u_int8_t *)&chunk_buf); + while (ch != NULL) { + chk_length = ntohs(ch->chunk_length); + if (chk_length < sizeof(*ch)) { + /* break to abort land */ + break; + } + switch (ch->chunk_type) { + case SCTP_PACKET_DROPPED: + /* we don't respond to pkt-dropped */ + return; + case SCTP_ABORT_ASSOCIATION: + /* we don't respond with an ABORT to an ABORT */ + return; + case SCTP_SHUTDOWN_COMPLETE: + /* + * we ignore it since we are not waiting for it + * and peer is gone + */ + return; + case SCTP_SHUTDOWN_ACK: + sctp_send_shutdown_complete2(m, iphlen, sh); + return; + default: + break; + } + offset += SCTP_SIZE32(chk_length); + ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset, + sizeof(*ch), (u_int8_t *)&chunk_buf); + } + sctp_send_abort(m, iphlen, sh, 0, op_err); +} + +/* + * check the inbound datagram to make sure there is not an abort + * inside it, if there is return 1, else return 0. + */ +int +sctp_is_there_an_abort_here(struct mbuf *m, int iphlen, int *vtagfill) +{ + struct sctp_chunkhdr *ch; + struct sctp_init_chunk *init_chk, chunk_buf; + int offset; + unsigned int chk_length; + + offset = iphlen + sizeof(struct sctphdr); + ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset, sizeof(*ch), + (u_int8_t *)&chunk_buf); + while (ch != NULL) { + chk_length = ntohs(ch->chunk_length); + if (chk_length < sizeof(*ch)) { + /* packet is probably corrupt */ + break; + } + /* we seem to be ok, is it an abort? */ + if (ch->chunk_type == SCTP_ABORT_ASSOCIATION) { + /* yep, tell them */ + return (1); + } + if (ch->chunk_type == SCTP_INITIATION) { + /* need to update the Vtag */ + init_chk = (struct sctp_init_chunk *)sctp_m_getptr(m, + offset, sizeof(*init_chk), (u_int8_t *)&chunk_buf); + if (init_chk != NULL) { + *vtagfill = ntohl(init_chk->init.initiate_tag); + } + } + /* Nope, move to the next chunk */ + offset += SCTP_SIZE32(chk_length); + ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset, + sizeof(*ch), (u_int8_t *)&chunk_buf); + } + return (0); +} + +/* + * currently (2/02), ifa_addr embeds scope_id's and don't + * have sin6_scope_id set (i.e. it's 0) + * so, create this function to compare link local scopes + */ +uint32_t +sctp_is_same_scope(struct sockaddr_in6 *addr1, struct sockaddr_in6 *addr2) +{ + struct sockaddr_in6 a, b; + + /* save copies */ + a = *addr1; + b = *addr2; + + if (a.sin6_scope_id == 0) + if (in6_recoverscope(&a, &a.sin6_addr, NULL)) { + /* can't get scope, so can't match */ + return (0); + } + if (b.sin6_scope_id == 0) + if (in6_recoverscope(&b, &b.sin6_addr, NULL)) { + /* can't get scope, so can't match */ + return (0); + } + if (a.sin6_scope_id != b.sin6_scope_id) + return (0); + + return (1); +} + +/* + * returns a sockaddr_in6 with embedded scope recovered and removed + */ +struct sockaddr_in6 * +sctp_recover_scope(struct sockaddr_in6 *addr, struct sockaddr_in6 *store) +{ + + /* check and strip embedded scope junk */ + if (addr->sin6_family == AF_INET6) { + if (IN6_IS_SCOPE_LINKLOCAL(&addr->sin6_addr)) { + if (addr->sin6_scope_id == 0) { + *store = *addr; + if (!in6_recoverscope(store, &store->sin6_addr, + NULL)) { + /* use the recovered scope */ + addr = store; + } + /* else, return the original "to" addr */ + } + } + } + return (addr); +} + +/* + * are the two addresses the same? currently a "scopeless" check + * returns: 1 if same, 0 if not + */ +int +sctp_cmpaddr(struct sockaddr *sa1, struct sockaddr *sa2) +{ + + /* must be valid */ + if (sa1 == NULL || sa2 == NULL) + return (0); + + /* must be the same family */ + if (sa1->sa_family != sa2->sa_family) + return (0); + + if (sa1->sa_family == AF_INET6) { + /* IPv6 addresses */ + struct sockaddr_in6 *sin6_1, *sin6_2; + + sin6_1 = (struct sockaddr_in6 *)sa1; + sin6_2 = (struct sockaddr_in6 *)sa2; + return (SCTP6_ARE_ADDR_EQUAL(&sin6_1->sin6_addr, + &sin6_2->sin6_addr)); + } else if (sa1->sa_family == AF_INET) { + /* IPv4 addresses */ + struct sockaddr_in *sin_1, *sin_2; + + sin_1 = (struct sockaddr_in *)sa1; + sin_2 = (struct sockaddr_in *)sa2; + return (sin_1->sin_addr.s_addr == sin_2->sin_addr.s_addr); + } else { + /* we don't do these... */ + return (0); + } +} + +void +sctp_print_address(struct sockaddr *sa) +{ + + if (sa->sa_family == AF_INET6) { + struct sockaddr_in6 *sin6; + sin6 = (struct sockaddr_in6 *)sa; + printf("IPv6 address: %s:%d scope:%u\n", + ip6_sprintf(&sin6->sin6_addr), ntohs(sin6->sin6_port), + sin6->sin6_scope_id); + } else if (sa->sa_family == AF_INET) { + struct sockaddr_in *sin; + sin = (struct sockaddr_in *)sa; + printf("IPv4 address: %s:%d\n", inet_ntoa(sin->sin_addr), + ntohs(sin->sin_port)); + } else { + printf("?\n"); + } +} + +void +sctp_print_address_pkt(struct ip *iph, struct sctphdr *sh) +{ + if (iph->ip_v == IPVERSION) { + struct sockaddr_in lsa, fsa; + + bzero(&lsa, sizeof(lsa)); + lsa.sin_len = sizeof(lsa); + lsa.sin_family = AF_INET; + lsa.sin_addr = iph->ip_src; + lsa.sin_port = sh->src_port; + bzero(&fsa, sizeof(fsa)); + fsa.sin_len = sizeof(fsa); + fsa.sin_family = AF_INET; + fsa.sin_addr = iph->ip_dst; + fsa.sin_port = sh->dest_port; + printf("src: "); + sctp_print_address((struct sockaddr *)&lsa); + printf("dest: "); + sctp_print_address((struct sockaddr *)&fsa); + } else if (iph->ip_v == (IPV6_VERSION >> 4)) { + struct ip6_hdr *ip6; + struct sockaddr_in6 lsa6, fsa6; + + ip6 = (struct ip6_hdr *)iph; + bzero(&lsa6, sizeof(lsa6)); + lsa6.sin6_len = sizeof(lsa6); + lsa6.sin6_family = AF_INET6; + lsa6.sin6_addr = ip6->ip6_src; + lsa6.sin6_port = sh->src_port; + bzero(&fsa6, sizeof(fsa6)); + fsa6.sin6_len = sizeof(fsa6); + fsa6.sin6_family = AF_INET6; + fsa6.sin6_addr = ip6->ip6_dst; + fsa6.sin6_port = sh->dest_port; + printf("src: "); + sctp_print_address((struct sockaddr *)&lsa6); + printf("dest: "); + sctp_print_address((struct sockaddr *)&fsa6); + } +} + +#if defined(__FreeBSD__) || defined(__APPLE__) + +/* cloned from uipc_socket.c */ + +#define SCTP_SBLINKRECORD(sb, m0) do { \ + if ((sb)->sb_lastrecord != NULL) \ + (sb)->sb_lastrecord->m_nextpkt = (m0); \ + else \ + (sb)->sb_mb = (m0); \ + (sb)->sb_lastrecord = (m0); \ +} while (/*CONSTCOND*/0) +#endif + + +int +sbappendaddr_nocheck(sb, asa, m0, control, tag, inp) + struct sockbuf *sb; + struct sockaddr *asa; + struct mbuf *m0, *control; + u_int32_t tag; + struct sctp_inpcb *inp; +{ +#ifdef __NetBSD__ + struct mbuf *m, *n; + + if (m0 && (m0->m_flags & M_PKTHDR) == 0) + panic("sbappendaddr_nocheck"); + + m0->m_pkthdr.csum_data = (int)tag; + + for (n = control; n; n = n->m_next) { + if (n->m_next == 0) /* keep pointer to last control buf */ + break; + } + if (((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) == 0) || + ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)== 0)) { + MGETHDR(m, M_DONTWAIT, MT_SONAME); + if (m == 0) + return (0); + m->m_len = 0; + if (asa->sa_len > MHLEN) { + MEXTMALLOC(m, asa->sa_len, M_NOWAIT); + if ((m->m_flags & M_EXT) == 0) { + m_free(m); + return (0); + } + } + m->m_len = asa->sa_len; + memcpy(mtod(m, caddr_t), (caddr_t)asa, asa->sa_len); + } else { + m = NULL; + } + if (n) { + n->m_next = m0; /* concatenate data to control */ + }else { + control = m0; + } + if (m) + m->m_next = control; + else + m = control; + m->m_pkthdr.csum_data = tag; + + for (n = m; n; n = n->m_next) + sballoc(sb, n); + if ((n = sb->sb_mb) != NULL) { + if ((n->m_nextpkt != inp->sb_last_mpkt) && (n->m_nextpkt == NULL)) { + inp->sb_last_mpkt = NULL; + } + if (inp->sb_last_mpkt) + inp->sb_last_mpkt->m_nextpkt = m; + else { + while (n->m_nextpkt) { + n = n->m_nextpkt; + } + n->m_nextpkt = m; + } + inp->sb_last_mpkt = m; + } else { + inp->sb_last_mpkt = sb->sb_mb = m; + inp->sctp_vtag_first = tag; + } + return (1); +#endif +#if defined(__FreeBSD__) || defined(__APPLE__) + struct mbuf *m, *n, *nlast; + int cnt=0; + + if (m0 && (m0->m_flags & M_PKTHDR) == 0) + panic("sbappendaddr_nocheck"); + + for (n = control; n; n = n->m_next) { + if (n->m_next == 0) /* get pointer to last control buf */ + break; + } + if (((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) == 0) || + ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)== 0)) { + if (asa->sa_len > MHLEN) + return (0); + try_again: + MGETHDR(m, M_DONTWAIT, MT_SONAME); + if (m == 0) + return (0); + m->m_len = 0; + /* safety */ + if (m == m0) { + printf("Duplicate mbuf allocated %p in and mget returned %p?\n", + m0, m); + if (cnt) { + panic("more than once"); + } + cnt++; + goto try_again; + } + m->m_len = asa->sa_len; + bcopy((caddr_t)asa, mtod(m, caddr_t), asa->sa_len); + } + else { + m = NULL; + } + if (n) + n->m_next = m0; /* concatenate data to control */ + else + control = m0; + if (m) + m->m_next = control; + else + m = control; + m->m_pkthdr.csum_data = (int)tag; + + SOCKBUF_LOCK(sb); + for (n = m; n; n = n->m_next) + sballoc(sb, n); + nlast = n; + if (sb->sb_mb == NULL) { + inp->sctp_vtag_first = tag; + } + +#ifdef __FREEBSD__ + if (sb->sb_mb == NULL) + inp->sctp_vtag_first = tag; + SCTP_SBLINKRECORD(sb, m); + sb->sb_mbtail = nlast; +#else + if ((n = sb->sb_mb) != NULL) { + if ((n->m_nextpkt != inp->sb_last_mpkt) && (n->m_nextpkt == NULL)) { + inp->sb_last_mpkt = NULL; + } + if (inp->sb_last_mpkt) + inp->sb_last_mpkt->m_nextpkt = m; + else { + while (n->m_nextpkt) { + n = n->m_nextpkt; + } + n->m_nextpkt = m; + } + inp->sb_last_mpkt = m; + } else { + inp->sb_last_mpkt = sb->sb_mb = m; + inp->sctp_vtag_first = tag; + } +#endif + SOCKBUF_UNLOCK(sb); + return (1); +#endif +#ifdef __OpenBSD__ + struct mbuf *m, *n; + + if (m0 && (m0->m_flags & M_PKTHDR) == 0) + panic("sbappendaddr_nocheck"); + m0->m_pkthdr.csum = (int)tag; + for (n = control; n; n = n->m_next) { + if (n->m_next == 0) /* keep pointer to last control buf */ + break; + } + if (((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) == 0) || + ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)== 0)) { + if (asa->sa_len > MHLEN) + return (0); + MGETHDR(m, M_DONTWAIT, MT_SONAME); + if (m == 0) + return (0); + m->m_len = asa->sa_len; + bcopy((caddr_t)asa, mtod(m, caddr_t), asa->sa_len); + } else { + m = NULL; + } + if (n) + n->m_next = m0; /* concatenate data to control */ + else + control = m0; + + m->m_pkthdr.csum = (int)tag; + m->m_next = control; + for (n = m; n; n = n->m_next) + sballoc(sb, n); + if ((n = sb->sb_mb) != NULL) { + if ((n->m_nextpkt != inp->sb_last_mpkt) && (n->m_nextpkt == NULL)) { + inp->sb_last_mpkt = NULL; + } + if (inp->sb_last_mpkt) + inp->sb_last_mpkt->m_nextpkt = m; + else { + while (n->m_nextpkt) { + n = n->m_nextpkt; + } + n->m_nextpkt = m; + } + inp->sb_last_mpkt = m; + } else { + inp->sb_last_mpkt = sb->sb_mb = m; + inp->sctp_vtag_first = tag; + } + return (1); +#endif +} + +/*************HOLD THIS COMMENT FOR PATCH FILE OF + *************ALTERNATE ROUTING CODE + */ + +/*************HOLD THIS COMMENT FOR END OF PATCH FILE OF + *************ALTERNATE ROUTING CODE + */ + +struct mbuf * +sctp_generate_invmanparam(int err) +{ + /* Return a MBUF with a invalid mandatory parameter */ + struct mbuf *m; + + MGET(m, M_DONTWAIT, MT_DATA); + if (m) { + struct sctp_paramhdr *ph; + m->m_len = sizeof(struct sctp_paramhdr); + ph = mtod(m, struct sctp_paramhdr *); + ph->param_length = htons(sizeof(struct sctp_paramhdr)); + ph->param_type = htons(err); + } + return (m); +} + +static int +sctp_should_be_moved(struct mbuf *this, struct sctp_association *asoc) +{ + struct mbuf *m; + /* + * given a mbuf chain, look through it finding + * the M_PKTHDR and return 1 if it belongs to + * the association given. We tell this by + * a kludge where we stuff the my_vtag of the asoc + * into the m->m_pkthdr.csum_data/csum field. + */ + m = this; + while (m) { + if (m->m_flags & M_PKTHDR) { + /* check it */ +#if defined(__OpenBSD__) + if ((u_int32_t)m->m_pkthdr.csum == asoc->my_vtag) +#else + if ((u_int32_t)m->m_pkthdr.csum_data == asoc->my_vtag) +#endif + { + /* Yep */ + return (1); + } + } + m = m->m_next; + } + return (0); +} + +u_int32_t +sctp_get_first_vtag_from_sb(struct socket *so) +{ + struct mbuf *this, *at; + u_int32_t retval; + + retval = 0; + if (so->so_rcv.sb_mb) { + /* grubbing time */ + this = so->so_rcv.sb_mb; + while (this) { + at = this; + /* get to the m_pkthdr */ + while (at) { + if (at->m_flags & M_PKTHDR) + break; + else { + at = at->m_next; + } + } + /* now do we have a m_pkthdr */ + if (at && (at->m_flags & M_PKTHDR)) { + /* check it */ +#if defined(__OpenBSD__) + if ((u_int32_t)at->m_pkthdr.csum != 0) +#else + if ((u_int32_t)at->m_pkthdr.csum_data != 0) +#endif + { + /* its the one */ +#if defined(__OpenBSD__) + retval = (u_int32_t)at->m_pkthdr.csum; +#else + retval = + (u_int32_t)at->m_pkthdr.csum_data; +#endif + break; + } + } + this = this->m_nextpkt; + } + + } + return (retval); + +} +void +sctp_grub_through_socket_buffer(struct sctp_inpcb *inp, struct socket *old, + struct socket *new, struct sctp_tcb *stcb) +{ + struct mbuf **put, **take, *next, *this; + struct sockbuf *old_sb, *new_sb; + struct sctp_association *asoc; + int moved_top = 0; + + asoc = &stcb->asoc; + old_sb = &old->so_rcv; + new_sb = &new->so_rcv; + if (old_sb->sb_mb == NULL) { + /* Nothing to move */ + return; + } + SOCKBUF_LOCK(old_sb); + SOCKBUF_LOCK(new_sb); + + if (inp->sctp_vtag_first == asoc->my_vtag) { + /* First one must be moved */ + struct mbuf *mm; + for (mm = old_sb->sb_mb; mm; mm = mm->m_next) { + /* + * Go down the chain and fix + * the space allocation of the + * two sockets. + */ + sbfree(old_sb, mm); + sballoc(new_sb, mm); + } + new_sb->sb_mb = old_sb->sb_mb; + old_sb->sb_mb = new_sb->sb_mb->m_nextpkt; + new_sb->sb_mb->m_nextpkt = NULL; + put = &new_sb->sb_mb->m_nextpkt; + moved_top = 1; + } else { + put = &new_sb->sb_mb; + } + + take = &old_sb->sb_mb; + next = old_sb->sb_mb; + while (next) { + this = next; + /* postion for next one */ + next = this->m_nextpkt; + /* check the tag of this packet */ + if (sctp_should_be_moved(this, asoc)) { + /* yes this needs to be moved */ + struct mbuf *mm; + *take = this->m_nextpkt; + this->m_nextpkt = NULL; + *put = this; + for (mm = this; mm; mm = mm->m_next) { + /* + * Go down the chain and fix + * the space allocation of the + * two sockets. + */ + sbfree(old_sb, mm); + sballoc(new_sb, mm); + } + put = &this->m_nextpkt; + + } else { + /* no advance our take point. */ + take = &this->m_nextpkt; + } + } + if (moved_top) { + /* + * Ok so now we must re-postion vtag_first to + * match the new first one since we moved the + * mbuf at the top. + */ + inp->sctp_vtag_first = sctp_get_first_vtag_from_sb(old); + } + SOCKBUF_UNLOCK(old_sb); + SOCKBUF_UNLOCK(new_sb); +} + +void +sctp_free_bufspace(struct sctp_tcb *stcb, struct sctp_association *asoc, + struct sctp_tmit_chunk *tp1) +{ + if (tp1->data == NULL) { + return; + } +#ifdef SCTP_MBCNT_LOGGING + sctp_log_mbcnt(SCTP_LOG_MBCNT_DECREASE, + asoc->total_output_queue_size, + tp1->book_size, + asoc->total_output_mbuf_queue_size, + tp1->mbcnt); +#endif + if (asoc->total_output_queue_size >= tp1->book_size) { + asoc->total_output_queue_size -= tp1->book_size; + } else { + asoc->total_output_queue_size = 0; + } + + /* Now free the mbuf */ + if (asoc->total_output_mbuf_queue_size >= tp1->mbcnt) { + asoc->total_output_mbuf_queue_size -= tp1->mbcnt; + } else { + asoc->total_output_mbuf_queue_size = 0; + } + if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) || + (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { + if (stcb->sctp_socket->so_snd.sb_cc >= tp1->book_size) { + stcb->sctp_socket->so_snd.sb_cc -= tp1->book_size; + } else { + stcb->sctp_socket->so_snd.sb_cc = 0; + + } + if (stcb->sctp_socket->so_snd.sb_mbcnt >= tp1->mbcnt) { + stcb->sctp_socket->so_snd.sb_mbcnt -= tp1->mbcnt; + } else { + stcb->sctp_socket->so_snd.sb_mbcnt = 0; + } + } +} + +int +sctp_release_pr_sctp_chunk(struct sctp_tcb *stcb, struct sctp_tmit_chunk *tp1, + int reason, struct sctpchunk_listhead *queue) +{ + int ret_sz = 0; + int notdone; + uint8_t foundeom = 0; + + do { + ret_sz += tp1->book_size; + tp1->sent = SCTP_FORWARD_TSN_SKIP; + if (tp1->data) { + sctp_free_bufspace(stcb, &stcb->asoc, tp1); + sctp_ulp_notify(SCTP_NOTIFY_DG_FAIL, stcb, reason, tp1); + sctp_m_freem(tp1->data); + tp1->data = NULL; + sctp_sowwakeup(stcb->sctp_ep, stcb->sctp_socket); + } + if (tp1->flags & SCTP_PR_SCTP_BUFFER) { + stcb->asoc.sent_queue_cnt_removeable--; + } + if (queue == &stcb->asoc.send_queue) { + TAILQ_REMOVE(&stcb->asoc.send_queue, tp1, sctp_next); + /* on to the sent queue */ + TAILQ_INSERT_TAIL(&stcb->asoc.sent_queue, tp1, + sctp_next); + stcb->asoc.sent_queue_cnt++; + } + if ((tp1->rec.data.rcv_flags & SCTP_DATA_NOT_FRAG) == + SCTP_DATA_NOT_FRAG) { + /* not frag'ed we ae done */ + notdone = 0; + foundeom = 1; + } else if (tp1->rec.data.rcv_flags & SCTP_DATA_LAST_FRAG) { + /* end of frag, we are done */ + notdone = 0; + foundeom = 1; + } else { + /* Its a begin or middle piece, we must mark all of it */ + notdone = 1; + tp1 = TAILQ_NEXT(tp1, sctp_next); + } + } while (tp1 && notdone); + if ((foundeom == 0) && (queue == &stcb->asoc.sent_queue)) { + /* + * The multi-part message was scattered + * across the send and sent queue. + */ + tp1 = TAILQ_FIRST(&stcb->asoc.send_queue); + /* + * recurse throught the send_queue too, starting at the + * beginning. + */ + if (tp1) { + ret_sz += sctp_release_pr_sctp_chunk(stcb, tp1, reason, + &stcb->asoc.send_queue); + } else { + printf("hmm, nothing on the send queue and no EOM?\n"); + } + } + return (ret_sz); +} + +/* + * checks to see if the given address, sa, is one that is currently + * known by the kernel + * note: can't distinguish the same address on multiple interfaces and + * doesn't handle multiple addresses with different zone/scope id's + * note: ifa_ifwithaddr() compares the entire sockaddr struct + */ +struct ifaddr * +sctp_find_ifa_by_addr(struct sockaddr *sa) +{ + struct ifnet *ifn; + struct ifaddr *ifa; + + /* go through all our known interfaces */ + TAILQ_FOREACH(ifn, &ifnet, if_list) { + /* go through each interface addresses */ + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + /* correct family? */ + if (ifa->ifa_addr->sa_family != sa->sa_family) + continue; + +#ifdef INET6 + if (ifa->ifa_addr->sa_family == AF_INET6) { + /* IPv6 address */ + struct sockaddr_in6 *sin1, *sin2, sin6_tmp; + sin1 = (struct sockaddr_in6 *)ifa->ifa_addr; + if (IN6_IS_SCOPE_LINKLOCAL(&sin1->sin6_addr)) { + /* create a copy and clear scope */ + memcpy(&sin6_tmp, sin1, + sizeof(struct sockaddr_in6)); + sin1 = &sin6_tmp; + in6_clearscope(&sin1->sin6_addr); + } + sin2 = (struct sockaddr_in6 *)sa; + if (memcmp(&sin1->sin6_addr, &sin2->sin6_addr, + sizeof(struct in6_addr)) == 0) { + /* found it */ + return (ifa); + } + } else +#endif + if (ifa->ifa_addr->sa_family == AF_INET) { + /* IPv4 address */ + struct sockaddr_in *sin1, *sin2; + sin1 = (struct sockaddr_in *)ifa->ifa_addr; + sin2 = (struct sockaddr_in *)sa; + if (sin1->sin_addr.s_addr == + sin2->sin_addr.s_addr) { + /* found it */ + return (ifa); + } + } + /* else, not AF_INET or AF_INET6, so skip */ + } /* end foreach ifa */ + } /* end foreach ifn */ + /* not found! */ + return (NULL); +} + + +#ifdef __APPLE__ +/* + * here we hack in a fix for Apple's m_copym for the case where the first mbuf + * in the chain is a M_PKTHDR and the length is zero + */ +static void +sctp_pkthdr_fix(struct mbuf *m) +{ + struct mbuf *m_nxt; + + if ((m->m_flags & M_PKTHDR) == 0) { + /* not a PKTHDR */ + return; + } + + if (m->m_len != 0) { + /* not a zero length PKTHDR mbuf */ + return; + } + + /* let's move in a word into the first mbuf... yes, ugly! */ + m_nxt = m->m_next; + if (m_nxt == NULL) { + /* umm... not a very useful mbuf chain... */ + return; + } + if ((size_t)m_nxt->m_len > sizeof(long)) { + /* move over a long */ + bcopy(mtod(m_nxt, caddr_t), mtod(m, caddr_t), sizeof(long)); + /* update mbuf data pointers and lengths */ + m->m_len += sizeof(long); + m_nxt->m_data += sizeof(long); + m_nxt->m_len -= sizeof(long); + } +} + +inline struct mbuf * +sctp_m_copym(struct mbuf *m, int off, int len, int wait) +{ + sctp_pkthdr_fix(m); + return (m_copym(m, off, len, wait)); +} +#endif /* __APPLE__ */ diff --git a/sys/netinet/sctputil.h b/sys/netinet/sctputil.h new file mode 100644 index 0000000000..b2ad9def2b --- /dev/null +++ b/sys/netinet/sctputil.h @@ -0,0 +1,283 @@ +/* $KAME: sctputil.h,v 1.14 2004/08/17 04:06:21 itojun Exp $ */ +/* $DragonFly: src/sys/netinet/sctputil.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +#ifndef __sctputil_h__ +#define __sctputil_h__ + +/* + * Copyright (C) 2002, 2003, 2004 Cisco Systems Inc, + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) + +#ifdef SCTP_MBUF_DEBUG +#define sctp_m_freem(m) do { \ + printf("m_freem(%p) m->nxtpkt:%p at %s[%d]\n", \ + (m), (m)->m_next, __FILE__, __LINE__); \ + m_freem(m); \ +} while (0); +#else +#define sctp_m_freem m_freem +#endif + +#ifdef __APPLE__ +struct mbuf *sctp_m_copym(struct mbuf *m, int off, int len, int wait); +#else +#define sctp_m_copym m_copym +#endif /* __APPLE__ */ + +/* + * Zone(pool) allocation routines: MUST be defined for each OS + * zone = zone/pool pointer + * name = string name of the zone/pool + * size = size of each zone/pool element + * number = number of elements in zone/pool + */ +#if defined(__FreeBSD__) +#if __FreeBSD_version >= 500000 +#include +#else +#include +#endif +#elif defined(__NetBSD__) || defined(__OpenBSD__) +#include +#endif + +/* SCTP_ZONE_INIT: initialize the zone */ +#if defined(__FreeBSD__) +#if __FreeBSD_version >= 500000 +#define UMA_ZFLAG_FULL 0x0020 +#define SCTP_ZONE_INIT(zone, name, size, number) { \ + zone = uma_zcreate(name, size, NULL, NULL, NULL, NULL, UMA_ALIGN_PTR,\ + UMA_ZFLAG_FULL); \ + uma_zone_set_max(zone, number); \ +} +#else +#define SCTP_ZONE_INIT(zone, name, size, number) \ + zone = zinit(name, size, number, ZONE_INTERRUPT, 0); +#endif +#elif defined(__APPLE__) +#define SCTP_ZONE_INIT(zone, name, size, number) \ + zone = (void *)zinit(size, number * size, number, name); +#elif defined(__OpenBSD__) || defined(__NetBSD__) +#define SCTP_ZONE_INIT(zone, name, size, number) \ + pool_init(&(zone), size, 0, 0, 0, name, NULL); +#else + /* don't know this OS! */ + force_comile_error; +#endif + +/* SCTP_ZONE_GET: allocate element from the zone */ +#if defined(__FreeBSD__) +#if __FreeBSD_version >= 500000 +#define SCTP_ZONE_GET(zone) \ + uma_zalloc(zone, M_NOWAIT); +#else +#define SCTP_ZONE_GET(zone) \ + zalloci(zone); +#endif +#elif defined(__APPLE__) +#define SCTP_ZONE_GET(zone) \ + zalloc(zone); +#elif defined(__NetBSD__) || defined(__OpenBSD__) +#define SCTP_ZONE_GET(zone) \ + pool_get(&zone, PR_NOWAIT); +#else + /* don't know this OS! */ + force_comile_error; +#endif + +/* SCTP_ZONE_FREE: free element from the zone */ +#if defined(__FreeBSD__) +#if __FreeBSD_version >= 500000 +#define SCTP_ZONE_FREE(zone, element) \ + uma_zfree(zone, element); +#else +#define SCTP_ZONE_FREE(zone, element) \ + zfreei(zone, element); +#endif +#elif defined(__APPLE__) +#define SCTP_ZONE_FREE(zone, element) \ + zfree(zone, element); +#elif defined(__NetBSD__) || defined(__OpenBSD__) +#define SCTP_ZONE_FREE(zone, element) \ + pool_put(&zone, element); +#else + /* don't know this OS! */ + force_comile_error; +#endif + +#define sctp_get_associd(stcb) ((sctp_assoc_t)stcb->asoc.my_vtag) + +/* + * Function prototypes + */ +struct ifaddr *sctp_find_ifa_by_addr(struct sockaddr *sa); + +u_int32_t sctp_select_initial_TSN(struct sctp_pcb *); + +u_int32_t sctp_select_a_tag(struct sctp_inpcb *); + +int sctp_init_asoc(struct sctp_inpcb *, struct sctp_association *, int, uint32_t); + +void sctp_fill_random_store(struct sctp_pcb *); + +int sctp_timer_start(int, struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); + +int sctp_timer_stop(int, struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); + +u_int32_t sctp_calculate_sum(struct mbuf *, int32_t *, u_int32_t); + +void sctp_mtu_size_reset(struct sctp_inpcb *, struct sctp_association *, + u_long); + +int find_next_best_mtu(int); + +u_int32_t sctp_calculate_rto(struct sctp_tcb *, struct sctp_association *, + struct sctp_nets *, struct timeval *); + +u_int32_t sctp_calculate_len(struct mbuf *); + +caddr_t sctp_m_getptr(struct mbuf *, int, int, u_int8_t *); + +struct sctp_paramhdr *sctp_get_next_param(struct mbuf *, int, + struct sctp_paramhdr *, int); + +int sctp_add_pad_tombuf(struct mbuf *, int); + +int sctp_pad_lastmbuf(struct mbuf *, int); + +void sctp_ulp_notify(u_int32_t, struct sctp_tcb *, u_int32_t, void *); + +void sctp_report_all_outbound(struct sctp_tcb *); + +int sctp_expand_mapping_array(struct sctp_association *); + +void sctp_abort_notification(struct sctp_tcb *, int); + +/* We abort responding to an IP packet for some reason */ +void sctp_abort_association(struct sctp_inpcb *, struct sctp_tcb *, + struct mbuf *, int, struct sctphdr *, struct mbuf *); + +/* We choose to abort via user input */ +void sctp_abort_an_association(struct sctp_inpcb *, struct sctp_tcb *, int, + struct mbuf *); + +void sctp_handle_ootb(struct mbuf *, int, int, struct sctphdr *, + struct sctp_inpcb *, struct mbuf *); + +int sctp_is_there_an_abort_here(struct mbuf *, int, int *); +uint32_t sctp_is_same_scope(struct sockaddr_in6 *, struct sockaddr_in6 *); +struct sockaddr_in6 *sctp_recover_scope(struct sockaddr_in6 *, + struct sockaddr_in6 *); + +int sctp_cmpaddr(struct sockaddr *, struct sockaddr *); + +void sctp_print_address(struct sockaddr *); +void sctp_print_address_pkt(struct ip *, struct sctphdr *); + +int sbappendaddr_nocheck __P((struct sockbuf *, struct sockaddr *, + struct mbuf *, struct mbuf *, u_int32_t, struct sctp_inpcb *)); + + +int sctp_release_pr_sctp_chunk(struct sctp_tcb *, struct sctp_tmit_chunk *, + int, struct sctpchunk_listhead *); + +struct mbuf *sctp_generate_invmanparam(int); + +/* + * this is an evil layer violation that I think is a hack.. but I stand + * alone on the tsvwg in this thought... everyone else considers it part + * of the sockets layer (along with all of the peeloff code :<) + */ +u_int32_t sctp_get_first_vtag_from_sb(struct socket *); + + +void sctp_grub_through_socket_buffer(struct sctp_inpcb *, struct socket *, + struct socket *, struct sctp_tcb *); + +void sctp_free_bufspace(struct sctp_tcb *, struct sctp_association *, + struct sctp_tmit_chunk *); + +#ifdef SCTP_STAT_LOGGING +void sctp_log_strm_del_alt(u_int32_t, u_int16_t, int); + +void sctp_log_strm_del(struct sctp_tmit_chunk *, struct sctp_tmit_chunk *, int); +void sctp_log_cwnd(struct sctp_nets *, int, uint8_t); +void sctp_log_maxburst(struct sctp_nets *, int, int, uint8_t); +void sctp_log_block(uint8_t, struct socket *, struct sctp_association *); +void sctp_log_rwnd(uint8_t, u_int32_t, u_int32_t, u_int32_t ); +void sctp_log_mbcnt(uint8_t, u_int32_t, u_int32_t, u_int32_t, u_int32_t); +void sctp_log_rwnd_set(uint8_t, u_int32_t, u_int32_t, u_int32_t, u_int32_t); +int sctp_fill_stat_log(struct mbuf *); +void sctp_log_fr(uint32_t, uint32_t, uint32_t, int); +void sctp_log_map(uint32_t, uint32_t, uint32_t, int); + +void sctp_clr_stat_log(void); + +#endif + +#ifdef SCTP_AUDITING_ENABLED +void sctp_auditing(int, struct sctp_inpcb *, struct sctp_tcb *, + struct sctp_nets *); +void sctp_audit_log(u_int8_t, u_int8_t); + +#endif + +#ifdef SCTP_BASE_FREEBSD +/* Note: these are in , but not in kernel space */ +#define timerclear(tvp) (tvp)->tv_sec = (tvp)->tv_usec = 0 +#define timerisset(tvp) ((tvp)->tv_sec || (tvp)->tv_usec) +#define timercmp(tvp, uvp, cmp) \ + (((tvp)->tv_sec == (uvp)->tv_sec) ? \ + ((tvp)->tv_usec cmp (uvp)->tv_usec) : \ + ((tvp)->tv_sec cmp (uvp)->tv_sec)) +#define timeradd(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec + (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec + (uvp)->tv_usec; \ + if ((vvp)->tv_usec >= 1000000) { \ + (vvp)->tv_sec++; \ + (vvp)->tv_usec -= 1000000; \ + } \ + } while (/* CONSTCOND */ 0) +#define timersub(tvp, uvp, vvp) \ + do { \ + (vvp)->tv_sec = (tvp)->tv_sec - (uvp)->tv_sec; \ + (vvp)->tv_usec = (tvp)->tv_usec - (uvp)->tv_usec; \ + if ((vvp)->tv_usec < 0) { \ + (vvp)->tv_sec--; \ + (vvp)->tv_usec += 1000000; \ + } \ + } while (/* CONSTCOND */ 0) +#endif /* SCTP_BASE_FREEBSD */ + +#endif /* _KERNEL */ +#endif diff --git a/sys/netinet6/sctp6_usrreq.c b/sys/netinet6/sctp6_usrreq.c new file mode 100644 index 0000000000..5564c92a1c --- /dev/null +++ b/sys/netinet6/sctp6_usrreq.c @@ -0,0 +1,1829 @@ +/* $KAME: sctp6_usrreq.c,v 1.35 2004/08/17 06:28:03 t-momose Exp $ */ +/* $DragonFly: src/sys/netinet6/sctp6_usrreq.c,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2003, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#if !(defined(__OpenBSD__) || defined(__APPLE__)) +#include "opt_inet.h" +#endif +#ifdef __FreeBSD__ +#include "opt_inet6.h" +#include "opt_inet.h" +#endif +#ifdef __NetBSD__ +#include "opt_inet.h" +#endif +#if !(defined(__OpenBSD__) || defined(__APPLE__)) +#include "opt_ipsec.h" +#endif +#ifdef __APPLE__ +#include +#elif !defined(__OpenBSD__) +#include "opt_sctp.h" +#endif + +#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 +#include +#include +#include +#include +#include +#include +#include +#if !defined(__OpenBSD__) +#include +#endif +#include +#include +#include +#include + +#ifdef IPSEC +#ifndef __OpenBSD__ +#include +#else +#undef IPSEC +#endif +#endif /*IPSEC*/ + +#if defined(NFAITH) && NFAITH > 0 +#include +#endif + +#include + +extern struct protosw inetsw[]; + +#if defined(HAVE_NRL_INPCB) || defined(__FreeBSD__) +#ifndef in6pcb +#define in6pcb inpcb +#endif +#ifndef sotoin6pcb +#define sotoin6pcb sotoinpcb +#endif +#endif + +#ifdef SCTP_DEBUG +extern u_int32_t sctp_debug_on; +#endif + +static int sctp6_detach __P((struct socket *so)); + +#if !(defined(__FreeBSD__) || defined(__APPLE__)) +extern void in6_sin_2_v4mapsin6 (struct sockaddr_in *sin, + struct sockaddr_in6 *sin6); +extern void in6_sin6_2_sin (struct sockaddr_in *, + struct sockaddr_in6 *sin6); +extern void in6_sin6_2_sin_in_sock(struct sockaddr *nam); + +/* + * Convert sockaddr_in6 to sockaddr_in. Original sockaddr_in6 must be + * v4 mapped addr or v4 compat addr + */ +void +in6_sin6_2_sin(struct sockaddr_in *sin, struct sockaddr_in6 *sin6) +{ + bzero(sin, sizeof(*sin)); + sin->sin_len = sizeof(struct sockaddr_in); + sin->sin_family = AF_INET; + sin->sin_port = sin6->sin6_port; + sin->sin_addr.s_addr = sin6->sin6_addr.s6_addr32[3]; +} + +/* Convert sockaddr_in to sockaddr_in6 in v4 mapped addr format. */ +void +in6_sin_2_v4mapsin6(struct sockaddr_in *sin, struct sockaddr_in6 *sin6) +{ + bzero(sin6, sizeof(*sin6)); + sin6->sin6_len = sizeof(struct sockaddr_in6); + sin6->sin6_family = AF_INET6; + sin6->sin6_port = sin->sin_port; + sin6->sin6_addr.s6_addr32[0] = 0; + sin6->sin6_addr.s6_addr32[1] = 0; + sin6->sin6_addr.s6_addr32[2] = IPV6_ADDR_INT32_SMP; + sin6->sin6_addr.s6_addr32[3] = sin->sin_addr.s_addr; +} + +/* Convert sockaddr_in6 into sockaddr_in. */ +void +in6_sin6_2_sin_in_sock(struct sockaddr *nam) +{ + struct sockaddr_in *sin_p; + struct sockaddr_in6 sin6; + + /* save original sockaddr_in6 addr and convert it to sockaddr_in */ + sin6 = *(struct sockaddr_in6 *)nam; + sin_p = (struct sockaddr_in *)nam; + in6_sin6_2_sin(sin_p, &sin6); +} + +#endif /* !(__FreeBSD__ || __APPLE__) */ + +extern int sctp_no_csum_on_loopback; + +int +#if defined(__APPLE__) +sctp6_input(mp, offp) +#else +sctp6_input(mp, offp, proto) +#endif + struct mbuf **mp; + int *offp; +#ifndef __APPLE__ + int proto; +#endif +{ + struct mbuf *m = *mp; + struct ip6_hdr *ip6; + struct sctphdr *sh; + struct sctp_inpcb *in6p = NULL; + struct sctp_nets *net; + int refcount_up = 0; + u_int32_t check, calc_check; + struct inpcb *in6p_ip; + struct sctp_chunkhdr *ch; + struct mbuf *opts = NULL; + int length, mlen, offset, iphlen; + u_int8_t ecn_bits; + struct sctp_tcb *stcb = NULL; + int off = *offp; + int s; + + ip6 = mtod(m, struct ip6_hdr *); +#ifndef PULLDOWN_TEST + /* If PULLDOWN_TEST off, must be in a single mbuf. */ + IP6_EXTHDR_CHECK(m, off, (int)(sizeof(*sh) + sizeof(*ch)), IPPROTO_DONE); + sh = (struct sctphdr *)((caddr_t)ip6 + off); + ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(*sh)); +#else + /* Ensure that (sctphdr + sctp_chunkhdr) in a row. */ + IP6_EXTHDR_GET(sh, struct sctphdr *, m, off, sizeof(*sh) + sizeof(*ch)); + if (sh == NULL) { + sctp_pegs[SCTP_HDR_DROPS]++; + return IPPROTO_DONE; + } + ch = (struct sctp_chunkhdr *)((caddr_t)sh + sizeof(struct sctphdr)); +#endif + + iphlen = off; + offset = iphlen + sizeof(*sh) + sizeof(*ch); + +#if defined(NFAITH) && NFAITH > 0 +#if defined(__FreeBSD_cc_version) && __FreeBSD_cc_version <= 430000 +#if defined(NFAITH) && 0 < NFAITH + if (faithprefix(&ip6h->ip6_dst)) { + /* XXX send icmp6 host/port unreach? */ + goto bad; + } +#endif +#else + +#ifdef __FreeBSD__ + if (faithprefix_p != NULL && (*faithprefix_p)(&ip6->ip6_dst)) { + /* XXX send icmp6 host/port unreach? */ + goto bad; + } +#else + if (faithprefix(&ip6->ip6_dst)) + goto bad; +#endif +#endif /* __FreeBSD_cc_version */ + +#endif /* NFAITH defined and > 0 */ + sctp_pegs[SCTP_INPKTS]++; +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("V6 input gets a packet iphlen:%d pktlen:%d\n", iphlen, m->m_pkthdr.len); + } +#endif + if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst)) { + /* No multi-cast support in SCTP */ + sctp_pegs[SCTP_IN_MCAST]++; + goto bad; + } + /* destination port of 0 is illegal, based on RFC2960. */ + if (sh->dest_port == 0) + goto bad; + if ((sctp_no_csum_on_loopback == 0) || + (m->m_pkthdr.rcvif == NULL) || + (m->m_pkthdr.rcvif->if_type != IFT_LOOP)) { + /* we do NOT validate things from the loopback if the + * sysctl is set to 1. + */ + check = sh->checksum; /* save incoming checksum */ + if ((check == 0) && (sctp_no_csum_on_loopback)) { + /* special hook for where we got a local address + * somehow routed across a non IFT_LOOP type interface + */ + if (IN6_ARE_ADDR_EQUAL(&ip6->ip6_src, &ip6->ip6_dst)) + goto sctp_skip_csum; + } + sh->checksum = 0; /* prepare for calc */ + calc_check = sctp_calculate_sum(m, &mlen, iphlen); + if (calc_check != check) { +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("Bad CSUM on SCTP packet calc_check:%x check:%x m:%x mlen:%d iphlen:%d\n", + calc_check, check, (u_int)m, + mlen, iphlen); + } +#endif + stcb = sctp_findassociation_addr(m, iphlen, offset - sizeof(*ch), + sh, ch, &in6p, &net); + /* in6p's ref-count increased && stcb locked */ + if ((in6p) && (stcb)) { + sctp_send_packet_dropped(stcb, net, m, iphlen, 1); + sctp_chunk_output((struct sctp_inpcb *)in6p, stcb, 2); + } else if ((in6p != NULL) && (stcb == NULL)) { + refcount_up = 1; + } + sctp_pegs[SCTP_BAD_CSUM]++; + goto bad; + } + sh->checksum = calc_check; + } else { +sctp_skip_csum: + mlen = m->m_pkthdr.len; + } + net = NULL; + /* + * Locate pcb and tcb for datagram + * sctp_findassociation_addr() wants IP/SCTP/first chunk header... + */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_INPUT1) { + printf("V6 Find the association\n"); + } +#endif + stcb = sctp_findassociation_addr(m, iphlen, offset - sizeof(*ch), + sh, ch, &in6p, &net); + /* in6p's ref-count increased */ + if (in6p == NULL) { + struct sctp_init_chunk *init_chk, chunk_buf; + + sctp_pegs[SCTP_NOPORTS]++; + if (ch->chunk_type == SCTP_INITIATION) { + /* we do a trick here to get the INIT tag, + * dig in and get the tag from the INIT and + * put it in the common header. + */ + init_chk = (struct sctp_init_chunk *)sctp_m_getptr(m, + iphlen + sizeof(*sh), sizeof(*init_chk), + (u_int8_t *)&chunk_buf); + sh->v_tag = init_chk->init.initiate_tag; + } + sctp_send_abort(m, iphlen, sh, 0, NULL); + goto bad; + } else if (stcb == NULL) { + refcount_up = 1; + } + in6p_ip = (struct inpcb *)in6p; +#ifdef IPSEC + /* + * Check AH/ESP integrity. + */ +#ifdef __OpenBSD__ + { + struct inpcb *i_inp; + struct m_tag *mtag; + struct tdb_ident *tdbi; + struct tdb *tdb; + int error, s; + + /* Find most recent IPsec tag */ + i_inp = (struct inpcb *)in6p; + mtag = m_tag_find(m, PACKET_TAG_IPSEC_IN_DONE, NULL); + s = splnet(); + if (mtag != NULL) { + tdbi = (struct tdb_ident *)(mtag + 1); + tdb = gettdb(tdbi->spi, &tdbi->dst, tdbi->proto); + } else + tdb = NULL; + + ipsp_spd_lookup(m, af, iphlen, &error, IPSP_DIRECTION_IN, + tdb, i_inp); + if (error) { + splx(s); + goto bad; + } + + /* Latch SA */ + if (i_inp->inp_tdb_in != tdb) { + if (tdb) { + tdb_add_inp(tdb, i_inp, 1); + if (i_inp->inp_ipo == NULL) { + i_inp->inp_ipo = ipsec_add_policy(i_inp, + af, IPSP_DIRECTION_OUT); + if (i_inp->inp_ipo == NULL) { + splx(s); + goto bad; + } + } + if (i_inp->inp_ipo->ipo_dstid == NULL && + tdb->tdb_srcid != NULL) { + i_inp->inp_ipo->ipo_dstid = + tdb->tdb_srcid; + tdb->tdb_srcid->ref_count++; + } + if (i_inp->inp_ipsec_remotecred == NULL && + tdb->tdb_remote_cred != NULL) { + i_inp->inp_ipsec_remotecred = + tdb->tdb_remote_cred; + tdb->tdb_remote_cred->ref_count++; + } + if (i_inp->inp_ipsec_remoteauth == NULL && + tdb->tdb_remote_auth != NULL) { + i_inp->inp_ipsec_remoteauth = + tdb->tdb_remote_auth; + tdb->tdb_remote_auth->ref_count++; + } + } else { /* Just reset */ + TAILQ_REMOVE(&i_inp->inp_tdb_in->tdb_inp_in, + i_inp, binp_tdb_in_next); + i_inp->inp_tdb_in = NULL; + } + } + splx(s); + } +#else + if (ipsec6_in_reject_so(m, in6p->sctp_socket)) { +/* XXX */ +#ifndef __APPLE__ + /* FIX ME: need to find right stat for __APPLE__ */ + ipsec6stat.in_polvio++; +#endif + goto bad; + } +#endif +#endif /*IPSEC*/ + + /* + * Construct sockaddr format source address. + * Stuff source address and datagram in user buffer. + */ + if ((in6p->ip_inp.inp.inp_flags & INP_CONTROLOPTS) +#ifndef __OpenBSD__ + || (in6p->sctp_socket->so_options & SO_TIMESTAMP) +#endif + ) { +#if defined(__FreeBSD__) || defined(__APPLE__) +#if (defined(SCTP_BASE_FREEBSD) && __FreeBSD_version < 501113) || defined(__APPLE__) + ip6_savecontrol(in6p_ip, &opts, ip6, m); +#elif __FreeBSD_version >= 440000 || (defined(SCTP_BASE_FREEBSD) && __FreeBSD_version >= 501113) + ip6_savecontrol(in6p_ip, m, &opts); +#else + ip6_savecontrol(in6p_ip, m, &opts, NULL); +#endif +#else + ip6_savecontrol((struct in6pcb *)in6p_ip, m, &opts); +#endif + } + + /* + * CONTROL chunk processing + */ + length = ntohs(ip6->ip6_plen) + iphlen; + offset -= sizeof(*ch); + ecn_bits = ((ntohl(ip6->ip6_flow) >> 20) & 0x000000ff); +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + (void)sctp_common_input_processing(&m, iphlen, offset, length, sh, ch, + in6p, stcb, net, ecn_bits); + /* inp's ref-count reduced && stcb unlocked */ + splx(s); + /* XXX this stuff below gets moved to appropriate parts later... */ + if (m) + m_freem(m); + if (opts) + m_freem(opts); + + if ((in6p) && refcount_up){ + /* reduce ref-count */ + SCTP_INP_WLOCK(in6p); + SCTP_INP_DECR_REF(in6p); + SCTP_INP_WUNLOCK(in6p); + } + + return IPPROTO_DONE; + +bad: + if (stcb) + SCTP_TCB_UNLOCK(stcb); + + if ((in6p) && refcount_up){ + /* reduce ref-count */ + SCTP_INP_WLOCK(in6p); + SCTP_INP_DECR_REF(in6p); + SCTP_INP_WUNLOCK(in6p); + } + if (m) + m_freem(m); + if (opts) + m_freem(opts); + return IPPROTO_DONE; +} + + +static void +sctp6_notify_mbuf(struct sctp_inpcb *inp, + struct icmp6_hdr *icmp6, + struct sctphdr *sh, + struct sctp_tcb *stcb, + struct sctp_nets *net) +{ + unsigned int nxtsz; + + if ((inp == NULL) || (stcb == NULL) || (net == NULL) || + (icmp6 == NULL) || (sh == NULL)) { + goto out; + } + + /* First do we even look at it? */ + if (ntohl(sh->v_tag) != (stcb->asoc.peer_vtag)) + goto out; + + if (icmp6->icmp6_type != ICMP6_PACKET_TOO_BIG) { + /* not PACKET TO BIG */ + goto out; + } + /* + * ok we need to look closely. We could even get smarter and + * look at anyone that we sent to in case we get a different + * ICMP that tells us there is no way to reach a host, but for + * this impl, all we care about is MTU discovery. + */ + nxtsz = ntohl(icmp6->icmp6_mtu); + /* Stop any PMTU timer */ + sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, NULL); + + /* Adjust destination size limit */ + if (net->mtu > nxtsz) { + net->mtu = nxtsz; + } + /* now what about the ep? */ + if (stcb->asoc.smallest_mtu > nxtsz) { + struct sctp_tmit_chunk *chk; + struct sctp_stream_out *strm; + /* Adjust that too */ + stcb->asoc.smallest_mtu = nxtsz; + /* now off to subtract IP_DF flag if needed */ + + TAILQ_FOREACH(chk, &stcb->asoc.send_queue, sctp_next) { + if ((chk->send_size+IP_HDR_SIZE) > nxtsz) { + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + } + } + TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) { + if ((chk->send_size+IP_HDR_SIZE) > nxtsz) { + /* + * For this guy we also mark for immediate + * resend since we sent to big of chunk + */ + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + if (chk->sent != SCTP_DATAGRAM_RESEND) + stcb->asoc.sent_queue_retran_cnt++; + chk->sent = SCTP_DATAGRAM_RESEND; + chk->rec.data.doing_fast_retransmit = 0; + + chk->sent = SCTP_DATAGRAM_RESEND; + /* Clear any time so NO RTT is being done */ + chk->sent_rcv_time.tv_sec = 0; + chk->sent_rcv_time.tv_usec = 0; + stcb->asoc.total_flight -= chk->send_size; + net->flight_size -= chk->send_size; + } + } + TAILQ_FOREACH(strm, &stcb->asoc.out_wheel, next_spoke) { + TAILQ_FOREACH(chk, &strm->outqueue, sctp_next) { + if ((chk->send_size+IP_HDR_SIZE) > nxtsz) { + chk->flags |= CHUNK_FLAGS_FRAGMENT_OK; + } + } + } + } + sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, NULL); +out: + if (inp) { + /* reduce inp's ref-count */ + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + if (stcb) + SCTP_TCB_UNLOCK(stcb); +} + + +void +sctp6_ctlinput(cmd, pktdst, d) + int cmd; + struct sockaddr *pktdst; + void *d; +{ + struct sctphdr sh; + struct ip6ctlparam *ip6cp = NULL; + int s, cm; + + if (pktdst->sa_family != AF_INET6 || + pktdst->sa_len != sizeof(struct sockaddr_in6)) + return; + + if ((unsigned)cmd >= PRC_NCMDS) + return; + if (PRC_IS_REDIRECT(cmd)) { + d = NULL; + } else if (inet6ctlerrmap[cmd] == 0) { + return; + } + + /* if the parameter is from icmp6, decode it. */ + if (d != NULL) { + ip6cp = (struct ip6ctlparam *)d; + } else { + ip6cp = (struct ip6ctlparam *)NULL; + } + + if (ip6cp) { + /* + * XXX: We assume that when IPV6 is non NULL, + * M and OFF are valid. + */ + /* check if we can safely examine src and dst ports */ + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + struct sctp_nets *net; + struct sockaddr_in6 final; + + if (ip6cp->ip6c_m == NULL || + (size_t)ip6cp->ip6c_m->m_pkthdr.len < (ip6cp->ip6c_off + sizeof(sh))) + return; + + bzero(&sh, sizeof(sh)); + bzero(&final, sizeof(final)); + inp = NULL; + net = NULL; + m_copydata(ip6cp->ip6c_m, ip6cp->ip6c_off, sizeof(sh), + (caddr_t)&sh); + ip6cp->ip6c_src->sin6_port = sh.src_port; + final.sin6_len = sizeof(final); + final.sin6_family = AF_INET6; +#if defined(__FreeBSD__) && __FreeBSD_cc_version < 440000 + final.sin6_addr = *ip6cp->ip6c_finaldst; +#else + final.sin6_addr = ((struct sockaddr_in6 *)pktdst)->sin6_addr; +#endif /* __FreeBSD_cc_version */ + final.sin6_port = sh.dest_port; +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + stcb = sctp_findassociation_addr_sa((struct sockaddr *)ip6cp->ip6c_src, + (struct sockaddr *)&final, + &inp, &net, 1); + /* inp's ref-count increased && stcb locked */ + if (stcb != NULL && inp && (inp->sctp_socket != NULL)) { + if (cmd == PRC_MSGSIZE) { + sctp6_notify_mbuf(inp, + ip6cp->ip6c_icmp6, + &sh, + stcb, + net); + /* inp's ref-count reduced && stcb unlocked */ + } else { + if (cmd == PRC_HOSTDEAD) { + cm = EHOSTUNREACH; + } else { + cm = inet6ctlerrmap[cmd]; + } + sctp_notify(inp, cm, &sh, + (struct sockaddr *)&final, + stcb, net); + /* inp's ref-count reduced && stcb unlocked */ + } + } else { + if (PRC_IS_REDIRECT(cmd) && inp) { +#ifdef __OpenBSD__ + in_rtchange((struct inpcb *)inp, + inetctlerrmap[cmd]); +#else + in6_rtchange((struct in6pcb *)inp, + inet6ctlerrmap[cmd]); +#endif + } + if (inp) { + /* reduce inp's ref-count */ + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + if (stcb) + SCTP_TCB_UNLOCK(stcb); + } + splx(s); + } +} + +/* + * this routine can probably be collasped into the one in sctp_userreq.c + * since they do the same thing and now we lookup with a sockaddr + */ +#ifdef __FreeBSD__ +static int +sctp6_getcred(SYSCTL_HANDLER_ARGS) +{ + struct sockaddr_in6 addrs[2]; + struct sctp_inpcb *inp; + struct sctp_nets *net; + struct sctp_tcb *stcb; + int error, s; + +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 + error = suser(req->td); +#else + error = suser(req->p); +#endif + if (error) + return (error); + + if (req->newlen != sizeof(addrs)) + return (EINVAL); + if (req->oldlen != sizeof(struct ucred)) + return (EINVAL); + error = SYSCTL_IN(req, addrs, sizeof(addrs)); + if (error) + return (error); +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + + stcb = sctp_findassociation_addr_sa(sin6tosa(&addrs[0]), + sin6tosa(&addrs[1]), + &inp, &net, 1); + if (stcb == NULL || inp == NULL || inp->sctp_socket == NULL) { + error = ENOENT; + if (inp) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + goto out; + } + error = SYSCTL_OUT(req, inp->sctp_socket->so_cred, + sizeof(struct ucred)); + + SCTP_TCB_UNLOCK (stcb); + out: + splx(s); + return (error); +} + +SYSCTL_PROC(_net_inet6_sctp6, OID_AUTO, getcred, CTLTYPE_OPAQUE|CTLFLAG_RW, + 0, 0, + sctp6_getcred, "S,ucred", "Get the ucred of a SCTP6 connection"); + +#endif + +/* This is the same as the sctp_abort() could be made common */ +static int +sctp6_abort(struct socket *so) +{ + struct sctp_inpcb *inp; + int s; + + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) + return EINVAL; /* ??? possible? panic instead? */ + soisdisconnected(so); +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + sctp_inpcb_free(inp, 1); + splx(s); + return 0; +} + +static int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp6_attach(struct socket *so, int proto, struct thread *p) +#else +sctp6_attach(struct socket *so, int proto, struct proc *p) +#endif +{ + struct in6pcb *inp6; + int s, error; + struct sctp_inpcb *inp; + + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp != NULL) + return EINVAL; + + if (so->so_snd.sb_hiwat == 0 || so->so_rcv.sb_hiwat == 0) { + error = soreserve(so, sctp_sendspace, sctp_recvspace); + if (error) + return error; + } +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + error = sctp_inpcb_alloc(so); + splx(s); + if (error) + return error; + inp = (struct sctp_inpcb *)so->so_pcb; + inp->sctp_flags |= SCTP_PCB_FLAGS_BOUND_V6; /* I'm v6! */ + inp6 = (struct in6pcb *)inp; + +#if defined(__FreeBSD__) || defined(__APPLE__) + inp6->inp_vflag |= INP_IPV6; +#else +#if defined(__OpenBSD__) + inp->ip_inp.inp.inp_flags |= INP_IPV6; +#else + inp->inp_vflag |= INP_IPV6; +#endif +#endif +#if defined(__NetBSD__) + if (ip6_v6only) { + inp6->in6p_flags |= IN6P_IPV6_V6ONLY; + } + so->so_send = sctp_sosend; +#endif + inp6->in6p_hops = -1; /* use kernel default */ + inp6->in6p_cksum = -1; /* just to be sure */ +#ifdef INET + /* + * XXX: ugly!! + * IPv4 TTL initialization is necessary for an IPv6 socket as well, + * because the socket may be bound to an IPv6 wildcard address, + * which may match an IPv4-mapped IPv6 address. + */ +#if defined(__FreeBSD__) || defined(__APPLE__) + inp6->inp_ip_ttl = ip_defttl; +#else + inp->inp_ip_ttl = ip_defttl; +#endif +#endif + /* + * Hmm what about the IPSEC stuff that is missing here but + * in sctp_attach()? + */ + return 0; +} + +static int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp6_bind(struct socket *so, struct sockaddr *addr, struct thread *p) +{ +#else +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp6_bind(struct socket *so, struct sockaddr *addr, struct proc *p) +{ +#else +sctp6_bind(struct socket *so, struct mbuf *nam, struct proc *p) +{ + struct sockaddr *addr = nam ? mtod(nam, struct sockaddr *) : NULL; +#endif +#endif + struct sctp_inpcb *inp; + struct in6pcb *inp6; + int s, error; + + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) + return EINVAL; + + inp6 = (struct in6pcb *)inp; +#if defined(__FreeBSD__) || defined(__APPLE__) + inp6->inp_vflag &= ~INP_IPV4; + inp6->inp_vflag |= INP_IPV6; +#else +#if defined(__OpenBSD__) + inp->ip_inp.inp.inp_flags &= ~INP_IPV4; + inp->ip_inp.inp.inp_flags |= INP_IPV6; +#else + inp->inp_vflag &= ~INP_IPV4; + inp->inp_vflag |= INP_IPV6; +#endif +#endif + if (addr != NULL && +#if defined(__OpenBSD__) + (0) /* we always do dual bind */ +#elif defined (__NetBSD__) + (inp6->in6p_flags & IN6P_IPV6_V6ONLY) +#else + (inp6->inp_flags & IN6P_IPV6_V6ONLY) +#endif + == 0) { + if (addr->sa_family == AF_INET) { + /* binding v4 addr to v6 socket, so reset flags */ +#if defined(__FreeBSD__) || defined(__APPLE__) + inp6->inp_vflag |= INP_IPV4; + inp6->inp_vflag &= ~INP_IPV6; +#else +#if defined(__OpenBSD__) + inp->ip_inp.inp.inp_flags |= INP_IPV4; + inp->ip_inp.inp.inp_flags &= ~INP_IPV6; +#else + inp->inp_vflag |= INP_IPV4; + inp->inp_vflag &= ~INP_IPV6; +#endif +#endif + } else { + struct sockaddr_in6 *sin6_p; + sin6_p = (struct sockaddr_in6 *)addr; + + if (IN6_IS_ADDR_UNSPECIFIED(&sin6_p->sin6_addr)) { +#if defined(__FreeBSD__) || defined(__APPLE__) + inp6->inp_vflag |= INP_IPV4; +#else +#if defined(__OpenBSD__) + inp->ip_inp.inp.inp_flags |= INP_IPV4; +#else + inp->inp_vflag |= INP_IPV4; +#endif +#endif + } + else if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) { + struct sockaddr_in sin; + in6_sin6_2_sin(&sin, sin6_p); +#if defined(__FreeBSD__) || defined(__APPLE__) + inp6->inp_vflag |= INP_IPV4; + inp6->inp_vflag &= ~INP_IPV6; +#else +#if defined(__OpenBSD__) + inp->ip_inp.inp.inp_flags |= INP_IPV4; + inp->ip_inp.inp.inp_flags &= ~INP_IPV6; + +#else + inp->inp_vflag |= INP_IPV4; + inp->inp_vflag &= ~INP_IPV6; +#endif +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + error = sctp_inpcb_bind(so, (struct sockaddr *)&sin, p); + splx(s); + return error; + } + } + } else if (addr != NULL) { + /* IPV6_V6ONLY socket */ + if (addr->sa_family == AF_INET) { + /* can't bind v4 addr to v6 only socket! */ + return EINVAL; + } else { + struct sockaddr_in6 *sin6_p; + sin6_p = (struct sockaddr_in6 *)addr; + + if (IN6_IS_ADDR_V4MAPPED(&sin6_p->sin6_addr)) + /* can't bind v4-mapped addrs either! */ + /* NOTE: we don't support SIIT */ + return EINVAL; + } + } +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + error = sctp_inpcb_bind(so, addr, p); + splx(s); + return error; +} + +/*This could be made common with sctp_detach() since they are identical */ +static int +sctp6_detach(struct socket *so) +{ + struct sctp_inpcb *inp; + int s; + + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) + return EINVAL; +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + if (((so->so_options & SO_LINGER) && (so->so_linger == 0)) || + (so->so_rcv.sb_cc > 0)) + sctp_inpcb_free(inp, 1); + else + sctp_inpcb_free(inp, 0); + splx(s); + return 0; +} + +static int +sctp6_disconnect(struct socket *so) +{ + struct sctp_inpcb *inp; + int s; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); /* XXX */ +#endif + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == NULL) { + splx(s); + return (ENOTCONN); + } + if (inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { + if (LIST_EMPTY(&inp->sctp_asoc_list)) { + /* No connection */ + splx(s); + return (ENOTCONN); + } else { + int some_on_streamwheel = 0; + struct sctp_association *asoc; + struct sctp_tcb *stcb; + + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { + splx(s); + return (EINVAL); + } + asoc = &stcb->asoc; + if (!TAILQ_EMPTY(&asoc->out_wheel)) { + /* Check to see if some data queued */ + struct sctp_stream_out *outs; + TAILQ_FOREACH(outs, &asoc->out_wheel, + next_spoke) { + if (!TAILQ_EMPTY(&outs->outqueue)) { + some_on_streamwheel = 1; + break; + } + } + } + + if (TAILQ_EMPTY(&asoc->send_queue) && + TAILQ_EMPTY(&asoc->sent_queue) && + (some_on_streamwheel == 0)) { + /* nothing queued to send, so I'm done... */ + if ((SCTP_GET_STATE(asoc) != + SCTP_STATE_SHUTDOWN_SENT) && + (SCTP_GET_STATE(asoc) != + SCTP_STATE_SHUTDOWN_ACK_SENT)) { + /* only send SHUTDOWN the first time */ +#ifdef SCTP_DEBUG + if (sctp_debug_on & SCTP_DEBUG_OUTPUT4) { + printf("%s:%d sends a shutdown\n", + __FILE__, + __LINE__ + ); + } +#endif + sctp_send_shutdown(stcb, stcb->asoc.primary_destination); + sctp_chunk_output(stcb->sctp_ep, stcb, 1); + asoc->state = SCTP_STATE_SHUTDOWN_SENT; + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, + stcb->sctp_ep, stcb, + asoc->primary_destination); + sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, + stcb->sctp_ep, stcb, + asoc->primary_destination); + } + } else { + /* + * we still got (or just got) data to send, + * so set SHUTDOWN_PENDING + */ + /* + * XXX sockets draft says that MSG_EOF should + * be sent with no data. currently, we will + * allow user data to be sent first and move + * to SHUTDOWN-PENDING + */ + asoc->state |= SCTP_STATE_SHUTDOWN_PENDING; + } + splx(s); + return (0); + } + } else { + /* UDP model does not support this */ + splx(s); + return EOPNOTSUPP; + } +} + +int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct thread *p); +#else +sctp_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct proc *p); +#endif + + +static int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct thread *p) +{ +#else +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp6_send(struct socket *so, int flags, struct mbuf *m, struct sockaddr *addr, + struct mbuf *control, struct proc *p) +{ +#else +sctp6_send(struct socket *so, int flags, struct mbuf *m, struct mbuf *nam, + struct mbuf *control, struct proc *p) +{ + struct sockaddr *addr = nam ? mtod(nam, struct sockaddr *) : NULL; +#endif +#endif + struct sctp_inpcb *inp; + struct inpcb *in_inp; + struct in6pcb *inp6; +#ifdef INET + struct sockaddr_in6 *sin6; +#endif /* INET */ + /* No SPL needed since sctp_output does this */ + + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == NULL) { + if (control) { + m_freem(control); + control = NULL; + } + m_freem(m); + return EINVAL; + } + in_inp = (struct inpcb *)inp; + inp6 = (struct in6pcb *)inp; + /* For the TCP model we may get a NULL addr, if we + * are a connected socket thats ok. + */ + if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) && + (addr == NULL)) { + goto connected_type; + } + if (addr == NULL) { + m_freem(m); + if (control) { + m_freem(control); + control = NULL; + } + return (EDESTADDRREQ); + } + +#ifdef INET + sin6 = (struct sockaddr_in6 *)addr; + if ( + +#if defined(__OpenBSD__) + (0) /* we always do dual bind */ +#elif defined (__NetBSD__) + (inp6->in6p_flags & IN6P_IPV6_V6ONLY) +#else + (inp6->inp_flags & IN6P_IPV6_V6ONLY) +#endif + ) { + /* + * if IPV6_V6ONLY flag, we discard datagrams + * destined to a v4 addr or v4-mapped addr + */ + if (addr->sa_family == AF_INET) { + return EINVAL; + } + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + return EINVAL; + } + } + + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + if (!ip6_v6only) { + struct sockaddr_in sin; + /* convert v4-mapped into v4 addr and send */ + in6_sin6_2_sin(&sin, sin6); + return sctp_send(so, flags, m, (struct sockaddr *)&sin, + control, p); + } else { + /* mapped addresses aren't enabled */ + return EINVAL; + } + } +#endif /* INET */ + connected_type: + /* now what about control */ + if (control) { + if (inp->control) { + printf("huh? control set?\n"); + m_freem(inp->control); + inp->control = NULL; + } + inp->control = control; + } + /* add it in possibly */ + if ((inp->pkt) && + (inp->pkt->m_flags & M_PKTHDR)) { + struct mbuf *x; + int c_len; + + c_len = 0; + /* How big is it */ + for (x=m;x;x = x->m_next) { + c_len += x->m_len; + } + inp->pkt->m_pkthdr.len += c_len; + } + /* Place the data */ + if (inp->pkt) { + inp->pkt_last->m_next = m; + inp->pkt_last = m; + } else { + inp->pkt_last = inp->pkt = m; + } + if ( +#if defined(__FreeBSD__) || defined(__APPLE__) + /* FreeBSD and MacOSX uses a flag passed */ + ((flags & PRUS_MORETOCOME) == 0) +#elif defined(__NetBSD__) + /* NetBSD uses the so_state field */ + ((so->so_state & SS_MORETOCOME) == 0) +#else + 1 /* Open BSD does not have any "more to come" indication */ +#endif + ) { + /* + * note with the current version this code will only be + * used by OpenBSD, NetBSD and FreeBSD have methods for + * re-defining sosend() to use sctp_sosend(). One can + * optionaly switch back to this code (by changing back + * the defininitions but this is not advisable. + */ + int ret; + ret = sctp_output(inp, inp->pkt , addr, inp->control, p, flags); + inp->pkt = NULL; + inp->control = NULL; + return (ret); + } else { + return (0); + } +} + +static int +#if defined(__FreeBSD__) && __FreeBSD_version >= 500000 +sctp6_connect(struct socket *so, struct sockaddr *addr, struct thread *p) +{ +#else +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp6_connect(struct socket *so, struct sockaddr *addr, struct proc *p) +{ +#else +sctp6_connect(struct socket *so, struct mbuf *nam, struct proc *p) +{ + struct sockaddr *addr = mtod(nam, struct sockaddr *); +#endif +#endif +#if defined(__NetBSD__) || defined(__OpenBSD__) + int s = splsoftnet(); +#else + int s = splnet(); +#endif + int error = 0; + struct sctp_inpcb *inp; + struct in6pcb *inp6; + struct sctp_tcb *stcb; +#ifdef INET + struct sockaddr_in6 *sin6; + struct sockaddr_storage ss; +#endif /* INET */ + + inp6 = (struct in6pcb *)so->so_pcb; + inp = (struct sctp_inpcb *)so->so_pcb; + if (inp == 0) { + splx(s); + return (ECONNRESET); /* I made the same as TCP since + * we are not setup? */ + } + SCTP_ASOC_CREATE_LOCK(inp); + SCTP_INP_RLOCK(inp); + if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == + SCTP_PCB_FLAGS_UNBOUND) { + /* Bind a ephemeral port */ + SCTP_INP_RUNLOCK(inp); + error = sctp6_bind(so, NULL, p); + if (error) { + splx(s); + SCTP_ASOC_CREATE_UNLOCK(inp); + + return (error); + } + SCTP_INP_RLOCK(inp); + } + + if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) && + (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED)) { + /* We are already connected AND the TCP model */ + splx(s); + SCTP_INP_RUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + return (EADDRINUSE); + } + +#ifdef INET + sin6 = (struct sockaddr_in6 *)addr; + if ( +#if defined(__OpenBSD__) + (0) /* we always do dual bind */ +#elif defined (__NetBSD__) + (inp6->in6p_flags & IN6P_IPV6_V6ONLY) +#else + (inp6->inp_flags & IN6P_IPV6_V6ONLY) +#endif + ) { + /* + * if IPV6_V6ONLY flag, ignore connections + * destined to a v4 addr or v4-mapped addr + */ + if (addr->sa_family == AF_INET) { + splx(s); + SCTP_INP_RUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + return EINVAL; + } + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + splx(s); + SCTP_INP_RUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + return EINVAL; + } + } + + if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { + if (!ip6_v6only) { + /* convert v4-mapped into v4 addr */ + in6_sin6_2_sin((struct sockaddr_in *)&ss, sin6); + addr = (struct sockaddr *)&ss; + } else { + /* mapped addresses aren't enabled */ + splx(s); + SCTP_INP_RUNLOCK(inp); + SCTP_ASOC_CREATE_UNLOCK(inp); + return EINVAL; + } + } else +#endif /* INET */ + addr = addr; /* for true v6 address case */ + + /* Now do we connect? */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb) + SCTP_TCB_UNLOCK (stcb); + SCTP_INP_RUNLOCK(inp); + }else { + SCTP_INP_RUNLOCK(inp); + SCTP_INP_WLOCK(inp); + SCTP_INP_INCR_REF(inp); + SCTP_INP_WUNLOCK(inp); + stcb = sctp_findassociation_ep_addr(&inp, addr, NULL, NULL, NULL); + if (stcb == NULL) { + SCTP_INP_WLOCK(inp); + SCTP_INP_DECR_REF(inp); + SCTP_INP_WUNLOCK(inp); + } + } + + if (stcb != NULL) { + /* Already have or am bring up an association */ + SCTP_ASOC_CREATE_UNLOCK(inp); + SCTP_TCB_UNLOCK (stcb); + splx(s); + return (EALREADY); + } + /* We are GOOD to go */ + stcb = sctp_aloc_assoc(inp, addr, 1, &error, 0); + SCTP_ASOC_CREATE_UNLOCK(inp); + if (stcb == NULL) { + /* Gak! no memory */ + splx(s); + return (error); + } + if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) { + stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED; + /* Set the connected flag so we can queue data */ + soisconnecting(so); + } + stcb->asoc.state = SCTP_STATE_COOKIE_WAIT; + SCTP_GETTIME_TIMEVAL(&stcb->asoc.time_entered); + sctp_send_initiate(inp, stcb); + SCTP_TCB_UNLOCK (stcb); + splx(s); + return error; +} + +static int +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp6_getaddr(struct socket *so, struct sockaddr **addr) +{ + struct sockaddr_in6 *sin6; +#else +sctp6_getaddr(struct socket *so, struct mbuf *nam) +{ + struct sockaddr_in6 *sin6 = mtod(nam, struct sockaddr_in6 *); +#endif + struct sctp_inpcb *inp; + /* + * Do the malloc first in case it blocks. + */ +#if defined(__FreeBSD__) || defined(__APPLE__) + MALLOC(sin6, struct sockaddr_in6 *, sizeof *sin6, M_SONAME, + M_WAITOK | M_ZERO); +#else + nam->m_len = sizeof(*sin6); +#endif + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + + inp = (struct sctp_inpcb *)so->so_pcb; + if (!inp) { +#if defined(__FreeBSD__) || defined(__APPLE__) + FREE(sin6, M_SONAME); +#endif + return ECONNRESET; + } + + sin6->sin6_port = inp->sctp_lport; + if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { + /* For the bound all case you get back 0 */ + if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) { + struct sctp_tcb *stcb; + struct sockaddr_in6 *sin_a6; + struct sctp_nets *net; + int fnd; + + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { + goto notConn6; + } + fnd = 0; + sin_a6 = NULL; + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + sin_a6 = (struct sockaddr_in6 *)&net->ro._l_addr; + if (sin_a6->sin6_family == AF_INET6) { + fnd = 1; + break; + } + } + if ((!fnd) || (sin_a6 == NULL)) { + /* punt */ + goto notConn6; + } + sin6->sin6_addr = sctp_ipv6_source_address_selection( + inp, stcb, (struct route *)&net->ro, net, 0); + + } else { + /* For the bound all case you get back 0 */ + notConn6: + memset(&sin6->sin6_addr, 0, sizeof(sin6->sin6_addr)); + } + } else { + /* Take the first IPv6 address in the list */ + struct sctp_laddr *laddr; + int fnd = 0; + LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { + if (laddr->ifa->ifa_addr->sa_family == AF_INET6) { + struct sockaddr_in6 *sin_a; + sin_a = (struct sockaddr_in6 *)laddr->ifa->ifa_addr; + sin6->sin6_addr = sin_a->sin6_addr; + fnd = 1; + break; + } + } + if (!fnd) { +#if defined(__FreeBSD__) || defined(__APPLE__) + FREE(sin6, M_SONAME); +#endif + return ENOENT; + } + } + /* Scoping things for v6 */ + if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) + /* skip ifp check below */ + in6_recoverscope(sin6, &sin6->sin6_addr, NULL); + else + sin6->sin6_scope_id = 0; /*XXX*/ +#if defined(__FreeBSD__) || defined(__APPLE__) + (*addr) = (struct sockaddr *)sin6; +#endif + return (0); +} + +static int +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp6_peeraddr(struct socket *so, struct sockaddr **addr) +{ + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)*addr; +#else +sctp6_peeraddr(struct socket *so, struct mbuf *nam) +{ + struct sockaddr_in6 *sin6 = mtod(nam, struct sockaddr_in6 *); +#endif + int fnd; + struct sockaddr_in6 *sin_a6; + struct sctp_inpcb *inp; + struct sctp_tcb *stcb; + struct sctp_nets *net; + /* + * Do the malloc first in case it blocks. + */ + inp = (struct sctp_inpcb *)so->so_pcb; + if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0) { + /* UDP type and listeners will drop out here */ + return (ENOTCONN); + } +#if defined(__FreeBSD__) || defined(__APPLE__) + MALLOC(sin6, struct sockaddr_in6 *, sizeof *sin6, M_SONAME, + M_WAITOK | M_ZERO); +#else + nam->m_len = sizeof(*sin6); +#endif + bzero(sin6, sizeof(*sin6)); + sin6->sin6_family = AF_INET6; + sin6->sin6_len = sizeof(*sin6); + + /* We must recapture incase we blocked */ + inp = (struct sctp_inpcb *)so->so_pcb; + if (!inp) { +#if defined(__FreeBSD__) || defined(__APPLE__) + FREE(sin6, M_SONAME); +#endif + return ECONNRESET; + } + stcb = LIST_FIRST(&inp->sctp_asoc_list); + if (stcb == NULL) { +#if defined(__FreeBSD__) || defined(__APPLE__) + FREE(sin6, M_SONAME); +#endif + return ECONNRESET; + } + fnd = 0; + TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { + sin_a6 = (struct sockaddr_in6 *)&net->ro._l_addr; + if (sin_a6->sin6_family == AF_INET6) { + fnd = 1; + sin6->sin6_port = stcb->rport; + sin6->sin6_addr = sin_a6->sin6_addr; + break; + } + } + if (!fnd) { + /* No IPv4 address */ +#if defined(__FreeBSD__) || defined(__APPLE__) + FREE(sin6, M_SONAME); +#endif + return ENOENT; + } + in6_recoverscope(sin6, &sin6->sin6_addr, NULL); +#if defined(__FreeBSD__) || defined(__APPLE__) + *addr = (struct sockaddr *)sin6; +#endif + return (0); +} + +static int +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp6_in6getaddr(struct socket *so, struct sockaddr **nam) +{ + struct sockaddr *addr; +#else +sctp6_in6getaddr(struct socket *so, struct mbuf *nam) +{ + struct sockaddr *addr = mtod(nam, struct sockaddr *); +#endif + struct in6pcb *inp6 = sotoin6pcb(so); + int error, s; + + if (inp6 == NULL) + return EINVAL; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + /* allow v6 addresses precedence */ + error = sctp6_getaddr(so, nam); + if (error) { + /* try v4 next if v6 failed */ + error = sctp_ingetaddr(so, nam); + if (error) { + splx(s); + return (error); + } +#if defined(__FreeBSD__) || defined(__APPLE__) + addr = *nam; +#endif + /* if I'm V6ONLY, convert it to v4-mapped */ + if ( +#if defined(__OpenBSD__) + (0) /* we always do dual bind */ +#elif defined (__NetBSD__) + (inp6->in6p_flags & IN6P_IPV6_V6ONLY) +#else + (inp6->inp_flags & IN6P_IPV6_V6ONLY) +#endif + ) { + struct sockaddr_in6 sin6; + in6_sin_2_v4mapsin6((struct sockaddr_in *)addr, &sin6); + memcpy(addr, &sin6, sizeof(struct sockaddr_in6)); +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + nam->m_len = sizeof(sin6); +#endif +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + } else { + nam->m_len = sizeof(struct sockaddr_in); +#endif + } +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + } else { + nam->m_len = sizeof(struct sockaddr_in6); +#endif + } + splx(s); + return (error); +} + + +static int +#if defined(__FreeBSD__) || defined(__APPLE__) +sctp6_getpeeraddr(struct socket *so, struct sockaddr **nam) +{ + struct sockaddr *addr = *nam; +#else +sctp6_getpeeraddr(struct socket *so, struct mbuf *nam) +{ + struct sockaddr *addr = mtod(nam, struct sockaddr *); +#endif + struct in6pcb *inp6 = sotoin6pcb(so); + int error, s; + + if (inp6 == NULL) + return EINVAL; + +#if defined(__NetBSD__) || defined(__OpenBSD__) + s = splsoftnet(); +#else + s = splnet(); +#endif + /* allow v6 addresses precedence */ + error = sctp6_peeraddr(so, nam); + if (error) { + /* try v4 next if v6 failed */ + error = sctp_peeraddr(so, nam); + if (error) { + splx(s); + return (error); + } + /* if I'm V6ONLY, convert it to v4-mapped */ + if ( +#if defined(__OpenBSD__) + (0) /* we always do dual bind */ +#elif defined (__NetBSD__) + (inp6->in6p_flags & IN6P_IPV6_V6ONLY) +#else + (inp6->inp_flags & IN6P_IPV6_V6ONLY) +#endif + ) { + struct sockaddr_in6 sin6; + in6_sin_2_v4mapsin6((struct sockaddr_in *)addr, &sin6); + memcpy(addr, &sin6, sizeof(struct sockaddr_in6)); +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + nam->m_len = sizeof(sin6); +#endif +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + } else { + nam->m_len = sizeof(struct sockaddr_in); +#endif + } +#if !(defined(__FreeBSD__) || defined(__APPLE__)) + } else { + nam->m_len = sizeof(struct sockaddr_in6); +#endif + } + splx(s); + return error; +} + +#if defined(__FreeBSD__) || defined(__APPLE__) +struct pr_usrreqs sctp6_usrreqs = { + sctp6_abort, sctp_accept, sctp6_attach, sctp6_bind, + sctp6_connect, pru_connect2_notsupp, in6_control, + sctp6_detach, sctp6_disconnect, sctp_listen, sctp6_getpeeraddr, + sctp_usr_recvd, pru_rcvoob_notsupp, sctp6_send, pru_sense_null, + sctp_shutdown, sctp6_in6getaddr, sctp_sosend, soreceive, sopoll +}; + +#else + +int +sctp6_usrreq(so, req, m, nam, control, p) + struct socket *so; + int req; + struct mbuf *m, *nam, *control; + struct proc *p; +{ + int s; + int error = 0; + int family; + +#if defined(__OpenBSD__) + p = curproc; +#endif + s = splsoftnet(); + family = so->so_proto->pr_domain->dom_family; + + if (req == PRU_CONTROL) { + switch (family) { + case PF_INET: + error = in_control(so, (long)m, (caddr_t)nam, + (struct ifnet *)control +#if defined(__NetBSD__) + , p +#endif + ); +#ifdef INET6 + case PF_INET6: + error = in6_control(so, (long)m, (caddr_t)nam, + (struct ifnet *)control, p); +#endif + default: + error = EAFNOSUPPORT; + } + splx(s); + return (error); + } +#ifdef __NetBSD__ + if (req == PRU_PURGEIF) { + struct ifnet *ifn; + struct ifaddr *ifa; + ifn = (struct ifnet *)control; + TAILQ_FOREACH(ifa, &ifn->if_addrlist, ifa_list) { + if (ifa->ifa_addr->sa_family == family) { + sctp_delete_ip_address(ifa); + } + } + switch (family) { + case PF_INET: + in_purgeif (ifn); + break; +#ifdef INET6 + case PF_INET6: + in6_purgeif (ifn); + break; +#endif + default: + splx(s); + return (EAFNOSUPPORT); + } + splx(s); + return (0); + } +#endif + switch (req) { + case PRU_ATTACH: + error = sctp6_attach(so, family, p); + break; + case PRU_DETACH: + error = sctp6_detach(so); + break; + case PRU_BIND: + if (nam == NULL) + return (EINVAL); + error = sctp6_bind(so, nam, p); + break; + case PRU_LISTEN: + error = sctp_listen(so, p); + break; + case PRU_CONNECT: + if (nam == NULL) + return (EINVAL); + error = sctp6_connect(so, nam, p); + break; + case PRU_DISCONNECT: + error = sctp6_disconnect(so); + break; + case PRU_ACCEPT: + if (nam == NULL) + return (EINVAL); + error = sctp_accept(so, nam); + break; + case PRU_SHUTDOWN: + error = sctp_shutdown(so); + break; + + case PRU_RCVD: + /* + * For OpenBSD and NetBSD, this is real ugly. The (mbuf *) + * nam that is passed (by soreceive()) is the int flags + * cast as a (mbuf *) yuck! + */ + error = sctp_usr_recvd(so, (int)((long)nam)); + break; + + case PRU_SEND: + /* Flags are ignored */ + error = sctp6_send(so, 0, m, nam, control, p); + break; + case PRU_ABORT: + error = sctp6_abort(so); + break; + + case PRU_SENSE: + error = 0; + break; + case PRU_RCVOOB: + error = EAFNOSUPPORT; + break; + case PRU_SENDOOB: + error = EAFNOSUPPORT; + break; + case PRU_PEERADDR: + error = sctp6_getpeeraddr(so, nam); + break; + case PRU_SOCKADDR: + error = sctp6_in6getaddr(so, nam); + break; + case PRU_SLOWTIMO: + error = 0; + break; + default: + break; + } + splx(s); + return (error); +} +#endif diff --git a/sys/netinet6/sctp6_var.h b/sys/netinet6/sctp6_var.h new file mode 100644 index 0000000000..5cfa6244b9 --- /dev/null +++ b/sys/netinet6/sctp6_var.h @@ -0,0 +1,65 @@ +/* $KAME: sctp6_var.h,v 1.6 2003/11/25 06:40:55 ono Exp $ */ +/* $DragonFly: src/sys/netinet6/sctp6_var.h,v 1.1 2005/07/15 14:46:17 eirikn Exp $ */ + +/* + * Copyright (c) 2001, 2002, 2004 Cisco Systems, Inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by Cisco Systems, Inc. + * 4. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY CISCO SYSTEMS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL CISCO SYSTEMS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#ifndef _NETINET6_SCTP6_VAR_H_ +#define _NETINET6_SCTP6_VAR_H_ + +#if defined(_KERNEL) || (defined(__APPLE__) && defined(KERNEL)) + +#if defined(__FreeBSD__) || (__APPLE__) +SYSCTL_DECL(_net_inet6_sctp6); +extern struct pr_usrreqs sctp6_usrreqs; +int sctp6_ctloutput __P((struct socket *, struct sockopt *)); +#else +int sctp6_ctloutput __P((int, struct socket *, int, int, struct mbuf **)); +#if defined(__NetBSD__) || defined(__OpenBSD__) +int sctp6_usrreq __P((struct socket *, int, struct mbuf *, struct mbuf *, + struct mbuf *, struct proc *)); +#else +int sctp6_usrreq __P((struct socket *, int, struct mbuf *, struct mbuf *, + struct mbuf *)); +#endif /* __NetBSD__ || __OpenBSD__ */ +#endif /* __FreeBSD__ */ + +#if defined(__APPLE__) +int sctp6_input __P((struct mbuf **, int *)); +#else +int sctp6_input __P((struct mbuf **, int *, int)); +#endif +int sctp6_output __P((struct sctp_inpcb *, struct mbuf *, struct sockaddr *, + struct mbuf *, struct proc *)); +void sctp6_ctlinput __P((int, struct sockaddr *, void *)); + +#endif /* _KERNEL */ +#endif -- 2.41.0