int
sctp_lower_sosend(struct socket *so,
struct sockaddr *addr,
struct uio *uio,
struct mbuf *i_pak,
struct mbuf *control,
int flags,
struct sctp_sndrcvinfo *srcv
#if !defined(__Userspace__)
,
#if defined(__FreeBSD__)
struct thread *p
#elif defined(_WIN32)
PKTHREAD p
#else
struct proc *p
#endif
#endif
)
{
#if defined(__FreeBSD__) && !defined(__Userspace__)
struct epoch_tracker et;
#endif
ssize_t sndlen = 0, max_len, local_add_more;
int error, len;
struct mbuf *top = NULL;
int queue_only = 0, queue_only_for_init = 0;
int free_cnt_applied = 0;
int un_sent;
int now_filled = 0;
unsigned int inqueue_bytes = 0;
struct sctp_block_entry be;
struct sctp_inpcb *inp;
struct sctp_tcb *stcb = NULL;
struct timeval now;
struct sctp_nets *net;
struct sctp_association *asoc;
struct sctp_inpcb *t_inp;
int user_marks_eor;
int create_lock_applied = 0;
int nagle_applies = 0;
int some_on_control = 0;
int got_all_of_the_send = 0;
int hold_tcblock = 0;
int non_blocking = 0;
ssize_t local_soresv = 0;
uint16_t port;
uint16_t sinfo_flags;
sctp_assoc_t sinfo_assoc_id;
error = 0;
net = NULL;
stcb = NULL;
asoc = NULL;
#if defined(__APPLE__) && !defined(__Userspace__)
sctp_lock_assert(so);
#endif
t_inp = inp = (struct sctp_inpcb *)so->so_pcb;
if (inp == NULL) {
SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
if (i_pak) {
SCTP_RELEASE_PKT(i_pak);
}
return (error);
}
if ((uio == NULL) && (i_pak == NULL)) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
return (EINVAL);
}
user_marks_eor = sctp_is_feature_on(inp, SCTP_PCB_FLAGS_EXPLICIT_EOR);
atomic_add_int(&inp->total_sends, 1);
if (uio) {
#if defined(__APPLE__) && !defined(__Userspace__)
#if defined(APPLE_LEOPARD)
if (uio->uio_resid < 0) {
#else
if (uio_resid(uio) < 0) {
#endif
#else
if (uio->uio_resid < 0) {
#endif
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
return (EINVAL);
}
#if defined(__APPLE__) && !defined(__Userspace__)
#if defined(APPLE_LEOPARD)
sndlen = uio->uio_resid;
#else
sndlen = uio_resid(uio);
#endif
#else
sndlen = uio->uio_resid;
#endif
} else {
top = SCTP_HEADER_TO_CHAIN(i_pak);
sndlen = SCTP_HEADER_LEN(i_pak);
}
SCTPDBG(SCTP_DEBUG_OUTPUT1, "Send called addr:%p send length %zd\n",
(void *)addr,
sndlen);
if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) &&
SCTP_IS_LISTENING(inp)) {
/* The listener can NOT send */
SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, ENOTCONN);
error = ENOTCONN;
goto out_unlocked;
}
/**
* Pre-screen address, if one is given the sin-len
* must be set correctly!
*/
if (addr) {
union sctp_sockstore *raddr = (union sctp_sockstore *)addr;
switch (raddr->sa.sa_family) {
#ifdef INET
case AF_INET:
#ifdef HAVE_SIN_LEN
if (raddr->sin.sin_len != sizeof(struct sockaddr_in)) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out_unlocked;
}
#endif
port = raddr->sin.sin_port;
break;
#endif
#ifdef INET6
case AF_INET6:
#ifdef HAVE_SIN6_LEN
if (raddr->sin6.sin6_len != sizeof(struct sockaddr_in6)) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out_unlocked;
}
#endif
port = raddr->sin6.sin6_port;
break;
#endif
#if defined(__Userspace__)
case AF_CONN:
#ifdef HAVE_SCONN_LEN
if (raddr->sconn.sconn_len != sizeof(struct sockaddr_conn)) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out_unlocked;
}
#endif
port = raddr->sconn.sconn_port;
break;
#endif
default:
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EAFNOSUPPORT);
error = EAFNOSUPPORT;
goto out_unlocked;
}
} else
port = 0;
if (srcv) {
sinfo_flags = srcv->sinfo_flags;
sinfo_assoc_id = srcv->sinfo_assoc_id;
if (INVALID_SINFO_FLAG(sinfo_flags) ||
PR_SCTP_INVALID_POLICY(sinfo_flags)) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out_unlocked;
}
if (srcv->sinfo_flags)
SCTP_STAT_INCR(sctps_sends_with_flags);
} else {
sinfo_flags = inp->def_send.sinfo_flags;
sinfo_assoc_id = inp->def_send.sinfo_assoc_id;
}
#if defined(__FreeBSD__) && !defined(__Userspace__)
if (flags & MSG_EOR) {
sinfo_flags |= SCTP_EOR;
}
if (flags & MSG_EOF) {
sinfo_flags |= SCTP_EOF;
}
#endif
if (sinfo_flags & SCTP_SENDALL) {
/* its a sendall */
error = sctp_sendall(inp, uio, top, srcv);
top = NULL;
goto out_unlocked;
}
if ((sinfo_flags & SCTP_ADDR_OVER) && (addr == NULL)) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out_unlocked;
}
/* now we must find the assoc */
if ((inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) ||
(inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) {
SCTP_INP_RLOCK(inp);
stcb = LIST_FIRST(&inp->sctp_asoc_list);
if (stcb) {
SCTP_TCB_LOCK(stcb);
hold_tcblock = 1;
}
SCTP_INP_RUNLOCK(inp);
} else if (sinfo_assoc_id) {
stcb = sctp_findassociation_ep_asocid(inp, sinfo_assoc_id, 1);
if (stcb != NULL) {
hold_tcblock = 1;
}
} else if (addr) {
/*-
* Since we did not use findep we must
* increment it, and if we don't find a tcb
* decrement it.
*/
SCTP_INP_WLOCK(inp);
SCTP_INP_INCR_REF(inp);
SCTP_INP_WUNLOCK(inp);
stcb = sctp_findassociation_ep_addr(&t_inp, addr, &net, NULL, NULL);
if (stcb == NULL) {
SCTP_INP_WLOCK(inp);
SCTP_INP_DECR_REF(inp);
SCTP_INP_WUNLOCK(inp);
} else {
hold_tcblock = 1;
}
}
if ((stcb == NULL) && (addr)) {
/* Possible implicit send? */
SCTP_ASOC_CREATE_LOCK(inp);
create_lock_applied = 1;
if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) ||
(inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE)) {
/* Should I really unlock ? */
SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out_unlocked;
}
if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) &&
(addr->sa_family == AF_INET6)) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out_unlocked;
}
SCTP_INP_WLOCK(inp);
SCTP_INP_INCR_REF(inp);
SCTP_INP_WUNLOCK(inp);
/* With the lock applied look again */
stcb = sctp_findassociation_ep_addr(&t_inp, addr, &net, NULL, NULL);
#if defined(INET) || defined(INET6)
if ((stcb == NULL) && (control != NULL) && (port > 0)) {
stcb = sctp_findassociation_cmsgs(&t_inp, port, control, &net, &error);
}
#endif
if (stcb == NULL) {
SCTP_INP_WLOCK(inp);
SCTP_INP_DECR_REF(inp);
SCTP_INP_WUNLOCK(inp);
} else {
hold_tcblock = 1;
}
if (error) {
goto out_unlocked;
}
if (t_inp != inp) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, ENOTCONN);
error = ENOTCONN;
goto out_unlocked;
}
}
if (stcb == NULL) {
if (addr == NULL) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, ENOENT);
error = ENOENT;
goto out_unlocked;
} else {
/* We must go ahead and start the INIT process */
uint32_t vrf_id;
if ((sinfo_flags & SCTP_ABORT) ||
((sinfo_flags & SCTP_EOF) && (sndlen == 0))) {
/*-
* User asks to abort a non-existant assoc,
* or EOF a non-existant assoc with no data
*/
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, ENOENT);
error = ENOENT;
goto out_unlocked;
}
/* get an asoc/stcb struct */
vrf_id = inp->def_vrf_id;
#ifdef INVARIANTS
if (create_lock_applied == 0) {
panic("Error, should hold create lock and I don't?");
}
#endif
stcb = sctp_aloc_assoc(inp, addr, &error, 0, 0, vrf_id,
inp->sctp_ep.pre_open_stream_count,
inp->sctp_ep.port,
#if !defined(__Userspace__)
p,
#else
(struct proc *)NULL,
#endif
SCTP_INITIALIZE_AUTH_PARAMS);
if (stcb == NULL) {
/* Error is setup for us in the call */
goto out_unlocked;
}
if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) {
stcb->sctp_ep->sctp_flags |= SCTP_PCB_FLAGS_CONNECTED;
/* Set the connected flag so we can queue data */
soisconnecting(so);
}
hold_tcblock = 1;
if (create_lock_applied) {
SCTP_ASOC_CREATE_UNLOCK(inp);
create_lock_applied = 0;
} else {
SCTP_PRINTF("Huh-3? create lock should have been on??\n");
}
/* Turn on queue only flag to prevent data from being sent */
queue_only = 1;
asoc = &stcb->asoc;
SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
(void)SCTP_GETTIME_TIMEVAL(&asoc->time_entered);
if (control) {
if (sctp_process_cmsgs_for_init(stcb, control, &error)) {
sctp_free_assoc(inp, stcb, SCTP_PCBFREE_FORCE,
SCTP_FROM_SCTP_OUTPUT + SCTP_LOC_6);
hold_tcblock = 0;
stcb = NULL;
goto out_unlocked;
}
}
/* out with the INIT */
queue_only_for_init = 1;
/*-
* we may want to dig in after this call and adjust the MTU
* value. It defaulted to 1500 (constant) but the ro
* structure may now have an update and thus we may need to
* change it BEFORE we append the message.
*/
}
} else
asoc = &stcb->asoc;
if (srcv == NULL) {
srcv = (struct sctp_sndrcvinfo *)&asoc->def_send;
sinfo_flags = srcv->sinfo_flags;
#if defined(__FreeBSD__) && !defined(__Userspace__)
if (flags & MSG_EOR) {
sinfo_flags |= SCTP_EOR;
}
if (flags & MSG_EOF) {
sinfo_flags |= SCTP_EOF;
}
#endif
}
if (sinfo_flags & SCTP_ADDR_OVER) {
if (addr)
net = sctp_findnet(stcb, addr);
else
net = NULL;
if ((net == NULL) ||
((port != 0) && (port != stcb->rport))) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out_unlocked;
}
} else {
if (stcb->asoc.alternate) {
net = stcb->asoc.alternate;
} else {
net = stcb->asoc.primary_destination;
}
}
atomic_add_int(&stcb->total_sends, 1);
/* Keep the stcb from being freed under our feet */
atomic_add_int(&asoc->refcnt, 1);
free_cnt_applied = 1;
if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_NO_FRAGMENT)) {
if (sndlen > (ssize_t)asoc->smallest_mtu) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EMSGSIZE);
error = EMSGSIZE;
goto out_unlocked;
}
}
#if defined(__Userspace__)
if (inp->recv_callback) {
non_blocking = 1;
}
#endif
if (SCTP_SO_IS_NBIO(so)
#if defined(__FreeBSD__) && !defined(__Userspace__)
|| (flags & (MSG_NBIO | MSG_DONTWAIT)) != 0
#endif
) {
non_blocking = 1;
}
/* would we block? */
if (non_blocking) {
ssize_t amount;
if (hold_tcblock == 0) {
SCTP_TCB_LOCK(stcb);
hold_tcblock = 1;
}
inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
if (user_marks_eor == 0) {
amount = sndlen;
} else {
amount = 1;
}
if ((SCTP_SB_LIMIT_SND(so) < (amount + inqueue_bytes + stcb->asoc.sb_send_resv)) ||
(stcb->asoc.chunks_on_out_queue >= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue))) {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EWOULDBLOCK);
if (sndlen > (ssize_t)SCTP_SB_LIMIT_SND(so))
error = EMSGSIZE;
else
error = EWOULDBLOCK;
goto out_unlocked;
}
stcb->asoc.sb_send_resv += (uint32_t)sndlen;
SCTP_TCB_UNLOCK(stcb);
hold_tcblock = 0;
} else {
atomic_add_int(&stcb->asoc.sb_send_resv, sndlen);
}
local_soresv = sndlen;
if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ECONNRESET);
error = ECONNRESET;
goto out_unlocked;
}
if (create_lock_applied) {
SCTP_ASOC_CREATE_UNLOCK(inp);
create_lock_applied = 0;
}
/* Is the stream no. valid? */
if (srcv->sinfo_stream >= asoc->streamoutcnt) {
/* Invalid stream number */
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out_unlocked;
}
if ((asoc->strmout[srcv->sinfo_stream].state != SCTP_STREAM_OPEN) &&
(asoc->strmout[srcv->sinfo_stream].state != SCTP_STREAM_OPENING)) {
/*
* Can't queue any data while stream reset is underway.
*/
if (asoc->strmout[srcv->sinfo_stream].state > SCTP_STREAM_OPEN) {
error = EAGAIN;
} else {
error = EINVAL;
}
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, error);
goto out_unlocked;
}
if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
(SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
queue_only = 1;
}
/* we are now done with all control */
if (control) {
sctp_m_freem(control);
control = NULL;
}
if ((SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_SENT) ||
(SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED) ||
(SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_ACK_SENT) ||
(asoc->state & SCTP_STATE_SHUTDOWN_PENDING)) {
if (sinfo_flags & SCTP_ABORT) {
;
} else {
SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ECONNRESET);
error = ECONNRESET;
goto out_unlocked;
}
}
/* Ok, we will attempt a msgsnd :> */
#if !(defined(_WIN32) || defined(__Userspace__))
if (p) {
#if defined(__FreeBSD__)
p->td_ru.ru_msgsnd++;
#else
p->p_stats->p_ru.ru_msgsnd++;
#endif
}
#endif
/* Are we aborting? */
if (sinfo_flags & SCTP_ABORT) {
struct mbuf *mm;
ssize_t tot_demand, tot_out = 0, max_out;
SCTP_STAT_INCR(sctps_sends_with_abort);
if ((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
(SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) {
/* It has to be up before we abort */
/* how big is the user initiated abort? */
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out;
}
if (hold_tcblock) {
SCTP_TCB_UNLOCK(stcb);
hold_tcblock = 0;
}
if (top) {
struct mbuf *cntm = NULL;
mm = sctp_get_mbuf_for_msg(sizeof(struct sctp_paramhdr), 0, M_WAITOK, 1, MT_DATA);
if (sndlen != 0) {
for (cntm = top; cntm; cntm = SCTP_BUF_NEXT(cntm)) {
tot_out += SCTP_BUF_LEN(cntm);
}
}
} else {
/* Must fit in a MTU */
tot_out = sndlen;
tot_demand = (tot_out + sizeof(struct sctp_paramhdr));
if (tot_demand > SCTP_DEFAULT_ADD_MORE) {
/* To big */
SCTP_LTRACE_ERR_RET(NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, EMSGSIZE);
error = EMSGSIZE;
goto out;
}
mm = sctp_get_mbuf_for_msg((unsigned int)tot_demand, 0, M_WAITOK, 1, MT_DATA);
}
if (mm == NULL) {
SCTP_LTRACE_ERR_RET(NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, ENOMEM);
error = ENOMEM;
goto out;
}
max_out = asoc->smallest_mtu - sizeof(struct sctp_paramhdr);
max_out -= sizeof(struct sctp_abort_msg);
if (tot_out > max_out) {
tot_out = max_out;
}
if (mm) {
struct sctp_paramhdr *ph;
/* now move forward the data pointer */
ph = mtod(mm, struct sctp_paramhdr *);
ph->param_type = htons(SCTP_CAUSE_USER_INITIATED_ABT);
ph->param_length = htons((uint16_t)(sizeof(struct sctp_paramhdr) + tot_out));
ph++;
SCTP_BUF_LEN(mm) = (int)(tot_out + sizeof(struct sctp_paramhdr));
if (top == NULL) {
#if defined(__APPLE__) && !defined(__Userspace__)
SCTP_SOCKET_UNLOCK(so, 0);
#endif
error = uiomove((caddr_t)ph, (int)tot_out, uio);
#if defined(__APPLE__) && !defined(__Userspace__)
SCTP_SOCKET_LOCK(so, 0);
#endif
if (error) {
/*-
* Here if we can't get his data we
* still abort we just don't get to
* send the users note :-0
*/
sctp_m_freem(mm);
mm = NULL;
}
} else {
if (sndlen != 0) {
SCTP_BUF_NEXT(mm) = top;
}
}
}
if (hold_tcblock == 0) {
SCTP_TCB_LOCK(stcb);
}
atomic_add_int(&stcb->asoc.refcnt, -1);
free_cnt_applied = 0;
/* release this lock, otherwise we hang on ourselves */
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_ENTER(et);
#endif
sctp_abort_an_association(stcb->sctp_ep, stcb, mm, false, SCTP_SO_LOCKED);
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_EXIT(et);
#endif
/* now relock the stcb so everything is sane */
hold_tcblock = 0;
stcb = NULL;
/* In this case top is already chained to mm
* avoid double free, since we free it below if
* top != NULL and driver would free it after sending
* the packet out
*/
if (sndlen != 0) {
top = NULL;
}
goto out_unlocked;
}
/* Calculate the maximum we can send */
inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
if (SCTP_SB_LIMIT_SND(so) > inqueue_bytes) {
max_len = SCTP_SB_LIMIT_SND(so) - inqueue_bytes;
} else {
max_len = 0;
}
if (hold_tcblock) {
SCTP_TCB_UNLOCK(stcb);
hold_tcblock = 0;
}
if (asoc->strmout == NULL) {
/* huh? software error */
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EFAULT);
error = EFAULT;
goto out_unlocked;
}
/* Unless E_EOR mode is on, we must make a send FIT in one call. */
if ((user_marks_eor == 0) &&
(sndlen > (ssize_t)SCTP_SB_LIMIT_SND(stcb->sctp_socket))) {
/* It will NEVER fit */
SCTP_LTRACE_ERR_RET(NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, EMSGSIZE);
error = EMSGSIZE;
goto out_unlocked;
}
if ((uio == NULL) && user_marks_eor) {
/*-
* We do not support eeor mode for
* sending with mbuf chains (like sendfile).
*/
SCTP_LTRACE_ERR_RET(NULL, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out_unlocked;
}
if (user_marks_eor) {
local_add_more = (ssize_t)min(SCTP_SB_LIMIT_SND(so), SCTP_BASE_SYSCTL(sctp_add_more_threshold));
} else {
/*-
* For non-eeor the whole message must fit in
* the socket send buffer.
*/
local_add_more = sndlen;
}
len = 0;
if (non_blocking) {
goto skip_preblock;
}
if (((max_len <= local_add_more) &&
((ssize_t)SCTP_SB_LIMIT_SND(so) >= local_add_more)) ||
(max_len == 0) ||
((stcb->asoc.chunks_on_out_queue+stcb->asoc.stream_queue_cnt) >= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue))) {
/* No room right now ! */
SOCKBUF_LOCK(&so->so_snd);
inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
while ((SCTP_SB_LIMIT_SND(so) < (inqueue_bytes + local_add_more)) ||
((stcb->asoc.stream_queue_cnt + stcb->asoc.chunks_on_out_queue) >= SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue))) {
SCTPDBG(SCTP_DEBUG_OUTPUT1,"pre_block limit:%u <(inq:%d + %zd) || (%d+%d > %d)\n",
(unsigned int)SCTP_SB_LIMIT_SND(so),
inqueue_bytes,
local_add_more,
stcb->asoc.stream_queue_cnt,
stcb->asoc.chunks_on_out_queue,
SCTP_BASE_SYSCTL(sctp_max_chunks_on_queue));
if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
sctp_log_block(SCTP_BLOCK_LOG_INTO_BLKA, asoc, sndlen);
}
be.error = 0;
#if !(defined(_WIN32) && !defined(__Userspace__))
stcb->block_entry = &be;
#endif
error = sbwait(&so->so_snd);
stcb->block_entry = NULL;
if (error || so->so_error || be.error) {
if (error == 0) {
if (so->so_error)
error = so->so_error;
if (be.error) {
error = be.error;
}
}
SOCKBUF_UNLOCK(&so->so_snd);
goto out_unlocked;
}
if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
sctp_log_block(SCTP_BLOCK_LOG_OUTOF_BLK,
asoc, stcb->asoc.total_output_queue_size);
}
if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
SOCKBUF_UNLOCK(&so->so_snd);
goto out_unlocked;
}
inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
}
if (SCTP_SB_LIMIT_SND(so) > inqueue_bytes) {
max_len = SCTP_SB_LIMIT_SND(so) - inqueue_bytes;
} else {
max_len = 0;
}
SOCKBUF_UNLOCK(&so->so_snd);
}
skip_preblock:
if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
goto out_unlocked;
}
#if defined(__APPLE__) && !defined(__Userspace__)
error = sblock(&so->so_snd, SBLOCKWAIT(flags));
#endif
/* sndlen covers for mbuf case
* uio_resid covers for the non-mbuf case
* NOTE: uio will be null when top/mbuf is passed
*/
if (sndlen == 0) {
if (sinfo_flags & SCTP_EOF) {
got_all_of_the_send = 1;
goto dataless_eof;
} else {
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out;
}
}
if (top == NULL) {
struct sctp_stream_queue_pending *sp;
struct sctp_stream_out *strm;
uint32_t sndout;
SCTP_TCB_SEND_LOCK(stcb);
if ((asoc->stream_locked) &&
(asoc->stream_locked_on != srcv->sinfo_stream)) {
SCTP_TCB_SEND_UNLOCK(stcb);
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out;
}
strm = &stcb->asoc.strmout[srcv->sinfo_stream];
if (strm->last_msg_incomplete == 0) {
do_a_copy_in:
SCTP_TCB_SEND_UNLOCK(stcb);
sp = sctp_copy_it_in(stcb, asoc, srcv, uio, net, max_len, user_marks_eor, &error);
if (error) {
goto out;
}
SCTP_TCB_SEND_LOCK(stcb);
if (sp->msg_is_complete) {
strm->last_msg_incomplete = 0;
asoc->stream_locked = 0;
} else {
/* Just got locked to this guy in
* case of an interrupt.
*/
strm->last_msg_incomplete = 1;
if (stcb->asoc.idata_supported == 0) {
asoc->stream_locked = 1;
asoc->stream_locked_on = srcv->sinfo_stream;
}
sp->sender_all_done = 0;
}
sctp_snd_sb_alloc(stcb, sp->length);
atomic_add_int(&asoc->stream_queue_cnt, 1);
if (sinfo_flags & SCTP_UNORDERED) {
SCTP_STAT_INCR(sctps_sends_with_unord);
}
sp->processing = 1;
TAILQ_INSERT_TAIL(&strm->outqueue, sp, next);
stcb->asoc.ss_functions.sctp_ss_add_to_stream(stcb, asoc, strm, sp, 1);
} else {
sp = TAILQ_LAST(&strm->outqueue, sctp_streamhead);
if (sp == NULL) {
/* ???? Huh ??? last msg is gone */
#ifdef INVARIANTS
panic("Warning: Last msg marked incomplete, yet nothing left?");
#else
SCTP_PRINTF("Warning: Last msg marked incomplete, yet nothing left?\n");
strm->last_msg_incomplete = 0;
#endif
goto do_a_copy_in;
}
if (sp->processing) {
SCTP_TCB_SEND_UNLOCK(stcb);
SCTP_LTRACE_ERR_RET(inp, stcb, net, SCTP_FROM_SCTP_OUTPUT, EINVAL);
error = EINVAL;
goto out;
} else {
sp->processing = 1;
}
}
SCTP_TCB_SEND_UNLOCK(stcb);
#if defined(__APPLE__) && !defined(__Userspace__)
#if defined(APPLE_LEOPARD)
while (uio->uio_resid > 0) {
#else
while (uio_resid(uio) > 0) {
#endif
#else
while (uio->uio_resid > 0) {
#endif
/* How much room do we have? */
struct mbuf *new_tail, *mm;
inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
if (SCTP_SB_LIMIT_SND(so) > inqueue_bytes)
max_len = SCTP_SB_LIMIT_SND(so) - inqueue_bytes;
else
max_len = 0;
if ((max_len > (ssize_t)SCTP_BASE_SYSCTL(sctp_add_more_threshold)) ||
(max_len && (SCTP_SB_LIMIT_SND(so) < SCTP_BASE_SYSCTL(sctp_add_more_threshold))) ||
#if defined(__APPLE__) && !defined(__Userspace__)
#if defined(APPLE_LEOPARD)
(uio->uio_resid && (uio->uio_resid <= max_len))) {
#else
(uio_resid(uio) && (uio_resid(uio) <= max_len))) {
#endif
#else
(uio->uio_resid && (uio->uio_resid <= max_len))) {
#endif
sndout = 0;
new_tail = NULL;
if (hold_tcblock) {
SCTP_TCB_UNLOCK(stcb);
hold_tcblock = 0;
}
#if defined(__APPLE__) && !defined(__Userspace__)
SCTP_SOCKET_UNLOCK(so, 0);
#endif
#if defined(__FreeBSD__) || defined(__Userspace__)
mm = sctp_copy_resume(uio, (int)max_len, user_marks_eor, &error, &sndout, &new_tail);
#else
mm = sctp_copy_resume(uio, (int)max_len, &error, &sndout, &new_tail);
#endif
#if defined(__APPLE__) && !defined(__Userspace__)
SCTP_SOCKET_LOCK(so, 0);
#endif
if ((mm == NULL) || error) {
if (mm) {
sctp_m_freem(mm);
}
SCTP_TCB_SEND_LOCK(stcb);
if (((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0) &&
((stcb->asoc.state & SCTP_STATE_WAS_ABORTED) == 0) &&
(sp != NULL)) {
sp->processing = 0;
}
SCTP_TCB_SEND_UNLOCK(stcb);
goto out;
}
/* Update the mbuf and count */
SCTP_TCB_SEND_LOCK(stcb);
if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) ||
(stcb->asoc.state & SCTP_STATE_WAS_ABORTED)) {
/* we need to get out.
* Peer probably aborted.
*/
sctp_m_freem(mm);
if (stcb->asoc.state & SCTP_STATE_WAS_ABORTED) {
SCTP_LTRACE_ERR_RET(NULL, stcb, NULL, SCTP_FROM_SCTP_OUTPUT, ECONNRESET);
error = ECONNRESET;
}
SCTP_TCB_SEND_UNLOCK(stcb);
goto out;
}
if (sp->tail_mbuf) {
/* tack it to the end */
SCTP_BUF_NEXT(sp->tail_mbuf) = mm;
sp->tail_mbuf = new_tail;
} else {
/* A stolen mbuf */
sp->data = mm;
sp->tail_mbuf = new_tail;
}
sctp_snd_sb_alloc(stcb, sndout);
atomic_add_int(&sp->length, sndout);
len += sndout;
if (sinfo_flags & SCTP_SACK_IMMEDIATELY) {
sp->sinfo_flags |= SCTP_SACK_IMMEDIATELY;
}
/* Did we reach EOR? */
#if defined(__APPLE__) && !defined(__Userspace__)
#if defined(APPLE_LEOPARD)
if ((uio->uio_resid == 0) &&
#else
if ((uio_resid(uio) == 0) &&
#endif
#else
if ((uio->uio_resid == 0) &&
#endif
((user_marks_eor == 0) ||
(sinfo_flags & SCTP_EOF) ||
(user_marks_eor && (sinfo_flags & SCTP_EOR)))) {
sp->msg_is_complete = 1;
} else {
sp->msg_is_complete = 0;
}
SCTP_TCB_SEND_UNLOCK(stcb);
}
#if defined(__APPLE__) && !defined(__Userspace__)
#if defined(APPLE_LEOPARD)
if (uio->uio_resid == 0) {
#else
if (uio_resid(uio) == 0) {
#endif
#else
if (uio->uio_resid == 0) {
#endif
/* got it all? */
continue;
}
/* PR-SCTP? */
if ((asoc->prsctp_supported) && (asoc->sent_queue_cnt_removeable > 0)) {
/* This is ugly but we must assure locking order */
if (hold_tcblock == 0) {
SCTP_TCB_LOCK(stcb);
hold_tcblock = 1;
}
sctp_prune_prsctp(stcb, asoc, srcv, (int)sndlen);
inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
if (SCTP_SB_LIMIT_SND(so) > inqueue_bytes)
max_len = SCTP_SB_LIMIT_SND(so) - inqueue_bytes;
else
max_len = 0;
if (max_len > 0) {
continue;
}
SCTP_TCB_UNLOCK(stcb);
hold_tcblock = 0;
}
/* wait for space now */
if (non_blocking) {
/* Non-blocking io in place out */
SCTP_TCB_SEND_LOCK(stcb);
if (sp != NULL) {
sp->processing = 0;
}
SCTP_TCB_SEND_UNLOCK(stcb);
goto skip_out_eof;
}
/* What about the INIT, send it maybe */
if (queue_only_for_init) {
if (hold_tcblock == 0) {
SCTP_TCB_LOCK(stcb);
hold_tcblock = 1;
}
if (SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
/* a collision took us forward? */
queue_only = 0;
} else {
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_ENTER(et);
#endif
sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_EXIT(et);
#endif
SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
queue_only = 1;
}
}
if ((net->flight_size > net->cwnd) &&
(asoc->sctp_cmt_on_off == 0)) {
SCTP_STAT_INCR(sctps_send_cwnd_avoid);
queue_only = 1;
} else if (asoc->ifp_had_enobuf) {
SCTP_STAT_INCR(sctps_ifnomemqueued);
if (net->flight_size > (2 * net->mtu)) {
queue_only = 1;
}
asoc->ifp_had_enobuf = 0;
}
un_sent = stcb->asoc.total_output_queue_size - stcb->asoc.total_flight;
if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) &&
(stcb->asoc.total_flight > 0) &&
(stcb->asoc.stream_queue_cnt < SCTP_MAX_DATA_BUNDLING) &&
(un_sent < (int)(stcb->asoc.smallest_mtu - SCTP_MIN_OVERHEAD))) {
/*-
* Ok, Nagle is set on and we have data outstanding.
* Don't send anything and let SACKs drive out the
* data unless we have a "full" segment to send.
*/
if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_NAGLE_LOGGING_ENABLE) {
sctp_log_nagle_event(stcb, SCTP_NAGLE_APPLIED);
}
SCTP_STAT_INCR(sctps_naglequeued);
nagle_applies = 1;
} else {
if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_NAGLE_LOGGING_ENABLE) {
if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY))
sctp_log_nagle_event(stcb, SCTP_NAGLE_SKIPPED);
}
SCTP_STAT_INCR(sctps_naglesent);
nagle_applies = 0;
}
if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
sctp_misc_ints(SCTP_CWNDLOG_PRESEND, queue_only_for_init, queue_only,
nagle_applies, un_sent);
sctp_misc_ints(SCTP_CWNDLOG_PRESEND, stcb->asoc.total_output_queue_size,
stcb->asoc.total_flight,
stcb->asoc.chunks_on_out_queue, stcb->asoc.total_flight_count);
}
if (queue_only_for_init)
queue_only_for_init = 0;
if ((queue_only == 0) && (nagle_applies == 0)) {
/*-
* need to start chunk output
* before blocking.. note that if
* a lock is already applied, then
* the input via the net is happening
* and I don't need to start output :-D
*/
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_ENTER(et);
#endif
if (hold_tcblock == 0) {
if (SCTP_TCB_TRYLOCK(stcb)) {
hold_tcblock = 1;
sctp_chunk_output(inp,
stcb,
SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
}
} else {
sctp_chunk_output(inp,
stcb,
SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
}
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_EXIT(et);
#endif
}
if (hold_tcblock == 1) {
SCTP_TCB_UNLOCK(stcb);
hold_tcblock = 0;
}
SOCKBUF_LOCK(&so->so_snd);
/*-
* This is a bit strange, but I think it will
* work. The total_output_queue_size is locked and
* protected by the TCB_LOCK, which we just released.
* There is a race that can occur between releasing it
* above, and me getting the socket lock, where sacks
* come in but we have not put the SB_WAIT on the
* so_snd buffer to get the wakeup. After the LOCK
* is applied the sack_processing will also need to
* LOCK the so->so_snd to do the actual sowwakeup(). So
* once we have the socket buffer lock if we recheck the
* size we KNOW we will get to sleep safely with the
* wakeup flag in place.
*/
inqueue_bytes = stcb->asoc.total_output_queue_size - (stcb->asoc.chunks_on_out_queue * SCTP_DATA_CHUNK_OVERHEAD(stcb));
if (SCTP_SB_LIMIT_SND(so) <= (inqueue_bytes +
min(SCTP_BASE_SYSCTL(sctp_add_more_threshold), SCTP_SB_LIMIT_SND(so)))) {
if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
#if defined(__APPLE__) && !defined(__Userspace__)
#if defined(APPLE_LEOPARD)
sctp_log_block(SCTP_BLOCK_LOG_INTO_BLK,
asoc, uio->uio_resid);
#else
sctp_log_block(SCTP_BLOCK_LOG_INTO_BLK,
asoc, uio_resid(uio));
#endif
#else
sctp_log_block(SCTP_BLOCK_LOG_INTO_BLK,
asoc, uio->uio_resid);
#endif
}
be.error = 0;
#if !(defined(_WIN32) && !defined(__Userspace__))
stcb->block_entry = &be;
#endif
#if defined(__APPLE__) && !defined(__Userspace__)
sbunlock(&so->so_snd, 1);
#endif
error = sbwait(&so->so_snd);
stcb->block_entry = NULL;
if (error || so->so_error || be.error) {
if (error == 0) {
if (so->so_error)
error = so->so_error;
if (be.error) {
error = be.error;
}
}
SOCKBUF_UNLOCK(&so->so_snd);
SCTP_TCB_SEND_LOCK(stcb);
if (((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0) &&
((stcb->asoc.state & SCTP_STATE_WAS_ABORTED) == 0) &&
(sp != NULL)) {
sp->processing = 0;
}
SCTP_TCB_SEND_UNLOCK(stcb);
goto out_unlocked;
}
#if defined(__APPLE__) && !defined(__Userspace__)
error = sblock(&so->so_snd, SBLOCKWAIT(flags));
#endif
if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
sctp_log_block(SCTP_BLOCK_LOG_OUTOF_BLK,
asoc, stcb->asoc.total_output_queue_size);
}
}
SOCKBUF_UNLOCK(&so->so_snd);
SCTP_TCB_SEND_LOCK(stcb);
if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) ||
(stcb->asoc.state & SCTP_STATE_WAS_ABORTED)) {
SCTP_TCB_SEND_UNLOCK(stcb);
goto out_unlocked;
}
SCTP_TCB_SEND_UNLOCK(stcb);
}
SCTP_TCB_SEND_LOCK(stcb);
if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) ||
(stcb->asoc.state & SCTP_STATE_WAS_ABORTED)) {
SCTP_TCB_SEND_UNLOCK(stcb);
goto out_unlocked;
}
if (sp) {
if (sp->msg_is_complete == 0) {
strm->last_msg_incomplete = 1;
if (stcb->asoc.idata_supported == 0) {
asoc->stream_locked = 1;
asoc->stream_locked_on = srcv->sinfo_stream;
}
} else {
sp->sender_all_done = 1;
strm->last_msg_incomplete = 0;
asoc->stream_locked = 0;
}
sp->processing = 0;
} else {
SCTP_PRINTF("Huh no sp TSNH?\n");
strm->last_msg_incomplete = 0;
asoc->stream_locked = 0;
}
SCTP_TCB_SEND_UNLOCK(stcb);
#if defined(__APPLE__) && !defined(__Userspace__)
#if defined(APPLE_LEOPARD)
if (uio->uio_resid == 0) {
#else
if (uio_resid(uio) == 0) {
#endif
#else
if (uio->uio_resid == 0) {
#endif
got_all_of_the_send = 1;
}
} else {
/* We send in a 0, since we do NOT have any locks */
error = sctp_msg_append(stcb, net, top, srcv, 0);
top = NULL;
if (sinfo_flags & SCTP_EOF) {
got_all_of_the_send = 1;
}
}
if (error) {
goto out;
}
dataless_eof:
/* EOF thing ? */
if ((sinfo_flags & SCTP_EOF) &&
(got_all_of_the_send == 1)) {
SCTP_STAT_INCR(sctps_sends_with_eof);
error = 0;
if (hold_tcblock == 0) {
SCTP_TCB_LOCK(stcb);
hold_tcblock = 1;
}
if (TAILQ_EMPTY(&asoc->send_queue) &&
TAILQ_EMPTY(&asoc->sent_queue) &&
sctp_is_there_unsent_data(stcb, SCTP_SO_LOCKED) == 0) {
if ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc)) {
goto abort_anyway;
}
/* there is nothing queued to send, so I'm done... */
if ((SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) &&
(SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
(SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
struct sctp_nets *netp;
/* only send SHUTDOWN the first time through */
if (SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
SCTP_STAT_DECR_GAUGE32(sctps_currestab);
}
SCTP_SET_STATE(stcb, SCTP_STATE_SHUTDOWN_SENT);
sctp_stop_timers_for_shutdown(stcb);
if (stcb->asoc.alternate) {
netp = stcb->asoc.alternate;
} else {
netp = stcb->asoc.primary_destination;
}
sctp_send_shutdown(stcb, netp);
sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWN, stcb->sctp_ep, stcb,
netp);
sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb,
NULL);
}
} else {
/*-
* we still got (or just got) data to send, so set
* SHUTDOWN_PENDING
*/
/*-
* XXX sockets draft says that SCTP_EOF should be
* sent with no data. currently, we will allow user
* data to be sent first and move to
* SHUTDOWN-PENDING
*/
if ((SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_SENT) &&
(SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_RECEIVED) &&
(SCTP_GET_STATE(stcb) != SCTP_STATE_SHUTDOWN_ACK_SENT)) {
if (hold_tcblock == 0) {
SCTP_TCB_LOCK(stcb);
hold_tcblock = 1;
}
if ((*asoc->ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, asoc)) {
SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_PARTIAL_MSG_LEFT);
}
SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_SHUTDOWN_PENDING);
if (TAILQ_EMPTY(&asoc->send_queue) &&
TAILQ_EMPTY(&asoc->sent_queue) &&
(asoc->state & SCTP_STATE_PARTIAL_MSG_LEFT)) {
struct mbuf *op_err;
char msg[SCTP_DIAG_INFO_LEN];
abort_anyway:
if (free_cnt_applied) {
atomic_add_int(&stcb->asoc.refcnt, -1);
free_cnt_applied = 0;
}
SCTP_SNPRINTF(msg, sizeof(msg),
"%s:%d at %s", __FILE__, __LINE__, __func__);
op_err = sctp_generate_cause(SCTP_BASE_SYSCTL(sctp_diag_info_code),
msg);
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_ENTER(et);
#endif
sctp_abort_an_association(stcb->sctp_ep, stcb,
op_err, false, SCTP_SO_LOCKED);
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_EXIT(et);
#endif
/* now relock the stcb so everything is sane */
hold_tcblock = 0;
stcb = NULL;
goto out;
}
sctp_timer_start(SCTP_TIMER_TYPE_SHUTDOWNGUARD, stcb->sctp_ep, stcb,
NULL);
sctp_feature_off(inp, SCTP_PCB_FLAGS_NODELAY);
}
}
}
skip_out_eof:
if (!TAILQ_EMPTY(&stcb->asoc.control_send_queue)) {
some_on_control = 1;
}
if (queue_only_for_init) {
if (hold_tcblock == 0) {
SCTP_TCB_LOCK(stcb);
hold_tcblock = 1;
}
if (SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
/* a collision took us forward? */
queue_only = 0;
} else {
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_ENTER(et);
#endif
sctp_send_initiate(inp, stcb, SCTP_SO_LOCKED);
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_EXIT(et);
#endif
SCTP_SET_STATE(stcb, SCTP_STATE_COOKIE_WAIT);
queue_only = 1;
}
}
if ((net->flight_size > net->cwnd) &&
(stcb->asoc.sctp_cmt_on_off == 0)) {
SCTP_STAT_INCR(sctps_send_cwnd_avoid);
queue_only = 1;
} else if (asoc->ifp_had_enobuf) {
SCTP_STAT_INCR(sctps_ifnomemqueued);
if (net->flight_size > (2 * net->mtu)) {
queue_only = 1;
}
asoc->ifp_had_enobuf = 0;
}
un_sent = stcb->asoc.total_output_queue_size - stcb->asoc.total_flight;
if ((sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY)) &&
(stcb->asoc.total_flight > 0) &&
(stcb->asoc.stream_queue_cnt < SCTP_MAX_DATA_BUNDLING) &&
(un_sent < (int)(stcb->asoc.smallest_mtu - SCTP_MIN_OVERHEAD))) {
/*-
* Ok, Nagle is set on and we have data outstanding.
* Don't send anything and let SACKs drive out the
* data unless wen have a "full" segment to send.
*/
if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_NAGLE_LOGGING_ENABLE) {
sctp_log_nagle_event(stcb, SCTP_NAGLE_APPLIED);
}
SCTP_STAT_INCR(sctps_naglequeued);
nagle_applies = 1;
} else {
if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_NAGLE_LOGGING_ENABLE) {
if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_NODELAY))
sctp_log_nagle_event(stcb, SCTP_NAGLE_SKIPPED);
}
SCTP_STAT_INCR(sctps_naglesent);
nagle_applies = 0;
}
if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_BLK_LOGGING_ENABLE) {
sctp_misc_ints(SCTP_CWNDLOG_PRESEND, queue_only_for_init, queue_only,
nagle_applies, un_sent);
sctp_misc_ints(SCTP_CWNDLOG_PRESEND, stcb->asoc.total_output_queue_size,
stcb->asoc.total_flight,
stcb->asoc.chunks_on_out_queue, stcb->asoc.total_flight_count);
}
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_ENTER(et);
#endif
if ((queue_only == 0) && (nagle_applies == 0) && (stcb->asoc.peers_rwnd && un_sent)) {
/* we can attempt to send too. */
if (hold_tcblock == 0) {
/* If there is activity recv'ing sacks no need to send */
if (SCTP_TCB_TRYLOCK(stcb)) {
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
hold_tcblock = 1;
}
} else {
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
}
} else if ((queue_only == 0) &&
(stcb->asoc.peers_rwnd == 0) &&
(stcb->asoc.total_flight == 0)) {
/* We get to have a probe outstanding */
if (hold_tcblock == 0) {
hold_tcblock = 1;
SCTP_TCB_LOCK(stcb);
}
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_USR_SEND, SCTP_SO_LOCKED);
} else if (some_on_control) {
int num_out, reason, frag_point;
/* Here we do control only */
if (hold_tcblock == 0) {
hold_tcblock = 1;
SCTP_TCB_LOCK(stcb);
}
frag_point = sctp_get_frag_point(stcb, &stcb->asoc);
(void)sctp_med_chunk_output(inp, stcb, &stcb->asoc, &num_out,
&reason, 1, 1, &now, &now_filled, frag_point, SCTP_SO_LOCKED);
}
#if defined(__FreeBSD__) && !defined(__Userspace__)
NET_EPOCH_EXIT(et);
#endif
SCTPDBG(SCTP_DEBUG_OUTPUT1, "USR Send complete qo:%d prw:%d unsent:%d tf:%d cooq:%d toqs:%d err:%d\n",
queue_only, stcb->asoc.peers_rwnd, un_sent,
stcb->asoc.total_flight, stcb->asoc.chunks_on_out_queue,
stcb->asoc.total_output_queue_size, error);
out:
#if defined(__APPLE__) && !defined(__Userspace__)
sbunlock(&so->so_snd, 1);
#endif
out_unlocked:
if (local_soresv && stcb) {
atomic_subtract_int(&stcb->asoc.sb_send_resv, sndlen);
}
if (create_lock_applied) {
SCTP_ASOC_CREATE_UNLOCK(inp);
}
if ((stcb) && hold_tcblock) {
SCTP_TCB_UNLOCK(stcb);
}
if (stcb && free_cnt_applied) {
atomic_add_int(&stcb->asoc.refcnt, -1);
}
#ifdef INVARIANTS
#if defined(__FreeBSD__) && !defined(__Userspace__)
if (stcb) {
if (mtx_owned(&stcb->tcb_mtx)) {
panic("Leaving with tcb mtx owned?");
}
if (mtx_owned(&stcb->tcb_send_mtx)) {
panic("Leaving with tcb send mtx owned?");
}
}
#endif
#endif
if (top) {
sctp_m_freem(top);
}
if (control) {
sctp_m_freem(control);
}
return (error);
}分块解释代码
最新发布