6a91f103b6
a colliding INIT. This if fine except when we have data outstanding... we basically reset it to the previous value it was.. so then we end up assigning the same TSN to two different data chunks. This patch: 1) Finds a missing lock for when we change the stream numbers during COOKIE and INIT-ACK processing.. we were NOT locking the send_buffer.. which COULD cause problems (found by inspection looking for <2>) 2) Fixes a case during a colliding INIT where we incorrectly reset the sending Sequence thus in some cases duplicately assigning a TSN. 3) Additional enhancments to logging so we can see strm/tsn in the receiver AND new tracking to watch what the sender is doing with TSN and STRM seq's. Approved by: gnn
5364 lines
142 KiB
C
5364 lines
142 KiB
C
/*-
|
|
* Copyright (c) 2001-2006, 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:
|
|
*
|
|
* a) Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
*
|
|
* b) 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.
|
|
*
|
|
* c) Neither the name of Cisco Systems, Inc. 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
|
|
*/
|
|
|
|
/* $KAME: sctputil.c,v 1.37 2005/03/07 23:26:09 itojun Exp $ */
|
|
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
|
|
#include "opt_ipsec.h"
|
|
#include "opt_compat.h"
|
|
#include "opt_inet6.h"
|
|
#include "opt_inet.h"
|
|
#include "opt_sctp.h"
|
|
|
|
#include <sys/param.h>
|
|
#include <sys/systm.h>
|
|
#include <sys/fcntl.h>
|
|
#include <sys/lock.h>
|
|
#include <sys/malloc.h>
|
|
#include <sys/mbuf.h>
|
|
#include <sys/domain.h>
|
|
#include <sys/file.h> /* for struct knote */
|
|
#include <sys/kernel.h>
|
|
#include <sys/event.h>
|
|
#include <sys/poll.h>
|
|
|
|
#include <sys/protosw.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/socketvar.h>
|
|
#include <sys/proc.h>
|
|
#include <sys/kernel.h>
|
|
#include <sys/resourcevar.h>
|
|
#include <sys/signalvar.h>
|
|
#include <sys/sysctl.h>
|
|
#include <sys/uio.h>
|
|
#include <sys/jail.h>
|
|
|
|
#include <sys/callout.h>
|
|
|
|
#include <net/radix.h>
|
|
#include <net/route.h>
|
|
|
|
#ifdef INET6
|
|
#include <sys/domain.h>
|
|
#endif
|
|
|
|
#include <sys/limits.h>
|
|
#include <sys/mac.h>
|
|
#include <sys/mutex.h>
|
|
|
|
#include <net/if.h>
|
|
#include <net/if_types.h>
|
|
#include <net/route.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet/in_systm.h>
|
|
#include <netinet/ip.h>
|
|
#include <netinet/in_pcb.h>
|
|
#include <netinet/in_var.h>
|
|
#include <netinet/ip_var.h>
|
|
|
|
#ifdef INET6
|
|
#include <netinet/ip6.h>
|
|
#include <netinet6/ip6_var.h>
|
|
|
|
#include <netinet6/in6_pcb.h>
|
|
|
|
#include <netinet6/scope6_var.h>
|
|
#endif /* INET6 */
|
|
|
|
#ifdef IPSEC
|
|
#include <netinet6/ipsec.h>
|
|
#include <netkey/key.h>
|
|
#endif /* IPSEC */
|
|
|
|
#include <netinet/sctp_os.h>
|
|
#include <netinet/sctp_pcb.h>
|
|
#include <netinet/sctputil.h>
|
|
#include <netinet/sctp_var.h>
|
|
#ifdef INET6
|
|
#include <netinet6/sctp6_var.h>
|
|
#endif
|
|
#include <netinet/sctp_header.h>
|
|
#include <netinet/sctp_output.h>
|
|
#include <netinet/sctp_uio.h>
|
|
#include <netinet/sctp_timer.h>
|
|
#include <netinet/sctp_crc32.h>
|
|
#include <netinet/sctp_indata.h>/* for sctp_deliver_data() */
|
|
#include <netinet/sctp_auth.h>
|
|
#include <netinet/sctp_asconf.h>
|
|
|
|
extern int sctp_warm_the_crc32_table;
|
|
|
|
#define NUMBER_OF_MTU_SIZES 18
|
|
|
|
#ifdef SCTP_DEBUG
|
|
extern uint32_t sctp_debug_on;
|
|
|
|
#endif
|
|
|
|
|
|
#ifdef SCTP_STAT_LOGGING
|
|
int global_sctp_cwnd_log_at = 0;
|
|
int global_sctp_cwnd_log_rolled = 0;
|
|
struct sctp_cwnd_log sctp_clog[SCTP_STAT_LOG_SIZE];
|
|
|
|
static uint32_t
|
|
sctp_get_time_of_event(void)
|
|
{
|
|
struct timeval now;
|
|
uint32_t timeval;
|
|
|
|
SCTP_GETPTIME_TIMEVAL(&now);
|
|
timeval = (now.tv_sec % 0x00000fff);
|
|
timeval <<= 20;
|
|
timeval |= now.tv_usec & 0xfffff;
|
|
return (timeval);
|
|
}
|
|
|
|
|
|
void
|
|
sctp_clr_stat_log(void)
|
|
{
|
|
global_sctp_cwnd_log_at = 0;
|
|
global_sctp_cwnd_log_rolled = 0;
|
|
}
|
|
|
|
|
|
void
|
|
sctp_sblog(struct sockbuf *sb,
|
|
struct sctp_tcb *stcb, int from, int incr)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_SB;
|
|
sctp_clog[sctp_cwnd_log_at].x.sb.stcb = stcb;
|
|
sctp_clog[sctp_cwnd_log_at].x.sb.so_sbcc = sb->sb_cc;
|
|
if (stcb)
|
|
sctp_clog[sctp_cwnd_log_at].x.sb.stcb_sbcc = stcb->asoc.sb_cc;
|
|
else
|
|
sctp_clog[sctp_cwnd_log_at].x.sb.stcb_sbcc = 0;
|
|
sctp_clog[sctp_cwnd_log_at].x.sb.incr = incr;
|
|
}
|
|
|
|
void
|
|
sctp_log_closing(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int16_t loc)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = 0;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_CLOSE;
|
|
sctp_clog[sctp_cwnd_log_at].x.close.inp = (void *)inp;
|
|
sctp_clog[sctp_cwnd_log_at].x.close.sctp_flags = inp->sctp_flags;
|
|
if (stcb) {
|
|
sctp_clog[sctp_cwnd_log_at].x.close.stcb = (void *)stcb;
|
|
sctp_clog[sctp_cwnd_log_at].x.close.state = (uint16_t) stcb->asoc.state;
|
|
} else {
|
|
sctp_clog[sctp_cwnd_log_at].x.close.stcb = 0;
|
|
sctp_clog[sctp_cwnd_log_at].x.close.state = 0;
|
|
}
|
|
sctp_clog[sctp_cwnd_log_at].x.close.loc = loc;
|
|
}
|
|
|
|
|
|
void
|
|
rto_logging(struct sctp_nets *net, int from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_RTT;
|
|
sctp_clog[sctp_cwnd_log_at].x.rto.net = (void *)net;
|
|
sctp_clog[sctp_cwnd_log_at].x.rto.rtt = net->prev_rtt;
|
|
sctp_clog[sctp_cwnd_log_at].x.rto.rttvar = net->rtt_variance;
|
|
sctp_clog[sctp_cwnd_log_at].x.rto.direction = net->rto_variance_dir;
|
|
}
|
|
|
|
void
|
|
sctp_log_strm_del_alt(struct sctp_tcb *stcb, uint32_t tsn, uint16_t sseq, uint16_t stream, int from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_STRM;
|
|
sctp_clog[sctp_cwnd_log_at].x.strlog.stcb = stcb;
|
|
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_clog[sctp_cwnd_log_at].x.strlog.strm = stream;
|
|
}
|
|
|
|
void
|
|
sctp_log_nagle_event(struct sctp_tcb *stcb, int action)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) action;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_NAGLE;
|
|
sctp_clog[sctp_cwnd_log_at].x.nagle.stcb = (void *)stcb;
|
|
sctp_clog[sctp_cwnd_log_at].x.nagle.total_flight = stcb->asoc.total_flight;
|
|
sctp_clog[sctp_cwnd_log_at].x.nagle.total_in_queue = stcb->asoc.total_output_queue_size;
|
|
sctp_clog[sctp_cwnd_log_at].x.nagle.count_in_queue = stcb->asoc.chunks_on_out_queue;
|
|
sctp_clog[sctp_cwnd_log_at].x.nagle.count_in_flight = stcb->asoc.total_flight_count;
|
|
}
|
|
|
|
|
|
void
|
|
sctp_log_sack(uint32_t old_cumack, uint32_t cumack, uint32_t tsn, uint16_t gaps, uint16_t dups, int from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_SACK;
|
|
sctp_clog[sctp_cwnd_log_at].x.sack.cumack = cumack;
|
|
sctp_clog[sctp_cwnd_log_at].x.sack.oldcumack = old_cumack;
|
|
sctp_clog[sctp_cwnd_log_at].x.sack.tsn = tsn;
|
|
sctp_clog[sctp_cwnd_log_at].x.sack.numGaps = gaps;
|
|
sctp_clog[sctp_cwnd_log_at].x.sack.numDups = dups;
|
|
}
|
|
|
|
void
|
|
sctp_log_map(uint32_t map, uint32_t cum, uint32_t high, int from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_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;
|
|
}
|
|
|
|
void
|
|
sctp_log_fr(uint32_t biggest_tsn, uint32_t biggest_new_tsn, uint32_t tsn,
|
|
int from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_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;
|
|
}
|
|
|
|
|
|
void
|
|
sctp_log_mb(struct mbuf *m, int from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_MBUF;
|
|
sctp_clog[sctp_cwnd_log_at].x.mb.mp = m;
|
|
sctp_clog[sctp_cwnd_log_at].x.mb.mbuf_flags = (uint8_t) (m->m_flags);
|
|
sctp_clog[sctp_cwnd_log_at].x.mb.size = (uint16_t) (m->m_len);
|
|
sctp_clog[sctp_cwnd_log_at].x.mb.data = m->m_data;
|
|
if (m->m_flags & M_EXT) {
|
|
sctp_clog[sctp_cwnd_log_at].x.mb.ext = m->m_ext.ext_buf;
|
|
sctp_clog[sctp_cwnd_log_at].x.mb.refcnt = (uint8_t) (*m->m_ext.ref_cnt);
|
|
} else {
|
|
sctp_clog[sctp_cwnd_log_at].x.mb.ext = 0;
|
|
sctp_clog[sctp_cwnd_log_at].x.mb.refcnt = 0;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
sctp_log_strm_del(struct sctp_queued_to_read *control, struct sctp_queued_to_read *poschk,
|
|
int from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
if (control == NULL) {
|
|
printf("Gak log of NULL?\n");
|
|
return;
|
|
}
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_STRM;
|
|
sctp_clog[sctp_cwnd_log_at].x.strlog.stcb = control->stcb;
|
|
sctp_clog[sctp_cwnd_log_at].x.strlog.n_tsn = control->sinfo_tsn;
|
|
sctp_clog[sctp_cwnd_log_at].x.strlog.n_sseq = control->sinfo_ssn;
|
|
sctp_clog[sctp_cwnd_log_at].x.strlog.strm = control->sinfo_stream;
|
|
if (poschk != NULL) {
|
|
sctp_clog[sctp_cwnd_log_at].x.strlog.e_tsn = poschk->sinfo_tsn;
|
|
sctp_clog[sctp_cwnd_log_at].x.strlog.e_sseq = poschk->sinfo_ssn;
|
|
} else {
|
|
sctp_clog[sctp_cwnd_log_at].x.strlog.e_tsn = 0;
|
|
sctp_clog[sctp_cwnd_log_at].x.strlog.e_sseq = 0;
|
|
}
|
|
}
|
|
|
|
void
|
|
sctp_log_cwnd(struct sctp_tcb *stcb, struct sctp_nets *net, int augment, uint8_t from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_CWND;
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.net = net;
|
|
if (stcb->asoc.send_queue_cnt > 255)
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_send = 255;
|
|
else
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_send = stcb->asoc.send_queue_cnt;
|
|
if (stcb->asoc.stream_queue_cnt > 255)
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_str = 255;
|
|
else
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_str = stcb->asoc.stream_queue_cnt;
|
|
|
|
if (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.pseudo_cumack = net->pseudo_cumack;
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.meets_pseudo_cumack = net->new_pseudo_cumack;
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.need_new_pseudo_cumack = net->find_pseudo_cumack;
|
|
}
|
|
if (SCTP_CWNDLOG_PRESEND == from) {
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.meets_pseudo_cumack = stcb->asoc.peers_rwnd;
|
|
}
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.cwnd_augment = augment;
|
|
}
|
|
|
|
void
|
|
sctp_log_lock(struct sctp_inpcb *inp, struct sctp_tcb *stcb, uint8_t from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_LOCK_EVENT;
|
|
if (inp) {
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.sock = (void *)inp->sctp_socket;
|
|
|
|
} else {
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.sock = (void *)NULL;
|
|
}
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.inp = (void *)inp;
|
|
if (stcb) {
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.tcb_lock = mtx_owned(&stcb->tcb_mtx);
|
|
} else {
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.tcb_lock = SCTP_LOCK_UNKNOWN;
|
|
}
|
|
if (inp) {
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.inp_lock = mtx_owned(&inp->inp_mtx);
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.create_lock = mtx_owned(&inp->inp_create_mtx);
|
|
} else {
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.inp_lock = SCTP_LOCK_UNKNOWN;
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.create_lock = SCTP_LOCK_UNKNOWN;
|
|
}
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.info_lock = mtx_owned(&sctppcbinfo.ipi_ep_mtx);
|
|
if (inp->sctp_socket) {
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.sock_lock = mtx_owned(&(inp->sctp_socket->so_rcv.sb_mtx));
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.sockrcvbuf_lock = mtx_owned(&(inp->sctp_socket->so_rcv.sb_mtx));
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.socksndbuf_lock = mtx_owned(&(inp->sctp_socket->so_snd.sb_mtx));
|
|
} else {
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.sock_lock = SCTP_LOCK_UNKNOWN;
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.sockrcvbuf_lock = SCTP_LOCK_UNKNOWN;
|
|
sctp_clog[sctp_cwnd_log_at].x.lock.socksndbuf_lock = SCTP_LOCK_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
void
|
|
sctp_log_maxburst(struct sctp_tcb *stcb, struct sctp_nets *net, int error, int burst, uint8_t from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_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;
|
|
if (stcb->asoc.send_queue_cnt > 255)
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_send = 255;
|
|
else
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_send = stcb->asoc.send_queue_cnt;
|
|
if (stcb->asoc.stream_queue_cnt > 255)
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_str = 255;
|
|
else
|
|
sctp_clog[sctp_cwnd_log_at].x.cwnd.cnt_in_str = stcb->asoc.stream_queue_cnt;
|
|
}
|
|
|
|
void
|
|
sctp_log_rwnd(uint8_t from, uint32_t peers_rwnd, uint32_t snd_size, uint32_t overhead)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_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;
|
|
}
|
|
|
|
void
|
|
sctp_log_rwnd_set(uint8_t from, uint32_t peers_rwnd, uint32_t flight_size, uint32_t overhead, uint32_t a_rwndval)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_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;
|
|
}
|
|
|
|
void
|
|
sctp_log_mbcnt(uint8_t from, uint32_t total_oq, uint32_t book, uint32_t total_mbcnt_q, uint32_t mbcnt)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_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;
|
|
}
|
|
|
|
void
|
|
sctp_misc_ints(uint8_t from, uint32_t a, uint32_t b, uint32_t c, uint32_t d)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_MISC_EVENT;
|
|
sctp_clog[sctp_cwnd_log_at].x.misc.log1 = a;
|
|
sctp_clog[sctp_cwnd_log_at].x.misc.log2 = b;
|
|
sctp_clog[sctp_cwnd_log_at].x.misc.log3 = c;
|
|
sctp_clog[sctp_cwnd_log_at].x.misc.log4 = d;
|
|
}
|
|
|
|
void
|
|
sctp_wakeup_log(struct sctp_tcb *stcb, uint32_t cumtsn, uint32_t wake_cnt, int from)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_WAKE;
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.stcb = (void *)stcb;
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.wake_cnt = wake_cnt;
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.flight = stcb->asoc.total_flight_count;
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.send_q = stcb->asoc.send_queue_cnt;
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.sent_q = stcb->asoc.sent_queue_cnt;
|
|
|
|
if (stcb->asoc.stream_queue_cnt < 0xff)
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.stream_qcnt = (uint8_t) stcb->asoc.stream_queue_cnt;
|
|
else
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.stream_qcnt = 0xff;
|
|
|
|
if (stcb->asoc.chunks_on_out_queue < 0xff)
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.chunks_on_oque = (uint8_t) stcb->asoc.chunks_on_out_queue;
|
|
else
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.chunks_on_oque = 0xff;
|
|
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.sctpflags = 0;
|
|
/* set in the defered mode stuff */
|
|
if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_DONT_WAKE)
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.sctpflags |= 1;
|
|
if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_WAKEOUTPUT)
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.sctpflags |= 2;
|
|
if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_WAKEINPUT)
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.sctpflags |= 4;
|
|
/* what about the sb */
|
|
if (stcb->sctp_socket) {
|
|
struct socket *so = stcb->sctp_socket;
|
|
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.sbflags = (uint8_t) ((so->so_snd.sb_flags & 0x00ff));
|
|
} else {
|
|
sctp_clog[sctp_cwnd_log_at].x.wake.sbflags = 0xff;
|
|
}
|
|
}
|
|
|
|
void
|
|
sctp_log_block(uint8_t from, struct socket *so, struct sctp_association *asoc, int sendlen)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
|
|
SCTP_STATLOG_GETREF(sctp_cwnd_log_at);
|
|
sctp_clog[sctp_cwnd_log_at].from = (uint8_t) from;
|
|
sctp_clog[sctp_cwnd_log_at].time_event = sctp_get_time_of_event();
|
|
sctp_clog[sctp_cwnd_log_at].event_type = (uint8_t) SCTP_LOG_EVENT_BLOCK;
|
|
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 = (uint16_t) (asoc->send_queue_cnt + asoc->sent_queue_cnt);
|
|
sctp_clog[sctp_cwnd_log_at].x.blk.peer_rwnd = asoc->peers_rwnd;
|
|
sctp_clog[sctp_cwnd_log_at].x.blk.stream_qcnt = (uint16_t) asoc->stream_queue_cnt;
|
|
sctp_clog[sctp_cwnd_log_at].x.blk.chunks_on_oque = (uint16_t) asoc->chunks_on_out_queue;
|
|
sctp_clog[sctp_cwnd_log_at].x.blk.flight_size = (uint16_t) (asoc->total_flight / 1024);
|
|
sctp_clog[sctp_cwnd_log_at].x.blk.sndlen = sendlen;
|
|
}
|
|
|
|
int
|
|
sctp_fill_stat_log(struct mbuf *m)
|
|
{
|
|
int sctp_cwnd_log_at;
|
|
struct sctp_cwnd_log_req *req;
|
|
size_t size_limit;
|
|
int 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);
|
|
}
|
|
sctp_cwnd_log_at = global_sctp_cwnd_log_at;
|
|
req = mtod(m, struct sctp_cwnd_log_req *);
|
|
num = size_limit / sizeof(struct sctp_cwnd_log);
|
|
if (global_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) &&
|
|
((global_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) + 1;
|
|
}
|
|
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;
|
|
}
|
|
#ifdef INVARIENTS
|
|
if (req->num_ret > num) {
|
|
panic("Bad statlog get?");
|
|
}
|
|
#endif
|
|
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)) + sizeof(struct sctp_cwnd_log_req);
|
|
return (0);
|
|
}
|
|
|
|
#endif
|
|
|
|
#ifdef SCTP_AUDITING_ENABLED
|
|
uint8_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; i++) {
|
|
if ((sctp_audit_data[i][0] == 0xe0) &&
|
|
(sctp_audit_data[i][1] == 0x01)) {
|
|
cnt = 0;
|
|
printf("\n");
|
|
} else if (sctp_audit_data[i][0] == 0xf0) {
|
|
cnt = 0;
|
|
printf("\n");
|
|
} else if ((sctp_audit_data[i][0] == 0xc0) &&
|
|
(sctp_audit_data[i][1] == 0x01)) {
|
|
printf("\n");
|
|
cnt = 0;
|
|
}
|
|
printf("%2.2x%2.2x ", (uint32_t) sctp_audit_data[i][0],
|
|
(uint32_t) sctp_audit_data[i][1]);
|
|
cnt++;
|
|
if ((cnt % 14) == 0)
|
|
printf("\n");
|
|
}
|
|
for (i = 0; i < sctp_audit_indx; i++) {
|
|
if ((sctp_audit_data[i][0] == 0xe0) &&
|
|
(sctp_audit_data[i][1] == 0x01)) {
|
|
cnt = 0;
|
|
printf("\n");
|
|
} else if (sctp_audit_data[i][0] == 0xf0) {
|
|
cnt = 0;
|
|
printf("\n");
|
|
} else if ((sctp_audit_data[i][0] == 0xc0) &&
|
|
(sctp_audit_data[i][1] == 0x01)) {
|
|
printf("\n");
|
|
cnt = 0;
|
|
}
|
|
printf("%2.2x%2.2x ", (uint32_t) sctp_audit_data[i][0],
|
|
(uint32_t) sctp_audit_data[i][1]);
|
|
cnt++;
|
|
if ((cnt % 14) == 0)
|
|
printf("\n");
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
void
|
|
sctp_auditing(int from, struct sctp_inpcb *inp, struct sctp_tcb *stcb,
|
|
struct sctp_nets *net)
|
|
{
|
|
int resend_cnt, tot_out, rep, tot_book_cnt;
|
|
struct sctp_nets *lnet;
|
|
struct sctp_tmit_chunk *chk;
|
|
|
|
sctp_audit_data[sctp_audit_indx][0] = 0xAA;
|
|
sctp_audit_data[sctp_audit_indx][1] = 0x000000ff & from;
|
|
sctp_audit_indx++;
|
|
if (sctp_audit_indx >= 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(uint8_t ev, uint8_t fd)
|
|
{
|
|
int s;
|
|
|
|
s = splnet();
|
|
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
|
|
};
|
|
|
|
void
|
|
sctp_stop_timers_for_shutdown(struct sctp_tcb *stcb)
|
|
{
|
|
struct sctp_association *asoc;
|
|
struct sctp_nets *net;
|
|
|
|
asoc = &stcb->asoc;
|
|
|
|
callout_stop(&asoc->hb_timer.timer);
|
|
callout_stop(&asoc->dack_timer.timer);
|
|
callout_stop(&asoc->strreset_timer.timer);
|
|
callout_stop(&asoc->asconf_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->fr_timer.timer);
|
|
callout_stop(&net->pmtu_timer.timer);
|
|
}
|
|
}
|
|
|
|
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 locking 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_hmac(SCTP_HMAC, (uint8_t *) m->random_numbers,
|
|
sizeof(m->random_numbers), (uint8_t *) & m->random_counter,
|
|
sizeof(m->random_counter), (uint8_t *) 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) {
|
|
uint32_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);
|
|
}
|
|
|
|
uint32_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 = TICKS_TO_MSEC(m->sctp_ep.sctp_timeoutticks[SCTP_TIMER_HEARTBEAT]);
|
|
asoc->cookie_life = m->sctp_ep.def_cookie_life;
|
|
asoc->sctp_cmt_on_off = (uint8_t) sctp_cmt_on_off;
|
|
#ifdef AF_INET
|
|
asoc->default_tos = m->ip_inp.inp.inp_ip_tos;
|
|
#else
|
|
asoc->default_tos = 0;
|
|
#endif
|
|
|
|
#ifdef AF_INET6
|
|
asoc->default_flowlabel = ((struct in6pcb *)m)->in6p_flowinfo;
|
|
#else
|
|
asoc->default_flowlabel = 0;
|
|
#endif
|
|
if (override_tag) {
|
|
struct timeval now;
|
|
|
|
if (sctp_is_vtag_good(m, override_tag, &now)) {
|
|
asoc->my_vtag = override_tag;
|
|
} else {
|
|
return (ENOMEM);
|
|
}
|
|
|
|
} else {
|
|
asoc->my_vtag = sctp_select_a_tag(m);
|
|
}
|
|
/* Get the nonce tags */
|
|
asoc->my_vtag_nonce = sctp_select_a_tag(m);
|
|
asoc->peer_vtag_nonce = sctp_select_a_tag(m);
|
|
|
|
if (sctp_is_feature_on(m, SCTP_PCB_FLAGS_DONOT_HEARTBEAT))
|
|
asoc->hb_is_disabled = 1;
|
|
else
|
|
asoc->hb_is_disabled = 0;
|
|
|
|
asoc->refcnt = 0;
|
|
asoc->assoc_up_sent = 0;
|
|
asoc->assoc_id = asoc->my_vtag;
|
|
asoc->asconf_seq_out = asoc->str_reset_seq_out = asoc->init_seq_number = asoc->sending_seq =
|
|
sctp_select_initial_TSN(&m->sctp_ep);
|
|
/* we are optimisitic here */
|
|
asoc->peer_supports_pktdrop = 1;
|
|
|
|
asoc->sent_queue_retran_cnt = 0;
|
|
|
|
/* for CMT */
|
|
asoc->last_net_data_came_from = NULL;
|
|
|
|
/* 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;
|
|
asoc->free_chunk_cnt = 0;
|
|
|
|
asoc->iam_blocking = 0;
|
|
/* ECN Nonce initialization */
|
|
asoc->context = m->sctp_context;
|
|
asoc->def_send = m->def_send;
|
|
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;
|
|
asoc->delayed_ack = TICKS_TO_MSEC(m->sctp_ep.sctp_timeoutticks[SCTP_TIMER_RECV]);
|
|
asoc->pr_sctp_cnt = 0;
|
|
asoc->total_output_queue_size = 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 (
|
|
(inp6->inp_flags & IN6P_IPV6_V6ONLY)
|
|
== 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;
|
|
|
|
asoc->locked_on_sending = NULL;
|
|
asoc->stream_locked_on = 0;
|
|
asoc->ecn_echo_cnt_onq = 0;
|
|
asoc->stream_locked = 0;
|
|
|
|
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;
|
|
SCTP_MALLOC(asoc->strmout, struct sctp_stream_out *,
|
|
asoc->streamoutcnt * sizeof(struct sctp_stream_out),
|
|
"StreamsOut");
|
|
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].last_msg_incomplete = 0;
|
|
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;
|
|
SCTP_MALLOC(asoc->mapping_array, uint8_t *, asoc->mapping_array_size,
|
|
"MappingArray");
|
|
if (asoc->mapping_array == NULL) {
|
|
SCTP_FREE(asoc->strmout);
|
|
return (ENOMEM);
|
|
}
|
|
memset(asoc->mapping_array, 0, asoc->mapping_array_size);
|
|
/* Now the init of the other outqueues */
|
|
TAILQ_INIT(&asoc->free_chunks);
|
|
TAILQ_INIT(&asoc->free_strmoq);
|
|
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->resetHead);
|
|
asoc->max_inbound_streams = m->sctp_ep.max_open_streams_intome;
|
|
TAILQ_INIT(&asoc->asconf_queue);
|
|
/* authentication fields */
|
|
asoc->authinfo.random = NULL;
|
|
asoc->authinfo.assoc_key = NULL;
|
|
asoc->authinfo.assoc_keyid = 0;
|
|
asoc->authinfo.recv_key = NULL;
|
|
asoc->authinfo.recv_keyid = 0;
|
|
LIST_INIT(&asoc->shared_keys);
|
|
|
|
return (0);
|
|
}
|
|
|
|
int
|
|
sctp_expand_mapping_array(struct sctp_association *asoc)
|
|
{
|
|
/* mapping array needs to grow */
|
|
uint8_t *new_array;
|
|
uint16_t new_size;
|
|
|
|
new_size = asoc->mapping_array_size + SCTP_MAPPING_ARRAY_INCR;
|
|
SCTP_MALLOC(new_array, uint8_t *, new_size, "MappingArray");
|
|
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);
|
|
SCTP_FREE(asoc->mapping_array);
|
|
asoc->mapping_array = new_array;
|
|
asoc->mapping_array_size = new_size;
|
|
return (0);
|
|
}
|
|
|
|
extern unsigned int sctp_early_fr_msec;
|
|
|
|
static void
|
|
sctp_handle_addr_wq(void)
|
|
{
|
|
/* deal with the ADDR wq from the rtsock calls */
|
|
struct sctp_laddr *wi;
|
|
|
|
SCTP_IPI_ADDR_LOCK();
|
|
wi = LIST_FIRST(&sctppcbinfo.addr_wq);
|
|
if (wi == NULL) {
|
|
SCTP_IPI_ADDR_UNLOCK();
|
|
return;
|
|
}
|
|
LIST_REMOVE(wi, sctp_nxt_addr);
|
|
if (!LIST_EMPTY(&sctppcbinfo.addr_wq)) {
|
|
sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ,
|
|
(struct sctp_inpcb *)NULL,
|
|
(struct sctp_tcb *)NULL,
|
|
(struct sctp_nets *)NULL);
|
|
}
|
|
SCTP_IPI_ADDR_UNLOCK();
|
|
if (wi->action == RTM_ADD) {
|
|
sctp_add_ip_address(wi->ifa);
|
|
} else if (wi->action == RTM_DELETE) {
|
|
sctp_delete_ip_address(wi->ifa);
|
|
}
|
|
IFAFREE(wi->ifa);
|
|
SCTP_ZONE_FREE(sctppcbinfo.ipi_zone_laddr, wi);
|
|
SCTP_DECR_LADDR_COUNT();
|
|
}
|
|
|
|
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;
|
|
struct sctp_iterator *it = NULL;
|
|
|
|
|
|
s = splnet();
|
|
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, (uint8_t) tmr->type);
|
|
sctp_auditing(3, inp, stcb, net);
|
|
#endif
|
|
|
|
/* sanity checks... */
|
|
if (tmr->self != (void *)tmr) {
|
|
/*
|
|
* printf("Stale SCTP timer fired (%p), ignoring...\n",
|
|
* tmr);
|
|
*/
|
|
splx(s);
|
|
return;
|
|
}
|
|
if (!SCTP_IS_TIMER_TYPE_VALID(tmr->type)) {
|
|
/*
|
|
* printf("SCTP timer fired with invalid type: 0x%x\n",
|
|
* tmr->type);
|
|
*/
|
|
splx(s);
|
|
return;
|
|
}
|
|
if ((tmr->type != SCTP_TIMER_TYPE_ADDR_WQ) && (inp == NULL)) {
|
|
splx(s);
|
|
return;
|
|
}
|
|
/* if this is an iterator timeout, get the struct and clear inp */
|
|
if (tmr->type == SCTP_TIMER_TYPE_ITERATOR) {
|
|
it = (struct sctp_iterator *)inp;
|
|
inp = NULL;
|
|
}
|
|
if (inp) {
|
|
SCTP_INP_INCR_REF(inp);
|
|
if ((inp->sctp_socket == 0) &&
|
|
((tmr->type != SCTP_TIMER_TYPE_INPKILL) &&
|
|
(tmr->type != SCTP_TIMER_TYPE_SHUTDOWN) &&
|
|
(tmr->type != SCTP_TIMER_TYPE_SHUTDOWNACK) &&
|
|
(tmr->type != SCTP_TIMER_TYPE_SHUTDOWNGUARD) &&
|
|
(tmr->type != SCTP_TIMER_TYPE_ASOCKILL))
|
|
) {
|
|
splx(s);
|
|
SCTP_INP_DECR_REF(inp);
|
|
return;
|
|
}
|
|
}
|
|
if (stcb) {
|
|
if (stcb->asoc.state == 0) {
|
|
splx(s);
|
|
if (inp) {
|
|
SCTP_INP_DECR_REF(inp);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
#ifdef SCTP_DEBUG
|
|
if (sctp_debug_on & SCTP_DEBUG_TIMER1) {
|
|
printf("Timer type %d goes off\n", tmr->type);
|
|
}
|
|
#endif /* SCTP_DEBUG */
|
|
if (!callout_active(&tmr->timer)) {
|
|
splx(s);
|
|
if (inp) {
|
|
SCTP_INP_DECR_REF(inp);
|
|
}
|
|
return;
|
|
}
|
|
if (stcb) {
|
|
atomic_add_int(&stcb->asoc.refcnt, 1);
|
|
SCTP_TCB_LOCK(stcb);
|
|
atomic_add_int(&stcb->asoc.refcnt, -1);
|
|
}
|
|
/* mark as being serviced now */
|
|
callout_deactivate(&tmr->timer);
|
|
|
|
/* call the handler for the appropriate timer type */
|
|
switch (tmr->type) {
|
|
case SCTP_TIMER_TYPE_ADDR_WQ:
|
|
sctp_handle_addr_wq();
|
|
break;
|
|
case SCTP_TIMER_TYPE_ITERATOR:
|
|
SCTP_STAT_INCR(sctps_timoiterator);
|
|
sctp_iterator_timer(it);
|
|
break;
|
|
case SCTP_TIMER_TYPE_SEND:
|
|
SCTP_STAT_INCR(sctps_timodata);
|
|
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, SCTP_OUTPUT_FROM_T3);
|
|
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.
|
|
*/
|
|
chk = TAILQ_FIRST(&stcb->asoc.sent_queue);
|
|
sctp_timer_start(SCTP_TIMER_TYPE_SEND, inp, stcb,
|
|
chk->whoTo);
|
|
}
|
|
break;
|
|
case SCTP_TIMER_TYPE_INIT:
|
|
SCTP_STAT_INCR(sctps_timoinit);
|
|
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_STAT_INCR(sctps_timosack);
|
|
sctp_send_sack(stcb);
|
|
#ifdef SCTP_AUDITING_ENABLED
|
|
sctp_auditing(4, inp, stcb, net);
|
|
#endif
|
|
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SACK_TMR);
|
|
break;
|
|
case SCTP_TIMER_TYPE_SHUTDOWN:
|
|
if (sctp_shutdown_timer(inp, stcb, net)) {
|
|
/* no need to unlock on tcb its gone */
|
|
goto out_decr;
|
|
}
|
|
SCTP_STAT_INCR(sctps_timoshutdown);
|
|
#ifdef SCTP_AUDITING_ENABLED
|
|
sctp_auditing(4, inp, stcb, net);
|
|
#endif
|
|
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SHUT_TMR);
|
|
break;
|
|
case SCTP_TIMER_TYPE_HEARTBEAT:
|
|
{
|
|
struct sctp_nets *net;
|
|
int cnt_of_unconf = 0;
|
|
|
|
SCTP_STAT_INCR(sctps_timoheartbeat);
|
|
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 (cnt_of_unconf == 0) {
|
|
if (sctp_heartbeat_timer(inp, stcb, net, cnt_of_unconf)) {
|
|
/* no need to unlock on tcb its gone */
|
|
goto out_decr;
|
|
}
|
|
}
|
|
#ifdef SCTP_AUDITING_ENABLED
|
|
sctp_auditing(4, inp, stcb, net);
|
|
#endif
|
|
sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep,
|
|
stcb, net);
|
|
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_HB_TMR);
|
|
}
|
|
break;
|
|
case SCTP_TIMER_TYPE_COOKIE:
|
|
if (sctp_cookie_timer(inp, stcb, net)) {
|
|
/* no need to unlock on tcb its gone */
|
|
goto out_decr;
|
|
}
|
|
SCTP_STAT_INCR(sctps_timocookie);
|
|
#ifdef SCTP_AUDITING_ENABLED
|
|
sctp_auditing(4, inp, stcb, net);
|
|
#endif
|
|
/*
|
|
* We consider T3 and Cookie timer pretty much the same with
|
|
* respect to where from in chunk_output.
|
|
*/
|
|
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_T3);
|
|
break;
|
|
case SCTP_TIMER_TYPE_NEWCOOKIE:
|
|
{
|
|
struct timeval tv;
|
|
int i, secret;
|
|
|
|
SCTP_STAT_INCR(sctps_timosecret);
|
|
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_STAT_INCR(sctps_timopathmtu);
|
|
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;
|
|
}
|
|
SCTP_STAT_INCR(sctps_timoshutdownack);
|
|
#ifdef SCTP_AUDITING_ENABLED
|
|
sctp_auditing(4, inp, stcb, net);
|
|
#endif
|
|
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SHUT_ACK_TMR);
|
|
break;
|
|
case SCTP_TIMER_TYPE_SHUTDOWNGUARD:
|
|
SCTP_STAT_INCR(sctps_timoshutdownguard);
|
|
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_STAT_INCR(sctps_timostrmrst);
|
|
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_STRRST_TMR);
|
|
break;
|
|
case SCTP_TIMER_TYPE_EARLYFR:
|
|
/* Need to do FR of things for net */
|
|
SCTP_STAT_INCR(sctps_timoearlyfr);
|
|
sctp_early_fr_timer(inp, stcb, net);
|
|
break;
|
|
case SCTP_TIMER_TYPE_ASCONF:
|
|
if (sctp_asconf_timer(inp, stcb, net)) {
|
|
/* no need to unlock on tcb its gone */
|
|
goto out_decr;
|
|
}
|
|
SCTP_STAT_INCR(sctps_timoasconf);
|
|
#ifdef SCTP_AUDITING_ENABLED
|
|
sctp_auditing(4, inp, stcb, net);
|
|
#endif
|
|
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_ASCONF_TMR);
|
|
break;
|
|
|
|
case SCTP_TIMER_TYPE_AUTOCLOSE:
|
|
SCTP_STAT_INCR(sctps_timoautoclose);
|
|
sctp_autoclose_timer(inp, stcb, net);
|
|
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_AUTOCLOSE_TMR);
|
|
did_output = 0;
|
|
break;
|
|
case SCTP_TIMER_TYPE_ASOCKILL:
|
|
SCTP_STAT_INCR(sctps_timoassockill);
|
|
/* Can we free it yet? */
|
|
SCTP_INP_DECR_REF(inp);
|
|
sctp_timer_stop(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL);
|
|
sctp_free_assoc(inp, stcb, 0);
|
|
/*
|
|
* free asoc, always unlocks (or destroy's) so prevent
|
|
* duplicate unlock or unlock of a free mtx :-0
|
|
*/
|
|
stcb = NULL;
|
|
goto out_no_decr;
|
|
break;
|
|
case SCTP_TIMER_TYPE_INPKILL:
|
|
SCTP_STAT_INCR(sctps_timoinpkill);
|
|
/*
|
|
* special case, take away our increment since WE are the
|
|
* killer
|
|
*/
|
|
SCTP_INP_DECR_REF(inp);
|
|
sctp_timer_stop(SCTP_TIMER_TYPE_INPKILL, inp, NULL, NULL);
|
|
sctp_inpcb_free(inp, 1, 0);
|
|
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, (uint8_t) tmr->type);
|
|
if (inp)
|
|
sctp_auditing(5, inp, stcb, net);
|
|
#endif
|
|
if ((did_output) && stcb) {
|
|
/*
|
|
* 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:
|
|
if (inp) {
|
|
SCTP_INP_DECR_REF(inp);
|
|
}
|
|
out_no_decr:
|
|
|
|
#ifdef SCTP_DEBUG
|
|
if (sctp_debug_on & SCTP_DEBUG_TIMER1) {
|
|
printf("Timer now complete (type %d)\n", tmr->type);
|
|
}
|
|
#endif /* SCTP_DEBUG */
|
|
splx(s);
|
|
if (inp) {
|
|
}
|
|
}
|
|
|
|
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 ((t_type != SCTP_TIMER_TYPE_ADDR_WQ) &&
|
|
(inp == NULL))
|
|
return (EFAULT);
|
|
|
|
to_ticks = 0;
|
|
|
|
tmr = NULL;
|
|
if (stcb) {
|
|
SCTP_TCB_LOCK_ASSERT(stcb);
|
|
}
|
|
switch (t_type) {
|
|
case SCTP_TIMER_TYPE_ADDR_WQ:
|
|
/* Only 1 tick away :-) */
|
|
tmr = &sctppcbinfo.addr_wq_timer;
|
|
to_ticks = 1;
|
|
break;
|
|
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 = MSEC_TO_TICKS(stcb->asoc.delayed_ack);
|
|
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) &&
|
|
(lnet->dest_state & SCTP_ADDR_REACHABLE)) {
|
|
cnt_of_unconf++;
|
|
}
|
|
}
|
|
if (cnt_of_unconf) {
|
|
lnet = NULL;
|
|
sctp_heartbeat_timer(inp, stcb, lnet, cnt_of_unconf);
|
|
}
|
|
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.hb_is_disabled) &&
|
|
(cnt_of_unconf == 0)) {
|
|
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 = MSEC_TO_TICKS(to_ticks);
|
|
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_ASOCKILL:
|
|
if (stcb == NULL) {
|
|
return (EFAULT);
|
|
}
|
|
tmr = &stcb->asoc.strreset_timer;
|
|
to_ticks = MSEC_TO_TICKS(SCTP_ASOC_KILL_TIMEOUT);
|
|
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 = MSEC_TO_TICKS(SCTP_INP_KILL_TIMEOUT);
|
|
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_EARLYFR:
|
|
{
|
|
unsigned int msec;
|
|
|
|
if ((stcb == NULL) || (net == NULL)) {
|
|
return (EFAULT);
|
|
}
|
|
if (net->flight_size > net->cwnd) {
|
|
/* no need to start */
|
|
return (0);
|
|
}
|
|
SCTP_STAT_INCR(sctps_earlyfrstart);
|
|
if (net->lastsa == 0) {
|
|
/* Hmm no rtt estimate yet? */
|
|
msec = stcb->asoc.initial_rto >> 2;
|
|
} else {
|
|
msec = ((net->lastsa >> 2) + net->lastsv) >> 1;
|
|
}
|
|
if (msec < sctp_early_fr_msec) {
|
|
msec = sctp_early_fr_msec;
|
|
if (msec < SCTP_MINFR_MSEC_FLOOR) {
|
|
msec = SCTP_MINFR_MSEC_FLOOR;
|
|
}
|
|
}
|
|
to_ticks = MSEC_TO_TICKS(msec);
|
|
tmr = &net->fr_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;
|
|
tmr->self = (void *)tmr;
|
|
tmr->ticks = ticks;
|
|
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 ((t_type != SCTP_TIMER_TYPE_ADDR_WQ) &&
|
|
(inp == NULL))
|
|
return (EFAULT);
|
|
|
|
tmr = NULL;
|
|
if (stcb) {
|
|
SCTP_TCB_LOCK_ASSERT(stcb);
|
|
}
|
|
switch (t_type) {
|
|
case SCTP_TIMER_TYPE_ADDR_WQ:
|
|
tmr = &sctppcbinfo.addr_wq_timer;
|
|
break;
|
|
case SCTP_TIMER_TYPE_EARLYFR:
|
|
if ((stcb == NULL) || (net == NULL)) {
|
|
return (EFAULT);
|
|
}
|
|
tmr = &net->fr_timer;
|
|
SCTP_STAT_INCR(sctps_earlyfrstop);
|
|
break;
|
|
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_ASOCKILL:
|
|
/*
|
|
* Stop the asoc kill timer.
|
|
*/
|
|
if (stcb == NULL) {
|
|
return (EFAULT);
|
|
}
|
|
tmr = &stcb->asoc.strreset_timer;
|
|
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) || (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;
|
|
}
|
|
}
|
|
tmr->self = NULL;
|
|
callout_stop(&tmr->timer);
|
|
return (0);
|
|
}
|
|
|
|
#ifdef SCTP_USE_ADLER32
|
|
static uint32_t
|
|
update_adler32(uint32_t adler, uint8_t * buf, int32_t len)
|
|
{
|
|
uint32_t s1 = adler & 0xffff;
|
|
uint32_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
|
|
|
|
|
|
uint32_t
|
|
sctp_calculate_len(struct mbuf *m)
|
|
{
|
|
uint32_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 <machine/in_cksum.h>
|
|
|
|
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) {
|
|
if ((at->m_len - offset) > 0) {
|
|
#ifdef SCTP_USE_ADLER32
|
|
base = update_adler32(base,
|
|
(unsigned char *)(at->m_data + offset),
|
|
(unsigned int)(at->m_len - offset));
|
|
#else
|
|
if ((at->m_len - offset) < 4) {
|
|
/* Use old method if less than 4 bytes */
|
|
base = old_update_crc32(base,
|
|
(unsigned char *)(at->m_data + offset),
|
|
(unsigned int)(at->m_len - offset));
|
|
} else {
|
|
base = update_crc32(base,
|
|
(unsigned char *)(at->m_data + offset),
|
|
(unsigned int)(at->m_len - offset));
|
|
}
|
|
#endif /* SCTP_USE_ADLER32 */
|
|
tlen += at->m_len - offset;
|
|
/* we only offset once into the first mbuf */
|
|
}
|
|
if (offset) {
|
|
if (offset < at->m_len)
|
|
offset = 0;
|
|
else
|
|
offset -= at->m_len;
|
|
}
|
|
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;
|
|
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;
|
|
TAILQ_FOREACH(chk, &asoc->send_queue, sctp_next) {
|
|
|
|
if (chk->send_size > eff_mtu) {
|
|
chk->flags |= CHUNK_FLAGS_FRAGMENT_OK;
|
|
}
|
|
}
|
|
TAILQ_FOREACH(chk, &asoc->sent_queue, sctp_next) {
|
|
if (chk->send_size > eff_mtu) {
|
|
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
|
|
*/
|
|
uint32_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);
|
|
if ((int)net->prev_rtt > o_calctime) {
|
|
net->rtt_variance = net->prev_rtt - o_calctime;
|
|
/* decreasing */
|
|
net->rto_variance_dir = 0;
|
|
} else {
|
|
/* increasing */
|
|
net->rtt_variance = o_calctime - net->prev_rtt;
|
|
net->rto_variance_dir = 1;
|
|
}
|
|
#ifdef SCTP_RTTVAR_LOGGING
|
|
rto_logging(net, SCTP_LOG_RTTVAR);
|
|
#endif
|
|
net->prev_rtt = o_calctime;
|
|
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;
|
|
net->rto_variance_dir = 1;
|
|
net->prev_rtt = o_calctime;
|
|
net->rtt_variance = 0;
|
|
#ifdef SCTP_RTTVAR_LOGGING
|
|
rto_logging(net, SCTP_LOG_INITIAL_RTT);
|
|
#endif
|
|
}
|
|
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 ((uint32_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.
|
|
*/
|
|
__inline caddr_t
|
|
sctp_m_getptr(struct mbuf *m, int off, int len, uint8_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,
|
|
(uint8_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.
|
|
*/
|
|
uint8_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 = (uint8_t *) (mtod(m, caddr_t)+m->m_len);
|
|
m->m_len += padlen;
|
|
} else {
|
|
/* Hard way we must grow the mbuf */
|
|
struct mbuf *tmp;
|
|
|
|
tmp = sctp_get_mbuf_for_msg(padlen, 0, M_DONTWAIT, 1, 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, uint8_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, struct mbuf *last_mbuf)
|
|
{
|
|
/* find the last mbuf in chain and pad it */
|
|
struct mbuf *m_at;
|
|
|
|
m_at = m;
|
|
if (last_mbuf) {
|
|
return (sctp_add_pad_tombuf(last_mbuf, padval));
|
|
} else {
|
|
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);
|
|
}
|
|
|
|
int sctp_asoc_change_wake = 0;
|
|
|
|
static void
|
|
sctp_notify_assoc_change(uint32_t event, struct sctp_tcb *stcb,
|
|
uint32_t error, void *data)
|
|
{
|
|
struct mbuf *m_notify;
|
|
struct sctp_assoc_change *sac;
|
|
struct sctp_queued_to_read *control;
|
|
|
|
/*
|
|
* First if we are are going down dump everything we can to the
|
|
* socket rcv queue.
|
|
*/
|
|
|
|
if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
|
|
(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
|
|
(stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET)
|
|
) {
|
|
/* If the socket is gone we are out of here */
|
|
return;
|
|
}
|
|
/*
|
|
* 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)) {
|
|
if (TAILQ_EMPTY(&stcb->sctp_ep->read_queue)) {
|
|
stcb->sctp_socket->so_error = ECONNRESET;
|
|
}
|
|
/* Wake ANY sleepers */
|
|
sorwakeup(stcb->sctp_socket);
|
|
sowwakeup(stcb->sctp_socket);
|
|
sctp_asoc_change_wake++;
|
|
}
|
|
if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_RECVASSOCEVNT)) {
|
|
/* event not enabled */
|
|
return;
|
|
}
|
|
m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_assoc_change), 1, M_DONTWAIT, 1, 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;
|
|
control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
|
|
0, 0, 0, 0, 0, 0,
|
|
m_notify);
|
|
if (control == NULL) {
|
|
/* no memory */
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
control->length = m_notify->m_len;
|
|
/* not that we need this */
|
|
control->tail_mbuf = m_notify;
|
|
sctp_add_to_readq(stcb->sctp_ep, stcb,
|
|
control,
|
|
&stcb->sctp_socket->so_rcv, 1);
|
|
if (event == SCTP_COMM_LOST) {
|
|
/* Wake up any sleeper */
|
|
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 sctp_queued_to_read *control;
|
|
|
|
if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_RECVPADDREVNT))
|
|
/* event not enabled */
|
|
return;
|
|
|
|
m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_paddr_change), 1, M_DONTWAIT, 1, MT_DATA);
|
|
if (m_notify == NULL)
|
|
return;
|
|
m_notify->m_len = 0;
|
|
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;
|
|
|
|
/* append to socket */
|
|
control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
|
|
0, 0, 0, 0, 0, 0,
|
|
m_notify);
|
|
if (control == NULL) {
|
|
/* no memory */
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
control->length = m_notify->m_len;
|
|
/* not that we need this */
|
|
control->tail_mbuf = m_notify;
|
|
sctp_add_to_readq(stcb->sctp_ep, stcb,
|
|
control,
|
|
&stcb->sctp_socket->so_rcv, 1);
|
|
}
|
|
|
|
|
|
static void
|
|
sctp_notify_send_failed(struct sctp_tcb *stcb, uint32_t error,
|
|
struct sctp_tmit_chunk *chk)
|
|
{
|
|
struct mbuf *m_notify;
|
|
struct sctp_send_failed *ssf;
|
|
struct sctp_queued_to_read *control;
|
|
int length;
|
|
|
|
if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_RECVSENDFAILEVNT))
|
|
/* event not enabled */
|
|
return;
|
|
|
|
length = sizeof(struct sctp_send_failed) + chk->send_size;
|
|
m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_send_failed), 1, M_DONTWAIT, 1, 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;
|
|
m_notify->m_flags |= M_NOTIFICATION;
|
|
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;
|
|
/*
|
|
* For this case, we check the actual socket buffer, since the assoc
|
|
* is going away we don't want to overfill the socket buffer for a
|
|
* non-reader
|
|
*/
|
|
if (sctp_sbspace_failedmsgs(&stcb->sctp_socket->so_rcv) < m_notify->m_len) {
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
/* append to socket */
|
|
control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
|
|
0, 0, 0, 0, 0, 0,
|
|
m_notify);
|
|
if (control == NULL) {
|
|
/* no memory */
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
sctp_add_to_readq(stcb->sctp_ep, stcb,
|
|
control,
|
|
&stcb->sctp_socket->so_rcv, 1);
|
|
}
|
|
|
|
|
|
static void
|
|
sctp_notify_send_failed2(struct sctp_tcb *stcb, uint32_t error,
|
|
struct sctp_stream_queue_pending *sp)
|
|
{
|
|
struct mbuf *m_notify;
|
|
struct sctp_send_failed *ssf;
|
|
struct sctp_queued_to_read *control;
|
|
int length;
|
|
|
|
if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_RECVSENDFAILEVNT))
|
|
/* event not enabled */
|
|
return;
|
|
|
|
length = sizeof(struct sctp_send_failed) + sp->length;
|
|
m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_adaption_event), 1, M_DONTWAIT, 1, 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 = sp->stream;
|
|
ssf->ssf_info.sinfo_ssn = sp->strseq;
|
|
ssf->ssf_info.sinfo_flags = sp->sinfo_flags;
|
|
ssf->ssf_info.sinfo_ppid = sp->ppid;
|
|
ssf->ssf_info.sinfo_context = sp->context;
|
|
ssf->ssf_info.sinfo_assoc_id = sctp_get_associd(stcb);
|
|
ssf->ssf_assoc_id = sctp_get_associd(stcb);
|
|
m_notify->m_next = sp->data;
|
|
m_notify->m_flags |= M_NOTIFICATION;
|
|
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 */
|
|
sp->data = NULL;
|
|
/*
|
|
* For this case, we check the actual socket buffer, since the assoc
|
|
* is going away we don't want to overfill the socket buffer for a
|
|
* non-reader
|
|
*/
|
|
if (sctp_sbspace_failedmsgs(&stcb->sctp_socket->so_rcv) < m_notify->m_len) {
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
/* append to socket */
|
|
control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
|
|
0, 0, 0, 0, 0, 0,
|
|
m_notify);
|
|
if (control == NULL) {
|
|
/* no memory */
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
sctp_add_to_readq(stcb->sctp_ep, stcb,
|
|
control,
|
|
&stcb->sctp_socket->so_rcv, 1);
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
sctp_notify_adaptation_layer(struct sctp_tcb *stcb,
|
|
uint32_t error)
|
|
{
|
|
struct mbuf *m_notify;
|
|
struct sctp_adaptation_event *sai;
|
|
struct sctp_queued_to_read *control;
|
|
|
|
if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_ADAPTATIONEVNT))
|
|
/* event not enabled */
|
|
return;
|
|
|
|
m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_adaption_event), 1, M_DONTWAIT, 1, MT_DATA);
|
|
if (m_notify == NULL)
|
|
/* no space left */
|
|
return;
|
|
m_notify->m_len = 0;
|
|
sai = mtod(m_notify, struct sctp_adaptation_event *);
|
|
sai->sai_type = SCTP_ADAPTATION_INDICATION;
|
|
sai->sai_flags = 0;
|
|
sai->sai_length = sizeof(struct sctp_adaptation_event);
|
|
sai->sai_adaptation_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_adaptation_event);
|
|
m_notify->m_pkthdr.rcvif = 0;
|
|
m_notify->m_len = sizeof(struct sctp_adaptation_event);
|
|
m_notify->m_next = NULL;
|
|
|
|
/* append to socket */
|
|
control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
|
|
0, 0, 0, 0, 0, 0,
|
|
m_notify);
|
|
if (control == NULL) {
|
|
/* no memory */
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
control->length = m_notify->m_len;
|
|
/* not that we need this */
|
|
control->tail_mbuf = m_notify;
|
|
sctp_add_to_readq(stcb->sctp_ep, stcb,
|
|
control,
|
|
&stcb->sctp_socket->so_rcv, 1);
|
|
}
|
|
|
|
/* This always must be called with the read-queue LOCKED in the INP */
|
|
void
|
|
sctp_notify_partial_delivery_indication(struct sctp_tcb *stcb,
|
|
uint32_t error, int nolock)
|
|
{
|
|
struct mbuf *m_notify;
|
|
struct sctp_pdapi_event *pdapi;
|
|
struct sctp_queued_to_read *control;
|
|
struct sockbuf *sb;
|
|
|
|
if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_PDAPIEVNT))
|
|
/* event not enabled */
|
|
return;
|
|
|
|
m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_pdapi_event), 1, M_DONTWAIT, 1, 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;
|
|
control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
|
|
0, 0, 0, 0, 0, 0,
|
|
m_notify);
|
|
if (control == NULL) {
|
|
/* no memory */
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
control->length = m_notify->m_len;
|
|
/* not that we need this */
|
|
control->tail_mbuf = m_notify;
|
|
control->held_length = 0;
|
|
control->length = 0;
|
|
if (nolock == 0) {
|
|
SCTP_INP_READ_LOCK(stcb->sctp_ep);
|
|
}
|
|
sb = &stcb->sctp_socket->so_rcv;
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBALLOC, m_notify->m_len);
|
|
#endif
|
|
sctp_sballoc(stcb, sb, m_notify);
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0);
|
|
#endif
|
|
atomic_add_int(&control->length, m_notify->m_len);
|
|
control->end_added = 1;
|
|
if (stcb->asoc.control_pdapi)
|
|
TAILQ_INSERT_AFTER(&stcb->sctp_ep->read_queue, stcb->asoc.control_pdapi, control, next);
|
|
else {
|
|
/* we really should not see this case */
|
|
TAILQ_INSERT_TAIL(&stcb->sctp_ep->read_queue, control, next);
|
|
}
|
|
if (nolock == 0) {
|
|
SCTP_INP_READ_UNLOCK(stcb->sctp_ep);
|
|
}
|
|
if (stcb->sctp_ep && stcb->sctp_socket) {
|
|
/* This should always be the case */
|
|
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 sctp_queued_to_read *control;
|
|
|
|
/*
|
|
* 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! */
|
|
socantsendmore(stcb->sctp_socket);
|
|
}
|
|
if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_RECVSHUTDOWNEVNT))
|
|
/* event not enabled */
|
|
return;
|
|
|
|
m_notify = sctp_get_mbuf_for_msg(sizeof(struct sctp_shutdown_event), 1, M_DONTWAIT, 1, 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;
|
|
|
|
/* append to socket */
|
|
control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
|
|
0, 0, 0, 0, 0, 0,
|
|
m_notify);
|
|
if (control == NULL) {
|
|
/* no memory */
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
control->length = m_notify->m_len;
|
|
/* not that we need this */
|
|
control->tail_mbuf = m_notify;
|
|
sctp_add_to_readq(stcb->sctp_ep, stcb,
|
|
control,
|
|
&stcb->sctp_socket->so_rcv, 1);
|
|
}
|
|
|
|
static void
|
|
sctp_notify_stream_reset(struct sctp_tcb *stcb,
|
|
int number_entries, uint16_t * list, int flag)
|
|
{
|
|
struct mbuf *m_notify;
|
|
struct sctp_queued_to_read *control;
|
|
struct sctp_stream_reset_event *strreset;
|
|
int len;
|
|
|
|
if (sctp_is_feature_off(stcb->sctp_ep, SCTP_PCB_FLAGS_STREAM_RESETEVNT))
|
|
/* event not enabled */
|
|
return;
|
|
|
|
m_notify = sctp_get_mbuf_for_msg(MCLBYTES, 1, M_DONTWAIT, 1, 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)) {
|
|
/* never enough room */
|
|
sctp_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; i < number_entries; i++) {
|
|
strreset->strreset_list[i] = ntohs(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->asoc, &stcb->sctp_socket->so_rcv) < m_notify->m_len) {
|
|
/* no space */
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
/* append to socket */
|
|
control = sctp_build_readq_entry(stcb, stcb->asoc.primary_destination,
|
|
0, 0, 0, 0, 0, 0,
|
|
m_notify);
|
|
if (control == NULL) {
|
|
/* no memory */
|
|
sctp_m_freem(m_notify);
|
|
return;
|
|
}
|
|
control->length = m_notify->m_len;
|
|
/* not that we need this */
|
|
control->tail_mbuf = m_notify;
|
|
sctp_add_to_readq(stcb->sctp_ep, stcb,
|
|
control,
|
|
&stcb->sctp_socket->so_rcv, 1);
|
|
}
|
|
|
|
|
|
void
|
|
sctp_ulp_notify(uint32_t notification, struct sctp_tcb *stcb,
|
|
uint32_t error, void *data)
|
|
{
|
|
if (stcb == NULL) {
|
|
/* unlikely but */
|
|
return;
|
|
}
|
|
if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
|
|
(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
|
|
(stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET)
|
|
) {
|
|
/* 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;
|
|
}
|
|
if (stcb && (stcb->asoc.assoc_up_sent == 0) && (notification != SCTP_NOTIFY_ASSOC_UP)) {
|
|
if ((notification != SCTP_NOTIFY_ASSOC_DOWN) &&
|
|
(notification != SCTP_NOTIFY_ASSOC_ABORTED) &&
|
|
(notification != SCTP_NOTIFY_SPECIAL_SP_FAIL) &&
|
|
(notification != SCTP_NOTIFY_DG_FAIL) &&
|
|
(notification != SCTP_NOTIFY_PEER_SHUTDOWN)) {
|
|
sctp_notify_assoc_change(SCTP_COMM_UP, stcb, 0, NULL);
|
|
stcb->asoc.assoc_up_sent = 1;
|
|
}
|
|
}
|
|
switch (notification) {
|
|
case SCTP_NOTIFY_ASSOC_UP:
|
|
if (stcb->asoc.assoc_up_sent == 0) {
|
|
sctp_notify_assoc_change(SCTP_COMM_UP, stcb, error, NULL);
|
|
stcb->asoc.assoc_up_sent = 1;
|
|
}
|
|
break;
|
|
case SCTP_NOTIFY_ASSOC_DOWN:
|
|
sctp_notify_assoc_change(SCTP_SHUTDOWN_COMP, stcb, error, NULL);
|
|
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_SPECIAL_SP_FAIL:
|
|
sctp_notify_send_failed2(stcb, error,
|
|
(struct sctp_stream_queue_pending *)data);
|
|
break;
|
|
case SCTP_NOTIFY_DG_FAIL:
|
|
sctp_notify_send_failed(stcb, error,
|
|
(struct sctp_tmit_chunk *)data);
|
|
break;
|
|
case SCTP_NOTIFY_ADAPTATION_INDICATION:
|
|
/* Here the error is the adaptation indication */
|
|
sctp_notify_adaptation_layer(stcb, error);
|
|
break;
|
|
case SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION:
|
|
sctp_notify_partial_delivery_indication(stcb, error, 0);
|
|
break;
|
|
case SCTP_NOTIFY_STRDATA_ERR:
|
|
break;
|
|
case SCTP_NOTIFY_ASSOC_ABORTED:
|
|
sctp_notify_assoc_change(SCTP_COMM_LOST, stcb, error, NULL);
|
|
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, data);
|
|
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_STR_RESET_FAILED_OUT:
|
|
sctp_notify_stream_reset(stcb, error, ((uint16_t *) data), (SCTP_STRRESET_OUTBOUND_STR | SCTP_STRRESET_INBOUND_STR));
|
|
break;
|
|
|
|
case SCTP_NOTIFY_STR_RESET_FAILED_IN:
|
|
sctp_notify_stream_reset(stcb, error, ((uint16_t *) data), (SCTP_STRRESET_INBOUND_STR | 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;
|
|
case SCTP_NOTIFY_AUTH_NEW_KEY:
|
|
sctp_notify_authentication(stcb, SCTP_AUTH_NEWKEY, error,
|
|
(uint16_t) (uintptr_t) data);
|
|
break;
|
|
#if 0
|
|
case SCTP_NOTIFY_AUTH_KEY_CONFLICT:
|
|
sctp_notify_authentication(stcb, SCTP_AUTH_KEY_CONFLICT,
|
|
error, (uint16_t) (uintptr_t) data);
|
|
break;
|
|
#endif /* not yet? remove? */
|
|
|
|
|
|
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;
|
|
struct sctp_stream_queue_pending *sp;
|
|
|
|
asoc = &stcb->asoc;
|
|
|
|
if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
|
|
(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
|
|
(stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET)) {
|
|
return;
|
|
}
|
|
/* now through all the gunk freeing chunks */
|
|
|
|
TAILQ_FOREACH(outs, &asoc->out_wheel, next_spoke) {
|
|
/* now clean up any chunks here */
|
|
stcb->asoc.locked_on_sending = NULL;
|
|
sp = TAILQ_FIRST(&outs->outqueue);
|
|
while (sp) {
|
|
stcb->asoc.stream_queue_cnt--;
|
|
TAILQ_REMOVE(&outs->outqueue, sp, next);
|
|
sctp_free_spbufspace(stcb, asoc, sp);
|
|
sctp_ulp_notify(SCTP_NOTIFY_SPECIAL_SP_FAIL, stcb,
|
|
SCTP_NOTIFY_DATAGRAM_UNSENT, (void *)sp);
|
|
if (sp->data) {
|
|
sctp_m_freem(sp->data);
|
|
sp->data = NULL;
|
|
}
|
|
if (sp->net)
|
|
sctp_free_remote_addr(sp->net);
|
|
sp->net = NULL;
|
|
/* Free the chunk */
|
|
sctp_free_a_strmoq(stcb, sp);
|
|
sp = 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);
|
|
if (chk->data) {
|
|
/*
|
|
* trim off the sctp chunk header(it should
|
|
* be there)
|
|
*/
|
|
if (chk->send_size >= sizeof(struct sctp_data_chunk)) {
|
|
m_adj(chk->data, sizeof(struct sctp_data_chunk));
|
|
sctp_mbuf_crush(chk->data);
|
|
}
|
|
}
|
|
sctp_free_bufspace(stcb, asoc, chk, 1);
|
|
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_free_a_chunk(stcb, chk);
|
|
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) {
|
|
/*
|
|
* trim off the sctp chunk header(it should
|
|
* be there)
|
|
*/
|
|
if (chk->send_size >= sizeof(struct sctp_data_chunk)) {
|
|
m_adj(chk->data, sizeof(struct sctp_data_chunk));
|
|
sctp_mbuf_crush(chk->data);
|
|
}
|
|
}
|
|
sctp_free_bufspace(stcb, asoc, chk, 1);
|
|
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_free_a_chunk(stcb, chk);
|
|
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) ||
|
|
(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
|
|
(stcb->asoc.state & SCTP_STATE_CLOSED_SOCKET)) {
|
|
return;
|
|
}
|
|
/* Tell them we lost the asoc */
|
|
sctp_report_all_outbound(stcb);
|
|
if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) ||
|
|
((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
|
|
(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_CONNECTED))) {
|
|
stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_WAS_ABORTED;
|
|
}
|
|
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)
|
|
{
|
|
uint32_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, 0);
|
|
} else {
|
|
if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
|
|
if (LIST_FIRST(&inp->sctp_asoc_list) == NULL) {
|
|
sctp_inpcb_free(inp, 1, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
sctp_abort_an_association(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
|
|
int error, struct mbuf *op_err)
|
|
{
|
|
uint32_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, 0);
|
|
}
|
|
}
|
|
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);
|
|
SCTP_STAT_INCR_COUNTER32(sctps_aborted);
|
|
if ((SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_OPEN) ||
|
|
(SCTP_GET_STATE(&stcb->asoc) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
|
|
SCTP_STAT_DECR_GAUGE32(sctps_currestab);
|
|
}
|
|
/* now free the asoc */
|
|
sctp_free_assoc(inp, stcb, 0);
|
|
}
|
|
|
|
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;
|
|
|
|
SCTP_STAT_INCR_COUNTER32(sctps_outoftheblue);
|
|
/* 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, 0);
|
|
}
|
|
}
|
|
ch = (struct sctp_chunkhdr *)sctp_m_getptr(m, offset,
|
|
sizeof(*ch), (uint8_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), (uint8_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, uint32_t * 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),
|
|
(uint8_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), (uint8_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), (uint8_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 (sa6_recoverscope(&a)) {
|
|
/* can't get scope, so can't match */
|
|
return (0);
|
|
}
|
|
if (b.sin6_scope_id == 0)
|
|
if (sa6_recoverscope(&b)) {
|
|
/* 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 (!sa6_recoverscope(store)) {
|
|
/* 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
|
|
*/
|
|
__inline 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;
|
|
unsigned char *p;
|
|
|
|
sin = (struct sockaddr_in *)sa;
|
|
p = (unsigned char *)&sin->sin_addr;
|
|
printf("IPv4 address: %u.%u.%u.%u:%d\n",
|
|
p[0], p[1], p[2], p[3], 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(HAVE_SCTP_SO_LASTRECORD)
|
|
|
|
/* 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
|
|
|
|
void
|
|
sctp_pull_off_control_to_new_inp(struct sctp_inpcb *old_inp,
|
|
struct sctp_inpcb *new_inp,
|
|
struct sctp_tcb *stcb)
|
|
{
|
|
/*
|
|
* go through our old INP and pull off any control structures that
|
|
* belong to stcb and move then to the new inp.
|
|
*/
|
|
struct socket *old_so, *new_so;
|
|
struct sctp_queued_to_read *control, *nctl;
|
|
struct sctp_readhead tmp_queue;
|
|
struct mbuf *m;
|
|
int error;
|
|
|
|
old_so = old_inp->sctp_socket;
|
|
new_so = new_inp->sctp_socket;
|
|
TAILQ_INIT(&tmp_queue);
|
|
|
|
SOCKBUF_LOCK(&(old_so->so_rcv));
|
|
|
|
error = sblock(&old_so->so_rcv, 0);
|
|
|
|
SOCKBUF_UNLOCK(&(old_so->so_rcv));
|
|
if (error) {
|
|
/*
|
|
* Gak, can't get sblock, we have a problem. data will be
|
|
* left stranded.. and we don't dare look at it since the
|
|
* other thread may be reading something. Oh well, its a
|
|
* screwed up app that does a peeloff OR a accept while
|
|
* reading from the main socket... actually its only the
|
|
* peeloff() case, since I think read will fail on a
|
|
* listening socket..
|
|
*/
|
|
return;
|
|
}
|
|
/* lock the socket buffers */
|
|
SCTP_INP_READ_LOCK(old_inp);
|
|
control = TAILQ_FIRST(&old_inp->read_queue);
|
|
/* Pull off all for out target stcb */
|
|
while (control) {
|
|
nctl = TAILQ_NEXT(control, next);
|
|
if (control->stcb == stcb) {
|
|
/* remove it we want it */
|
|
TAILQ_REMOVE(&old_inp->read_queue, control, next);
|
|
TAILQ_INSERT_TAIL(&tmp_queue, control, next);
|
|
m = control->data;
|
|
while (m) {
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&old_so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBFREE, m->m_len);
|
|
#endif
|
|
sctp_sbfree(control, stcb, &old_so->so_rcv, m);
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&old_so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0);
|
|
#endif
|
|
m = m->m_next;
|
|
}
|
|
}
|
|
control = nctl;
|
|
}
|
|
SCTP_INP_READ_UNLOCK(old_inp);
|
|
|
|
/* Remove the sb-lock on the old socket */
|
|
SOCKBUF_LOCK(&(old_so->so_rcv));
|
|
|
|
sbunlock(&old_so->so_rcv);
|
|
SOCKBUF_UNLOCK(&(old_so->so_rcv));
|
|
|
|
/* Now we move them over to the new socket buffer */
|
|
control = TAILQ_FIRST(&tmp_queue);
|
|
SCTP_INP_READ_LOCK(new_inp);
|
|
while (control) {
|
|
nctl = TAILQ_NEXT(control, next);
|
|
TAILQ_INSERT_TAIL(&new_inp->read_queue, control, next);
|
|
m = control->data;
|
|
while (m) {
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&new_so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBALLOC, m->m_len);
|
|
#endif
|
|
sctp_sballoc(stcb, &new_so->so_rcv, m);
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&new_so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0);
|
|
#endif
|
|
m = m->m_next;
|
|
}
|
|
control = nctl;
|
|
}
|
|
SCTP_INP_READ_UNLOCK(new_inp);
|
|
}
|
|
|
|
|
|
void
|
|
sctp_add_to_readq(struct sctp_inpcb *inp,
|
|
struct sctp_tcb *stcb,
|
|
struct sctp_queued_to_read *control,
|
|
struct sockbuf *sb,
|
|
int end)
|
|
{
|
|
/*
|
|
* Here we must place the control on the end of the socket read
|
|
* queue AND increment sb_cc so that select will work properly on
|
|
* read.
|
|
*/
|
|
struct mbuf *m, *prev = NULL;
|
|
|
|
if (inp == NULL) {
|
|
/* Gak, TSNH!! */
|
|
#ifdef INVARIENTS
|
|
panic("Gak, inp NULL on add_to_readq");
|
|
#endif
|
|
return;
|
|
}
|
|
SCTP_INP_READ_LOCK(inp);
|
|
m = control->data;
|
|
control->held_length = 0;
|
|
control->length = 0;
|
|
while (m) {
|
|
if (m->m_len == 0) {
|
|
/* Skip mbufs with NO length */
|
|
if (prev == NULL) {
|
|
/* First one */
|
|
control->data = sctp_m_free(m);
|
|
m = control->data;
|
|
} else {
|
|
prev->m_next = sctp_m_free(m);
|
|
m = prev->m_next;
|
|
}
|
|
if (m == NULL) {
|
|
control->tail_mbuf = prev;;
|
|
}
|
|
continue;
|
|
}
|
|
prev = m;
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBALLOC, m->m_len);
|
|
#endif
|
|
sctp_sballoc(stcb, sb, m);
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0);
|
|
#endif
|
|
atomic_add_int(&control->length, m->m_len);
|
|
m = m->m_next;
|
|
}
|
|
if (prev != NULL) {
|
|
control->tail_mbuf = prev;
|
|
if (end) {
|
|
prev->m_flags |= M_EOR;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
if (end) {
|
|
control->end_added = 1;
|
|
}
|
|
TAILQ_INSERT_TAIL(&inp->read_queue, control, next);
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
if (inp && inp->sctp_socket) {
|
|
sctp_sorwakeup(inp, inp->sctp_socket);
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
sctp_append_to_readq(struct sctp_inpcb *inp,
|
|
struct sctp_tcb *stcb,
|
|
struct sctp_queued_to_read *control,
|
|
struct mbuf *m,
|
|
int end,
|
|
int ctls_cumack,
|
|
struct sockbuf *sb)
|
|
{
|
|
/*
|
|
* A partial delivery API event is underway. OR we are appending on
|
|
* the reassembly queue.
|
|
*
|
|
* If PDAPI this means we need to add m to the end of the data.
|
|
* Increase the length in the control AND increment the sb_cc.
|
|
* Otherwise sb is NULL and all we need to do is put it at the end
|
|
* of the mbuf chain.
|
|
*/
|
|
int len = 0;
|
|
struct mbuf *mm, *tail = NULL, *prev = NULL;
|
|
|
|
if (inp) {
|
|
SCTP_INP_READ_LOCK(inp);
|
|
}
|
|
if (control == NULL) {
|
|
get_out:
|
|
if (inp) {
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
}
|
|
return (-1);
|
|
}
|
|
if ((control->tail_mbuf) &&
|
|
(control->tail_mbuf->m_flags & M_EOR)) {
|
|
/* huh this one is complete? */
|
|
goto get_out;
|
|
}
|
|
mm = m;
|
|
if (mm == NULL) {
|
|
goto get_out;
|
|
}
|
|
while (mm) {
|
|
if (mm->m_len == 0) {
|
|
/* Skip mbufs with NO lenght */
|
|
if (prev == NULL) {
|
|
/* First one */
|
|
m = sctp_m_free(mm);
|
|
mm = m;
|
|
} else {
|
|
prev->m_next = sctp_m_free(mm);
|
|
mm = prev->m_next;
|
|
}
|
|
continue;
|
|
}
|
|
prev = mm;
|
|
len += mm->m_len;
|
|
if (sb) {
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBALLOC, mm->m_len);
|
|
#endif
|
|
sctp_sballoc(stcb, sb, mm);
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(sb, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0);
|
|
#endif
|
|
}
|
|
mm = mm->m_next;
|
|
}
|
|
if (prev) {
|
|
tail = prev;
|
|
} else {
|
|
/* Really there should always be a prev */
|
|
if (m == NULL) {
|
|
/* Huh nothing left? */
|
|
#ifdef INVARIENTS
|
|
panic("Nothing left to add?");
|
|
#else
|
|
goto get_out;
|
|
#endif
|
|
}
|
|
tail = m;
|
|
}
|
|
if (end) {
|
|
/* message is complete */
|
|
tail->m_flags |= M_EOR;
|
|
if (control == stcb->asoc.control_pdapi) {
|
|
stcb->asoc.control_pdapi = NULL;
|
|
}
|
|
control->held_length = 0;
|
|
control->end_added = 1;
|
|
}
|
|
atomic_add_int(&control->length, len);
|
|
if (control->tail_mbuf) {
|
|
/* append */
|
|
control->tail_mbuf->m_next = m;
|
|
control->tail_mbuf = tail;
|
|
} else {
|
|
/* nothing there */
|
|
#ifdef INVARIENTS
|
|
if (control->data != NULL) {
|
|
panic("This should NOT happen");
|
|
}
|
|
#endif
|
|
control->data = m;
|
|
control->tail_mbuf = tail;
|
|
}
|
|
/*
|
|
* When we are appending in partial delivery, the cum-ack is used
|
|
* for the actual pd-api highest tsn on this mbuf. The true cum-ack
|
|
* is populated in the outbound sinfo structure from the true cumack
|
|
* if the association exists...
|
|
*/
|
|
control->sinfo_tsn = control->sinfo_cumtsn = ctls_cumack;
|
|
if (inp) {
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
}
|
|
if (inp && inp->sctp_socket) {
|
|
sctp_sorwakeup(inp, inp->sctp_socket);
|
|
}
|
|
return (0);
|
|
}
|
|
|
|
|
|
|
|
/*************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;
|
|
|
|
m = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_DONTWAIT, 1, 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);
|
|
}
|
|
|
|
#ifdef SCTP_MBCNT_LOGGING
|
|
void
|
|
sctp_free_bufspace(struct sctp_tcb *stcb, struct sctp_association *asoc,
|
|
struct sctp_tmit_chunk *tp1, int chk_cnt)
|
|
{
|
|
if (tp1->data == NULL) {
|
|
return;
|
|
}
|
|
asoc->chunks_on_out_queue -= chk_cnt;
|
|
sctp_log_mbcnt(SCTP_LOG_MBCNT_DECREASE,
|
|
asoc->total_output_queue_size,
|
|
tp1->book_size,
|
|
0,
|
|
tp1->mbcnt);
|
|
if (asoc->total_output_queue_size >= tp1->book_size) {
|
|
asoc->total_output_queue_size -= tp1->book_size;
|
|
} else {
|
|
asoc->total_output_queue_size = 0;
|
|
}
|
|
|
|
if (stcb->sctp_socket && (((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) ||
|
|
((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE)))) {
|
|
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;
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
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, 1);
|
|
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 (PR_SCTP_BUF_ENABLED(tp1->flags)) {
|
|
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);
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
sctp_user_rcvd(struct sctp_tcb *stcb, int *freed_so_far, int hold_rlock,
|
|
uint32_t rwnd_req)
|
|
{
|
|
/* User pulled some data, do we need a rwnd update? */
|
|
int r_unlocked = 0;
|
|
uint32_t dif, rwnd;
|
|
struct socket *so = NULL;
|
|
|
|
if (stcb == NULL)
|
|
return;
|
|
|
|
atomic_add_int(&stcb->asoc.refcnt, 1);
|
|
|
|
if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
|
|
/* Pre-check If we are freeing no update */
|
|
goto no_lock;
|
|
}
|
|
SCTP_INP_INCR_REF(stcb->sctp_ep);
|
|
if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
|
|
(stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
|
|
goto out;
|
|
}
|
|
so = stcb->sctp_socket;
|
|
if (so == NULL) {
|
|
goto out;
|
|
}
|
|
atomic_add_int(&stcb->freed_by_sorcv_sincelast, *freed_so_far);
|
|
/* Have you have freed enough to look */
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_ENTER_USER_RECV,
|
|
(stcb->asoc.my_rwnd - stcb->asoc.my_last_reported_rwnd),
|
|
*freed_so_far,
|
|
stcb->freed_by_sorcv_sincelast,
|
|
rwnd_req);
|
|
#endif
|
|
*freed_so_far = 0;
|
|
/* Yep, its worth a look and the lock overhead */
|
|
|
|
/* Figure out what the rwnd would be */
|
|
rwnd = sctp_calc_rwnd(stcb, &stcb->asoc);
|
|
if (rwnd >= stcb->asoc.my_last_reported_rwnd) {
|
|
dif = rwnd - stcb->asoc.my_last_reported_rwnd;
|
|
} else {
|
|
dif = 0;
|
|
}
|
|
if (dif >= rwnd_req) {
|
|
if (hold_rlock) {
|
|
SCTP_INP_READ_UNLOCK(stcb->sctp_ep);
|
|
r_unlocked = 1;
|
|
}
|
|
if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
|
|
/*
|
|
* One last check before we allow the guy possibly
|
|
* to get in. There is a race, where the guy has not
|
|
* reached the gate. In that case
|
|
*/
|
|
goto out;
|
|
}
|
|
SCTP_TCB_LOCK(stcb);
|
|
if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
|
|
/* No reports here */
|
|
SCTP_TCB_UNLOCK(stcb);
|
|
goto out;
|
|
}
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_USER_RECV_SACKS,
|
|
stcb->asoc.my_rwnd,
|
|
stcb->asoc.my_last_reported_rwnd,
|
|
stcb->freed_by_sorcv_sincelast,
|
|
dif);
|
|
#endif
|
|
SCTP_STAT_INCR(sctps_wu_sacks_sent);
|
|
sctp_send_sack(stcb);
|
|
sctp_chunk_output(stcb->sctp_ep, stcb,
|
|
SCTP_OUTPUT_FROM_USR_RCVD);
|
|
/* make sure no timer is running */
|
|
sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL);
|
|
SCTP_TCB_UNLOCK(stcb);
|
|
} else {
|
|
/* Update how much we have pending */
|
|
stcb->freed_by_sorcv_sincelast = dif;
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_USER_RECV_SACKS,
|
|
stcb->asoc.my_rwnd,
|
|
stcb->asoc.my_last_reported_rwnd,
|
|
stcb->freed_by_sorcv_sincelast,
|
|
0);
|
|
#endif
|
|
}
|
|
out:
|
|
if (so && r_unlocked && hold_rlock) {
|
|
SCTP_STAT_INCR(sctps_locks_in_rcv);
|
|
SCTP_INP_READ_LOCK(stcb->sctp_ep);
|
|
}
|
|
SCTP_INP_DECR_REF(stcb->sctp_ep);
|
|
no_lock:
|
|
atomic_add_int(&stcb->asoc.refcnt, -1);
|
|
return;
|
|
}
|
|
|
|
int
|
|
sctp_sorecvmsg(struct socket *so,
|
|
struct uio *uio,
|
|
struct mbuf **mp,
|
|
struct sockaddr *from,
|
|
int fromlen,
|
|
int *msg_flags,
|
|
struct sctp_sndrcvinfo *sinfo,
|
|
int filling_sinfo)
|
|
{
|
|
/*
|
|
* MSG flags we will look at MSG_DONTWAIT - non-blocking IO.
|
|
* MSG_PEEK - Look don't touch :-D (only valid with OUT mbuf copy
|
|
* mp=NULL thus uio is the copy method to userland) MSG_WAITALL - ??
|
|
* On the way out we may send out any combination of:
|
|
* MSG_NOTIFICATION MSG_EOR
|
|
*
|
|
*/
|
|
struct sctp_inpcb *inp = NULL;
|
|
int my_len = 0;
|
|
int cp_len = 0, error = 0;
|
|
struct sctp_queued_to_read *control = NULL, *ctl = NULL, *nxt = NULL;
|
|
struct mbuf *m = NULL, *embuf = NULL;
|
|
struct sctp_tcb *stcb = NULL;
|
|
int wakeup_read_socket = 0;
|
|
int freecnt_applied = 0;
|
|
int out_flags = 0, in_flags = 0;
|
|
int block_allowed = 1;
|
|
int freed_so_far = 0;
|
|
int copied_so_far = 0;
|
|
int s, in_eeor_mode = 0;
|
|
int no_rcv_needed = 0;
|
|
uint32_t rwnd_req = 0;
|
|
int hold_sblock = 0;
|
|
int hold_rlock = 0;
|
|
int alen = 0, slen = 0;
|
|
int held_length = 0;
|
|
|
|
if (msg_flags) {
|
|
in_flags = *msg_flags;
|
|
} else {
|
|
in_flags = 0;
|
|
}
|
|
slen = uio->uio_resid;
|
|
/* Pull in and set up our int flags */
|
|
if (in_flags & MSG_OOB) {
|
|
/* Out of band's NOT supported */
|
|
return (EOPNOTSUPP);
|
|
}
|
|
if ((in_flags & MSG_PEEK) && (mp != NULL)) {
|
|
return (EINVAL);
|
|
}
|
|
if ((in_flags & (MSG_DONTWAIT
|
|
| MSG_NBIO
|
|
)) ||
|
|
(so->so_state & SS_NBIO)) {
|
|
block_allowed = 0;
|
|
}
|
|
/* setup the endpoint */
|
|
inp = (struct sctp_inpcb *)so->so_pcb;
|
|
if (inp == NULL) {
|
|
return (EFAULT);
|
|
}
|
|
s = splnet();
|
|
rwnd_req = (so->so_rcv.sb_hiwat >> SCTP_RWND_HIWAT_SHIFT);
|
|
/* Must be at least a MTU's worth */
|
|
if (rwnd_req < SCTP_MIN_RWND)
|
|
rwnd_req = SCTP_MIN_RWND;
|
|
in_eeor_mode = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR);
|
|
#ifdef SCTP_RECV_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_SORECV_ENTER,
|
|
rwnd_req, in_eeor_mode, so->so_rcv.sb_cc, uio->uio_resid);
|
|
#endif
|
|
SOCKBUF_LOCK(&so->so_rcv);
|
|
hold_sblock = 1;
|
|
#ifdef SCTP_RECV_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_SORECV_ENTERPL,
|
|
rwnd_req, block_allowed, so->so_rcv.sb_cc, uio->uio_resid);
|
|
#endif
|
|
|
|
|
|
error = sblock(&so->so_rcv, (block_allowed ? M_WAITOK : 0));
|
|
if (error) {
|
|
goto release_unlocked;
|
|
}
|
|
restart:
|
|
if (hold_sblock == 0) {
|
|
SOCKBUF_LOCK(&so->so_rcv);
|
|
hold_sblock = 1;
|
|
}
|
|
sbunlock(&so->so_rcv);
|
|
|
|
restart_nosblocks:
|
|
if (hold_sblock == 0) {
|
|
SOCKBUF_LOCK(&so->so_rcv);
|
|
hold_sblock = 1;
|
|
}
|
|
if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
|
|
(inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
|
|
goto out;
|
|
}
|
|
if (so->so_error || so->so_rcv.sb_state & SBS_CANTRCVMORE) {
|
|
if (so->so_error) {
|
|
error = so->so_error;
|
|
} else {
|
|
error = ENOTCONN;
|
|
}
|
|
goto out;
|
|
}
|
|
if ((so->so_rcv.sb_cc <= held_length) && block_allowed) {
|
|
/* we need to wait for data */
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_SORECV_BLOCKSA,
|
|
0, 0, so->so_rcv.sb_cc, uio->uio_resid);
|
|
#endif
|
|
if ((so->so_rcv.sb_cc == 0) &&
|
|
((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
|
|
(inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL))) {
|
|
if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) == 0) {
|
|
/*
|
|
* For active open side clear flags for
|
|
* re-use passive open is blocked by
|
|
* connect.
|
|
*/
|
|
if (inp->sctp_flags & SCTP_PCB_FLAGS_WAS_ABORTED) {
|
|
/*
|
|
* You were aborted, passive side
|
|
* always hits here
|
|
*/
|
|
error = ECONNRESET;
|
|
/*
|
|
* You get this once if you are
|
|
* active open side
|
|
*/
|
|
if (!(inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
|
|
/*
|
|
* Remove flag if on the
|
|
* active open side
|
|
*/
|
|
inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAS_ABORTED;
|
|
}
|
|
}
|
|
so->so_state &= ~(SS_ISCONNECTING |
|
|
SS_ISDISCONNECTING |
|
|
SS_ISCONFIRMING |
|
|
SS_ISCONNECTED);
|
|
if (error == 0) {
|
|
if ((inp->sctp_flags & SCTP_PCB_FLAGS_WAS_CONNECTED) == 0) {
|
|
error = ENOTCONN;
|
|
} else {
|
|
inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAS_CONNECTED;
|
|
}
|
|
}
|
|
goto out;
|
|
}
|
|
}
|
|
error = sbwait(&so->so_rcv);
|
|
if (error) {
|
|
goto out;
|
|
}
|
|
held_length = 0;
|
|
goto restart_nosblocks;
|
|
} else if (so->so_rcv.sb_cc == 0) {
|
|
error = EWOULDBLOCK;
|
|
goto out;
|
|
}
|
|
error = sblock(&so->so_rcv, (block_allowed ? M_WAITOK : 0));
|
|
/* we possibly have data we can read */
|
|
control = TAILQ_FIRST(&inp->read_queue);
|
|
if (control == NULL) {
|
|
/*
|
|
* This could be happening since the appender did the
|
|
* increment but as not yet did the tailq insert onto the
|
|
* read_queue
|
|
*/
|
|
if (hold_rlock == 0) {
|
|
SCTP_INP_READ_LOCK(inp);
|
|
hold_rlock = 1;
|
|
}
|
|
control = TAILQ_FIRST(&inp->read_queue);
|
|
if ((control == NULL) && (so->so_rcv.sb_cc != 0)) {
|
|
#ifdef INVARIENTS
|
|
panic("Huh, its non zero and nothing on control?");
|
|
#endif
|
|
so->so_rcv.sb_cc = 0;
|
|
}
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
hold_rlock = 0;
|
|
goto restart;
|
|
}
|
|
if ((control->length == 0) &&
|
|
(control->do_not_ref_stcb)) {
|
|
/*
|
|
* Clean up code for freeing assoc that left behind a
|
|
* pdapi.. maybe a peer in EEOR that just closed after
|
|
* sending and never indicated a EOR.
|
|
*/
|
|
SCTP_STAT_INCR(sctps_locks_in_rcva);
|
|
if (hold_rlock == 0) {
|
|
hold_rlock = 1;
|
|
SCTP_INP_READ_LOCK(inp);
|
|
}
|
|
control->held_length = 0;
|
|
if (control->data) {
|
|
/* Hmm there is data here .. fix */
|
|
struct mbuf *m;
|
|
int cnt = 0;
|
|
|
|
m = control->data;
|
|
while (m) {
|
|
cnt += m->m_len;
|
|
if (m->m_next == NULL) {
|
|
control->tail_mbuf = m;
|
|
m->m_flags |= M_EOR;
|
|
control->end_added = 1;
|
|
}
|
|
m = m->m_next;
|
|
}
|
|
control->length = cnt;
|
|
} else {
|
|
/* remove it */
|
|
TAILQ_REMOVE(&inp->read_queue, control, next);
|
|
/* Add back any hiddend data */
|
|
sctp_free_remote_addr(control->whoFrom);
|
|
sctp_free_a_readq(stcb, control);
|
|
}
|
|
if (hold_rlock) {
|
|
hold_rlock = 0;
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
}
|
|
goto restart;
|
|
}
|
|
if (control->length == 0) {
|
|
if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_FRAG_INTERLEAVE)) &&
|
|
(filling_sinfo)) {
|
|
/* find a more suitable one then this */
|
|
ctl = TAILQ_NEXT(control, next);
|
|
while (ctl) {
|
|
if ((ctl->stcb != control->stcb) && (ctl->length)) {
|
|
/* found one */
|
|
control = ctl;
|
|
goto found_one;
|
|
}
|
|
ctl = TAILQ_NEXT(ctl, next);
|
|
}
|
|
}
|
|
/*
|
|
* if we reach here, not suitable replacement is available
|
|
* <or> fragment interleave is NOT on. So stuff the sb_cc
|
|
* into the our held count, and its time to sleep again.
|
|
*/
|
|
held_length = so->so_rcv.sb_cc;
|
|
control->held_length = so->so_rcv.sb_cc;
|
|
goto restart;
|
|
}
|
|
/* Clear the held length since there is something to read */
|
|
control->held_length = 0;
|
|
if (hold_rlock) {
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
hold_rlock = 0;
|
|
}
|
|
found_one:
|
|
/*
|
|
* If we reach here, control has a some data for us to read off.
|
|
* Note that stcb COULD be NULL.
|
|
*/
|
|
if (hold_sblock) {
|
|
SOCKBUF_UNLOCK(&so->so_rcv);
|
|
hold_sblock = 0;
|
|
}
|
|
stcb = control->stcb;
|
|
if (stcb) {
|
|
if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) &&
|
|
(control->do_not_ref_stcb == 0)) {
|
|
if (freecnt_applied == 0)
|
|
stcb = NULL;
|
|
} else if (control->do_not_ref_stcb == 0) {
|
|
/* you can't free it on me please */
|
|
/*
|
|
* The lock on the socket buffer protects us so the
|
|
* free code will stop. But since we used the
|
|
* socketbuf lock and the sender uses the tcb_lock
|
|
* to increment, we need to use the atomic add to
|
|
* the refcnt
|
|
*/
|
|
atomic_add_int(&stcb->asoc.refcnt, 1);
|
|
freecnt_applied = 1;
|
|
/*
|
|
* Setup to remember how much we have not yet told
|
|
* the peer our rwnd has opened up. Note we grab the
|
|
* value from the tcb from last time. Note too that
|
|
* sack sending clears this when a sack is sent..
|
|
* which is fine. Once we hit the rwnd_req, we then
|
|
* will go to the sctp_user_rcvd() that will not
|
|
* lock until it KNOWs it MUST send a WUP-SACK.
|
|
*
|
|
*/
|
|
freed_so_far = stcb->freed_by_sorcv_sincelast;
|
|
stcb->freed_by_sorcv_sincelast = 0;
|
|
}
|
|
}
|
|
/* First lets get off the sinfo and sockaddr info */
|
|
if ((sinfo) && filling_sinfo) {
|
|
memcpy(sinfo, control, sizeof(struct sctp_nonpad_sndrcvinfo));
|
|
nxt = TAILQ_NEXT(control, next);
|
|
if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXT_RCVINFO)) {
|
|
struct sctp_extrcvinfo *s_extra;
|
|
|
|
s_extra = (struct sctp_extrcvinfo *)sinfo;
|
|
if (nxt) {
|
|
s_extra->next_flags = SCTP_NEXT_MSG_AVAIL;
|
|
if (nxt->sinfo_flags & SCTP_UNORDERED) {
|
|
s_extra->next_flags |= SCTP_NEXT_MSG_IS_UNORDERED;
|
|
}
|
|
s_extra->next_asocid = nxt->sinfo_assoc_id;
|
|
s_extra->next_length = nxt->length;
|
|
s_extra->next_ppid = nxt->sinfo_ppid;
|
|
s_extra->next_stream = nxt->sinfo_stream;
|
|
if (nxt->tail_mbuf != NULL) {
|
|
if (nxt->tail_mbuf->m_flags & M_EOR) {
|
|
s_extra->next_flags |= SCTP_NEXT_MSG_ISCOMPLETE;
|
|
}
|
|
}
|
|
} else {
|
|
/*
|
|
* we explicitly 0 this, since the memcpy
|
|
* got some other things beyond the older
|
|
* sinfo_ that is on the control's structure
|
|
* :-D
|
|
*/
|
|
s_extra->next_flags = SCTP_NO_NEXT_MSG;
|
|
s_extra->next_asocid = 0;
|
|
s_extra->next_length = 0;
|
|
s_extra->next_ppid = 0;
|
|
s_extra->next_stream = 0;
|
|
}
|
|
}
|
|
/*
|
|
* update off the real current cum-ack, if we have an stcb.
|
|
*/
|
|
if (stcb)
|
|
sinfo->sinfo_cumtsn = stcb->asoc.cumulative_tsn;
|
|
/*
|
|
* mask off the high bits, we keep the actual chunk bits in
|
|
* there.
|
|
*/
|
|
sinfo->sinfo_flags &= 0x00ff;
|
|
}
|
|
if (fromlen && from) {
|
|
struct sockaddr *to;
|
|
|
|
#ifdef AF_INET
|
|
cp_len = min(fromlen, control->whoFrom->ro._l_addr.sin.sin_len);
|
|
memcpy(from, &control->whoFrom->ro._l_addr, cp_len);
|
|
((struct sockaddr_in *)from)->sin_port = control->port_from;
|
|
#else
|
|
/* No AF_INET use AF_INET6 */
|
|
cp_len = min(fromlen, control->whoFrom->ro._l_addr.sin6.sin6_len);
|
|
memcpy(from, &control->whoFrom->ro._l_addr, cp_len);
|
|
((struct sockaddr_in6 *)from)->sin6_port = control->port_from;
|
|
#endif
|
|
|
|
to = from;
|
|
#if defined(AF_INET) && defined(AF_INET6)
|
|
if ((inp->sctp_flags & SCTP_PCB_FLAGS_NEEDS_MAPPED_V4) &&
|
|
(to->sa_family == AF_INET) &&
|
|
((size_t)fromlen >= sizeof(struct sockaddr_in6))) {
|
|
struct sockaddr_in *sin;
|
|
struct sockaddr_in6 sin6;
|
|
|
|
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;
|
|
memcpy(from, (caddr_t)&sin6, sizeof(sin6));
|
|
}
|
|
#endif
|
|
#if defined(AF_INET6)
|
|
{
|
|
struct sockaddr_in6 lsa6, *to6;
|
|
|
|
to6 = (struct sockaddr_in6 *)to;
|
|
sctp_recover_scope_mac(to6, (&lsa6));
|
|
|
|
}
|
|
#endif
|
|
}
|
|
/* now copy out what data we can */
|
|
if (mp == NULL) {
|
|
/* copy out each mbuf in the chain up to length */
|
|
get_more_data:
|
|
m = control->data;
|
|
while (m) {
|
|
/* Move out all we can */
|
|
cp_len = (int)uio->uio_resid;
|
|
my_len = (int)m->m_len;
|
|
if (cp_len > my_len) {
|
|
/* not enough in this buf */
|
|
cp_len = my_len;
|
|
}
|
|
if (hold_rlock) {
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
hold_rlock = 0;
|
|
}
|
|
splx(s);
|
|
if (cp_len > 0)
|
|
error = uiomove(mtod(m, char *), cp_len, uio);
|
|
s = splnet();
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_SORCV_DOESCPY,
|
|
so->so_rcv.sb_cc,
|
|
cp_len,
|
|
0,
|
|
0);
|
|
#endif
|
|
/* re-read */
|
|
if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) {
|
|
goto release;
|
|
}
|
|
if (stcb &&
|
|
stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
|
|
no_rcv_needed = 1;
|
|
}
|
|
if (error) {
|
|
/* error we are out of here */
|
|
goto release;
|
|
}
|
|
if ((m->m_next == NULL) &&
|
|
(cp_len >= m->m_len) &&
|
|
((control->end_added == 0) ||
|
|
(control->end_added && (TAILQ_NEXT(control, next) == NULL)))
|
|
) {
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_SORCV_DOESLCK,
|
|
so->so_rcv.sb_cc,
|
|
cp_len,
|
|
m->m_len,
|
|
control->length);
|
|
#endif
|
|
SCTP_STAT_INCR(sctps_locks_in_rcvb);
|
|
SCTP_INP_READ_LOCK(inp);
|
|
hold_rlock = 1;
|
|
}
|
|
if (cp_len == m->m_len) {
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_SORCV_DOESADJ,
|
|
so->so_rcv.sb_cc,
|
|
control->length,
|
|
cp_len,
|
|
0);
|
|
#endif
|
|
if (m->m_flags & M_EOR) {
|
|
out_flags |= MSG_EOR;
|
|
}
|
|
if (m->m_flags & M_NOTIFICATION) {
|
|
out_flags |= MSG_NOTIFICATION;
|
|
}
|
|
/* we ate up the mbuf */
|
|
if (in_flags & MSG_PEEK) {
|
|
/* just looking */
|
|
m = m->m_next;
|
|
copied_so_far += cp_len;
|
|
} else {
|
|
/* dispose of the mbuf */
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&so->so_rcv,
|
|
control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBFREE, m->m_len);
|
|
#endif
|
|
sctp_sbfree(control, stcb, &so->so_rcv, m);
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&so->so_rcv,
|
|
control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0);
|
|
#endif
|
|
embuf = m;
|
|
copied_so_far += cp_len;
|
|
freed_so_far += cp_len;
|
|
alen = atomic_fetchadd_int(&control->length, -(cp_len));
|
|
if (alen < cp_len) {
|
|
panic("Control length goes negative?");
|
|
}
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_SORCV_PASSBF,
|
|
so->so_rcv.sb_cc,
|
|
control->length,
|
|
0,
|
|
0);
|
|
#endif
|
|
control->data = sctp_m_free(m);
|
|
m = control->data;
|
|
/*
|
|
* been through it all, must hold sb
|
|
* lock ok to null tail
|
|
*/
|
|
if (control->data == NULL) {
|
|
#ifdef INVARIENTS
|
|
if ((control->end_added == 0) ||
|
|
(TAILQ_NEXT(control, next) == NULL)) {
|
|
/*
|
|
* If the end is not
|
|
* added, OR the
|
|
* next is NOT null
|
|
* we MUST have the
|
|
* lock.
|
|
*/
|
|
if (mtx_owned(&inp->inp_rdata_mtx) == 0) {
|
|
panic("Hmm we don't own the lock?");
|
|
}
|
|
}
|
|
#endif
|
|
control->tail_mbuf = NULL;
|
|
#ifdef INVARIENTS
|
|
if ((control->end_added) && ((out_flags & MSG_EOR) == 0)) {
|
|
panic("end_added, nothing left and no MSG_EOR");
|
|
}
|
|
#endif
|
|
}
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_SORCV_ADJD,
|
|
so->so_rcv.sb_cc,
|
|
control->length,
|
|
0,
|
|
0);
|
|
#endif
|
|
}
|
|
} else {
|
|
/* Do we need to trim the mbuf? */
|
|
if (m->m_flags & M_NOTIFICATION) {
|
|
out_flags |= MSG_NOTIFICATION;
|
|
}
|
|
if ((in_flags & MSG_PEEK) == 0) {
|
|
if (out_flags & MSG_NOTIFICATION) {
|
|
/*
|
|
* remark this one with the
|
|
* notify flag, they read
|
|
* only part of the
|
|
* notification.
|
|
*/
|
|
m->m_flags |= M_NOTIFICATION;
|
|
}
|
|
m->m_data += cp_len;
|
|
m->m_len -= cp_len;
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBFREE, cp_len);
|
|
#endif
|
|
atomic_subtract_int(&so->so_rcv.sb_cc, cp_len);
|
|
if (stcb) {
|
|
atomic_subtract_int(&stcb->asoc.sb_cc, cp_len);
|
|
}
|
|
copied_so_far += cp_len;
|
|
embuf = m;
|
|
freed_so_far += cp_len;
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&so->so_rcv, control->do_not_ref_stcb ? NULL : stcb,
|
|
SCTP_LOG_SBRESULT, 0);
|
|
#endif
|
|
alen = atomic_fetchadd_int(&control->length, -(cp_len));
|
|
if (alen < cp_len) {
|
|
panic("Control length goes negative2?");
|
|
}
|
|
} else {
|
|
copied_so_far += cp_len;
|
|
}
|
|
}
|
|
if ((out_flags & MSG_EOR) ||
|
|
(uio->uio_resid == 0)
|
|
) {
|
|
break;
|
|
}
|
|
if (((stcb) && (in_flags & MSG_PEEK) == 0) &&
|
|
(control->do_not_ref_stcb == 0) &&
|
|
(freed_so_far >= rwnd_req)) {
|
|
sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req);
|
|
}
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_SORCV_BOTWHILE,
|
|
so->so_rcv.sb_cc,
|
|
control->length,
|
|
0,
|
|
0);
|
|
#endif
|
|
|
|
} /* end while(m) */
|
|
/*
|
|
* At this point we have looked at it all and we either have
|
|
* a MSG_EOR/or read all the user wants... <OR>
|
|
* control->length == 0.
|
|
*/
|
|
if ((out_flags & MSG_EOR) &&
|
|
((in_flags & MSG_PEEK) == 0)) {
|
|
/* we are done with this control */
|
|
if (control->length == 0) {
|
|
if (control->data) {
|
|
#ifdef INVARIENTS
|
|
panic("control->data not null at read eor?");
|
|
#else
|
|
printf("Strange, data left in the control buffer .. invarients would panic?\n");
|
|
sctp_m_freem(control->data);
|
|
control->data = NULL;
|
|
#endif
|
|
}
|
|
done_with_control:
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
sctp_misc_ints(SCTP_SORCV_FREECTL,
|
|
so->so_rcv.sb_cc,
|
|
0,
|
|
0,
|
|
0);
|
|
#endif
|
|
if (TAILQ_NEXT(control, next) == NULL) {
|
|
/*
|
|
* If we don't have a next we need a
|
|
* lock, if there is a next interupt
|
|
* is filling ahead of us and we
|
|
* don't need a lock to remove this
|
|
* guy (which is the head of the
|
|
* queue).
|
|
*/
|
|
if (hold_rlock == 0) {
|
|
SCTP_STAT_INCR(sctps_locks_in_rcvc);
|
|
SCTP_INP_READ_LOCK(inp);
|
|
hold_rlock = 1;
|
|
}
|
|
}
|
|
TAILQ_REMOVE(&inp->read_queue, control, next);
|
|
/* Add back any hiddend data */
|
|
if (control->held_length) {
|
|
held_length = 0;
|
|
control->held_length = 0;
|
|
wakeup_read_socket = 1;
|
|
}
|
|
no_rcv_needed = control->do_not_ref_stcb;
|
|
sctp_free_remote_addr(control->whoFrom);
|
|
control->data = NULL;
|
|
sctp_free_a_readq(stcb, control);
|
|
control = NULL;
|
|
if ((freed_so_far >= rwnd_req) && (no_rcv_needed == 0))
|
|
sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req);
|
|
|
|
} else {
|
|
/*
|
|
* The user did not read all of this
|
|
* message, turn off the returned MSG_EOR
|
|
* since we are leaving more behind on the
|
|
* control to read.
|
|
*/
|
|
#ifdef INVARIENTS
|
|
if (control->end_added && (control->data == NULL) &&
|
|
(control->tail_mbuf == NULL)) {
|
|
panic("Gak, control->length is corrupt?");
|
|
}
|
|
#endif
|
|
no_rcv_needed = control->do_not_ref_stcb;
|
|
out_flags &= ~MSG_EOR;
|
|
}
|
|
}
|
|
if (out_flags & MSG_EOR) {
|
|
goto release;
|
|
}
|
|
if ((uio->uio_resid == 0) ||
|
|
((in_eeor_mode) && (copied_so_far >= max(so->so_rcv.sb_lowat, 1)))
|
|
) {
|
|
goto release;
|
|
}
|
|
/*
|
|
* If I hit here the receiver wants more and this message is
|
|
* NOT done (pd-api). So two questions. Can we block? if not
|
|
* we are done. Did the user NOT set MSG_WAITALL?
|
|
*/
|
|
if (block_allowed == 0) {
|
|
goto release;
|
|
}
|
|
/*
|
|
* We need to wait for more data a few things: - We don't
|
|
* sbunlock() so we don't get someone else reading. - We
|
|
* must be sure to account for the case where what is added
|
|
* is NOT to our control when we wakeup.
|
|
*/
|
|
|
|
/*
|
|
* Do we need to tell the transport a rwnd update might be
|
|
* needed before we go to sleep?
|
|
*/
|
|
if (((stcb) && (in_flags & MSG_PEEK) == 0) &&
|
|
((freed_so_far >= rwnd_req) &&
|
|
(control->do_not_ref_stcb == 0) &&
|
|
(no_rcv_needed == 0))) {
|
|
sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req);
|
|
}
|
|
wait_some_more:
|
|
if (so->so_error || so->so_rcv.sb_state & SBS_CANTRCVMORE) {
|
|
goto release;
|
|
}
|
|
if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)
|
|
goto release;
|
|
|
|
if (hold_rlock == 1) {
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
hold_rlock = 0;
|
|
}
|
|
if (hold_sblock == 0) {
|
|
SOCKBUF_LOCK(&so->so_rcv);
|
|
hold_sblock = 1;
|
|
}
|
|
#ifdef SCTP_RECV_DETAIL_RWND_LOGGING
|
|
if (stcb)
|
|
sctp_misc_ints(SCTP_SORECV_BLOCKSB,
|
|
freed_so_far,
|
|
stcb->asoc.my_rwnd,
|
|
so->so_rcv.sb_cc,
|
|
uio->uio_resid);
|
|
else
|
|
sctp_misc_ints(SCTP_SORECV_BLOCKSB,
|
|
freed_so_far,
|
|
0,
|
|
so->so_rcv.sb_cc,
|
|
uio->uio_resid);
|
|
#endif
|
|
if (so->so_rcv.sb_cc <= control->held_length) {
|
|
error = sbwait(&so->so_rcv);
|
|
if (error) {
|
|
goto release;
|
|
}
|
|
control->held_length = 0;
|
|
}
|
|
if (hold_sblock) {
|
|
SOCKBUF_UNLOCK(&so->so_rcv);
|
|
hold_sblock = 0;
|
|
}
|
|
if (control->length == 0) {
|
|
/* still nothing here */
|
|
if (control->end_added == 1) {
|
|
/* he aborted, or is done i.e.did a shutdown */
|
|
out_flags |= MSG_EOR;
|
|
if (control->pdapi_aborted)
|
|
out_flags |= MSG_TRUNC;
|
|
goto done_with_control;
|
|
}
|
|
if (so->so_rcv.sb_cc > held_length) {
|
|
SCTP_STAT_INCR(sctps_locks_in_rcvf);
|
|
control->held_length = so->so_rcv.sb_cc;
|
|
held_length = 0;
|
|
}
|
|
goto wait_some_more;
|
|
} else if (control->data == NULL) {
|
|
/*
|
|
* we must re-sync since data is probably being
|
|
* added
|
|
*/
|
|
SCTP_INP_READ_LOCK(inp);
|
|
if ((control->length > 0) && (control->data == NULL)) {
|
|
/*
|
|
* big trouble.. we have the lock and its
|
|
* corrupt?
|
|
*/
|
|
panic("Impossible data==NULL length !=0");
|
|
}
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
/* We will fall around to get more data */
|
|
}
|
|
goto get_more_data;
|
|
} else {
|
|
/* copy out the mbuf chain */
|
|
get_more_data2:
|
|
/*
|
|
* Do we have a uio, I doubt it if so we grab the size from
|
|
* it, if not you get it all
|
|
*/
|
|
if (uio)
|
|
cp_len = uio->uio_resid;
|
|
else
|
|
cp_len = control->length;
|
|
|
|
if ((uint32_t) cp_len >= control->length) {
|
|
/* easy way */
|
|
if ((control->end_added == 0) ||
|
|
(TAILQ_NEXT(control, next) == NULL)) {
|
|
/* Need to get rlock */
|
|
if (hold_rlock == 0) {
|
|
SCTP_INP_READ_LOCK(inp);
|
|
hold_rlock = 1;
|
|
}
|
|
}
|
|
if (control->tail_mbuf->m_flags & M_EOR) {
|
|
out_flags |= MSG_EOR;
|
|
}
|
|
if (control->data->m_flags & M_NOTIFICATION) {
|
|
out_flags |= MSG_NOTIFICATION;
|
|
}
|
|
if (uio)
|
|
uio->uio_resid -= control->length;
|
|
*mp = control->data;
|
|
m = control->data;
|
|
while (m) {
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&so->so_rcv,
|
|
control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBFREE, m->m_len);
|
|
#endif
|
|
sctp_sbfree(control, stcb, &so->so_rcv, m);
|
|
freed_so_far += m->m_len;
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&so->so_rcv,
|
|
control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0);
|
|
#endif
|
|
m = m->m_next;
|
|
}
|
|
control->data = control->tail_mbuf = NULL;
|
|
control->length = 0;
|
|
if (out_flags & MSG_EOR) {
|
|
/* Done with this control */
|
|
goto done_with_control;
|
|
}
|
|
/* still more to do with this conntrol */
|
|
/* do we really support msg_waitall here? */
|
|
if ((block_allowed == 0) ||
|
|
((in_flags & MSG_WAITALL) == 0)) {
|
|
goto release;
|
|
}
|
|
wait_some_more2:
|
|
if (so->so_error || so->so_rcv.sb_state & SBS_CANTRCVMORE)
|
|
goto release;
|
|
if (hold_rlock == 1) {
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
hold_rlock = 0;
|
|
}
|
|
if (hold_sblock == 0) {
|
|
SOCKBUF_LOCK(&so->so_rcv);
|
|
hold_sblock = 1;
|
|
}
|
|
if (so->so_rcv.sb_cc <= control->held_length) {
|
|
error = sbwait(&so->so_rcv);
|
|
if (error) {
|
|
goto release;
|
|
}
|
|
}
|
|
if (hold_sblock) {
|
|
SOCKBUF_UNLOCK(&so->so_rcv);
|
|
hold_sblock = 0;
|
|
}
|
|
if (control->length == 0) {
|
|
/* still nothing here */
|
|
if (control->end_added == 1) {
|
|
/*
|
|
* he aborted, or is done i.e.
|
|
* shutdown
|
|
*/
|
|
out_flags |= MSG_EOR;
|
|
if (control->pdapi_aborted)
|
|
out_flags |= MSG_TRUNC;
|
|
goto done_with_control;
|
|
}
|
|
if (so->so_rcv.sb_cc > held_length) {
|
|
control->held_length = so->so_rcv.sb_cc;
|
|
/*
|
|
* We don't use held_length while
|
|
* getting a message
|
|
*/
|
|
held_length = 0;
|
|
}
|
|
goto wait_some_more2;
|
|
}
|
|
goto get_more_data2;
|
|
} else {
|
|
/* hard way mbuf by mbuf */
|
|
m = control->data;
|
|
if (control->end_added == 0) {
|
|
/* need the rlock */
|
|
if (hold_rlock == 0) {
|
|
SCTP_INP_READ_LOCK(inp);
|
|
hold_rlock = 1;
|
|
}
|
|
}
|
|
if (m->m_flags & M_NOTIFICATION) {
|
|
out_flags |= MSG_NOTIFICATION;
|
|
}
|
|
while ((m) && (cp_len > 0)) {
|
|
if (cp_len >= m->m_len) {
|
|
*mp = m;
|
|
atomic_subtract_int(&control->length, m->m_len);
|
|
if (uio)
|
|
uio->uio_resid -= m->m_len;
|
|
cp_len -= m->m_len;
|
|
control->data = m->m_next;
|
|
m->m_next = NULL;
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&so->so_rcv,
|
|
control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBFREE, m->m_len);
|
|
#endif
|
|
sctp_sbfree(control, stcb, &so->so_rcv, m);
|
|
freed_so_far += m->m_len;
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&so->so_rcv,
|
|
control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBRESULT, 0);
|
|
#endif
|
|
mp = &m->m_next;
|
|
m = control->data;
|
|
} else {
|
|
/*
|
|
* got all he wants and its part of
|
|
* this mbuf only.
|
|
*/
|
|
if (uio)
|
|
uio->uio_resid -= m->m_len;
|
|
cp_len -= m->m_len;
|
|
if (hold_rlock) {
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
hold_rlock = 0;
|
|
}
|
|
if (hold_sblock) {
|
|
SOCKBUF_UNLOCK(&so->so_rcv);
|
|
hold_sblock = 0;
|
|
}
|
|
splx(s);
|
|
*mp = sctp_m_copym(m, 0, cp_len,
|
|
M_TRYWAIT
|
|
);
|
|
s = splnet();
|
|
#ifdef SCTP_LOCK_LOGGING
|
|
sctp_log_lock(inp, stcb, SCTP_LOG_LOCK_SOCKBUF_R);
|
|
#endif
|
|
if (hold_sblock == 0) {
|
|
SOCKBUF_LOCK(&so->so_rcv);
|
|
hold_sblock = 1;
|
|
}
|
|
if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)
|
|
goto release;
|
|
|
|
if (stcb &&
|
|
stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
|
|
no_rcv_needed = 1;
|
|
}
|
|
m->m_data += cp_len;
|
|
m->m_len -= cp_len;
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&so->so_rcv, control->do_not_ref_stcb ? NULL : stcb, SCTP_LOG_SBFREE, cp_len);
|
|
#endif
|
|
freed_so_far += cp_len;
|
|
atomic_subtract_int(&so->so_rcv.sb_cc, cp_len);
|
|
if (stcb) {
|
|
atomic_subtract_int(&stcb->asoc.sb_cc, cp_len);
|
|
if ((freed_so_far >= rwnd_req) &&
|
|
(control->do_not_ref_stcb == 0) &&
|
|
(no_rcv_needed == 0))
|
|
sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req);
|
|
}
|
|
#ifdef SCTP_SB_LOGGING
|
|
sctp_sblog(&so->so_rcv, control->do_not_ref_stcb ? NULL : stcb,
|
|
SCTP_LOG_SBRESULT, 0);
|
|
#endif
|
|
if (out_flags & MSG_NOTIFICATION) {
|
|
/*
|
|
* remark the first mbuf if
|
|
* they took a partial read.
|
|
*/
|
|
control->data->m_flags |= M_NOTIFICATION;
|
|
}
|
|
goto release;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
release:
|
|
if (hold_rlock == 1) {
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
hold_rlock = 0;
|
|
}
|
|
if (hold_sblock == 0) {
|
|
SOCKBUF_LOCK(&so->so_rcv);
|
|
hold_sblock = 1;
|
|
}
|
|
sbunlock(&so->so_rcv);
|
|
|
|
release_unlocked:
|
|
if (hold_sblock) {
|
|
SOCKBUF_UNLOCK(&so->so_rcv);
|
|
hold_sblock = 0;
|
|
}
|
|
if ((stcb) && (in_flags & MSG_PEEK) == 0) {
|
|
if ((freed_so_far >= rwnd_req) &&
|
|
(control && (control->do_not_ref_stcb == 0)) &&
|
|
(no_rcv_needed == 0))
|
|
sctp_user_rcvd(stcb, &freed_so_far, hold_rlock, rwnd_req);
|
|
}
|
|
if (msg_flags)
|
|
*msg_flags |= out_flags;
|
|
out:
|
|
if (hold_rlock == 1) {
|
|
SCTP_INP_READ_UNLOCK(inp);
|
|
hold_rlock = 0;
|
|
}
|
|
if (hold_sblock) {
|
|
SOCKBUF_UNLOCK(&so->so_rcv);
|
|
hold_sblock = 0;
|
|
}
|
|
if (freecnt_applied) {
|
|
/*
|
|
* The lock on the socket buffer protects us so the free
|
|
* code will stop. But since we used the socketbuf lock and
|
|
* the sender uses the tcb_lock to increment, we need to use
|
|
* the atomic add to the refcnt.
|
|
*/
|
|
if (stcb == NULL) {
|
|
panic("stcb for refcnt has gone NULL?");
|
|
}
|
|
atomic_add_int(&stcb->asoc.refcnt, -1);
|
|
freecnt_applied = 0;
|
|
/* Save the value back for next time */
|
|
stcb->freed_by_sorcv_sincelast = freed_so_far;
|
|
}
|
|
splx(s);
|
|
#ifdef SCTP_RECV_RWND_LOGGING
|
|
if (stcb) {
|
|
sctp_misc_ints(SCTP_SORECV_DONE,
|
|
freed_so_far,
|
|
((uio) ? (slen - uio->uio_resid) : slen),
|
|
stcb->asoc.my_rwnd,
|
|
so->so_rcv.sb_cc);
|
|
} else {
|
|
sctp_misc_ints(SCTP_SORECV_DONE,
|
|
freed_so_far,
|
|
((uio) ? (slen - uio->uio_resid) : slen),
|
|
0,
|
|
so->so_rcv.sb_cc);
|
|
}
|
|
#endif
|
|
if (wakeup_read_socket) {
|
|
sctp_sorwakeup(inp, so);
|
|
}
|
|
return (error);
|
|
}
|
|
|
|
|
|
#ifdef SCTP_MBUF_LOGGING
|
|
struct mbuf *
|
|
sctp_m_free(struct mbuf *m)
|
|
{
|
|
if (m->m_flags & M_EXT) {
|
|
sctp_log_mb(m, SCTP_MBUF_IFREE);
|
|
}
|
|
return (m_free(m));
|
|
}
|
|
|
|
void
|
|
sctp_m_freem(struct mbuf *mb)
|
|
{
|
|
while (mb != NULL)
|
|
mb = sctp_m_free(mb);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
int
|
|
sctp_soreceive(so, psa, uio, mp0, controlp, flagsp)
|
|
struct socket *so;
|
|
struct sockaddr **psa;
|
|
struct uio *uio;
|
|
struct mbuf **mp0;
|
|
struct mbuf **controlp;
|
|
int *flagsp;
|
|
{
|
|
int error, fromlen;
|
|
uint8_t sockbuf[256];
|
|
struct sockaddr *from;
|
|
struct sctp_extrcvinfo sinfo;
|
|
int filling_sinfo = 1;
|
|
struct sctp_inpcb *inp;
|
|
|
|
inp = (struct sctp_inpcb *)so->so_pcb;
|
|
/* pickup the assoc we are reading from */
|
|
if (inp == NULL) {
|
|
return (EINVAL);
|
|
}
|
|
if ((sctp_is_feature_off(inp,
|
|
SCTP_PCB_FLAGS_RECVDATAIOEVNT)) ||
|
|
(controlp == NULL)) {
|
|
/* user does not want the sndrcv ctl */
|
|
filling_sinfo = 0;
|
|
}
|
|
if (psa) {
|
|
from = (struct sockaddr *)sockbuf;
|
|
fromlen = sizeof(sockbuf);
|
|
from->sa_len = 0;
|
|
} else {
|
|
from = NULL;
|
|
fromlen = 0;
|
|
}
|
|
|
|
error = sctp_sorecvmsg(so, uio, mp0, from, fromlen, flagsp,
|
|
(struct sctp_sndrcvinfo *)&sinfo, filling_sinfo);
|
|
if ((controlp) && (filling_sinfo)) {
|
|
/* copy back the sinfo in a CMSG format */
|
|
if (filling_sinfo)
|
|
*controlp = sctp_build_ctl_nchunk(inp,
|
|
(struct sctp_sndrcvinfo *)&sinfo);
|
|
else
|
|
*controlp = NULL;
|
|
}
|
|
if (psa) {
|
|
/* copy back the address info */
|
|
if (from && from->sa_len) {
|
|
*psa = sodupsockaddr(from, M_NOWAIT);
|
|
} else {
|
|
*psa = NULL;
|
|
}
|
|
}
|
|
return (error);
|
|
}
|