/*- * SPDX-License-Identifier: BSD-3-Clause * * Copyright (c) 2001-2008, by Cisco Systems, Inc. All rights reserved. * Copyright (c) 2008-2012, by Randall Stewart. All rights reserved. * Copyright (c) 2008-2012, by Michael Tuexen. 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.
*/
tmp_a = a->sin6_addr; if (in6_embedscope(&tmp_a, a) != 0) { return (0);
}
tmp_b = b->sin6_addr; if (in6_embedscope(&tmp_b, b) != 0) { return (0);
} return (IN6_ARE_ADDR_EQUAL(&tmp_a, &tmp_b)); #endif #else return (IN6_ARE_ADDR_EQUAL(&(a->sin6_addr), &(b->sin6_addr))); #endif/* SCTP_EMBEDDED_V6_SCOPE */
} #endif
void
sctp_fill_pcbinfo(struct sctp_pcbinfo *spcb)
{ /* * We really don't need to lock this, but I will just because it * does not hurt.
*/
SCTP_INP_INFO_RLOCK();
spcb->ep_count = SCTP_BASE_INFO(ipi_count_ep);
spcb->asoc_count = SCTP_BASE_INFO(ipi_count_asoc);
spcb->laddr_count = SCTP_BASE_INFO(ipi_count_laddr);
spcb->raddr_count = SCTP_BASE_INFO(ipi_count_raddr);
spcb->chk_count = SCTP_BASE_INFO(ipi_count_chunk);
spcb->readq_count = SCTP_BASE_INFO(ipi_count_readq);
spcb->stream_oque = SCTP_BASE_INFO(ipi_count_strmoq);
spcb->free_chunks = SCTP_BASE_INFO(ipi_free_chunks);
SCTP_INP_INFO_RUNLOCK();
}
/*- * Addresses are added to VRF's (Virtual Router's). For BSD we * have only the default VRF 0. We maintain a hash list of * VRF's. Each VRF has its own list of sctp_ifn's. Each of * these has a list of addresses. When we add a new address * to a VRF we lookup the ifn/ifn_index, if the ifn does * not exist we create it and add it to the list of IFN's * within the VRF. Once we have the sctp_ifn, we add the * address to the list. So we look something like: * * hash-vrf-table * vrf-> ifn-> ifn -> ifn * vrf | * ... +--ifa-> ifa -> ifa * vrf * * We keep these separate lists since the SCTP subsystem will * point to these from its source address selection nets structure. * When an address is deleted it does not happen right away on * the SCTP side, it gets scheduled. What we do when a * delete happens is immediately remove the address from * the master list and decrement the refcount. As our * addip iterator works through and frees the src address * selection pointing to the sctp_ifa, eventually the refcount * will reach 0 and we will delete it. Note that it is assumed * that any locking on system level ifn/ifa is done at the * caller of these functions and these routines will only * lock the SCTP structures as they add or delete things. * * Other notes on VRF concepts. * - An endpoint can be in multiple VRF's * - An association lives within a VRF and only one VRF. * - Any incoming packet we can deduce the VRF for by * looking at the mbuf/pak inbound (for BSD its VRF=0 :D) * - Any downward send call or connect call must supply the * VRF via ancillary data or via some sort of set default * VRF socket option call (again for BSD no brainer since * the VRF is always 0). * - An endpoint may add multiple VRF's to it. * - Listening sockets can accept associations in any * of the VRF's they are in but the assoc will end up * in only one VRF (gotten from the packet or connect/send). *
*/
void
sctp_free_ifa(struct sctp_ifa *sctp_ifap)
{ if (SCTP_DECREMENT_AND_CHECK_REFCOUNT(&sctp_ifap->refcount)) { /* We zero'd the count */ if (sctp_ifap->ifn_p) {
sctp_free_ifn(sctp_ifap->ifn_p);
}
SCTP_FREE(sctp_ifap, SCTP_M_IFA);
atomic_subtract_int(&SCTP_BASE_INFO(ipi_count_ifas), 1);
}
}
staticvoid
sctp_delete_ifn(struct sctp_ifn *sctp_ifnp, int hold_addr_lock)
{ struct sctp_ifn *found;
found = sctp_find_ifn(sctp_ifnp->ifn_p, sctp_ifnp->ifn_index); if (found == NULL) { /* Not in the list.. sorry */ return;
} if (hold_addr_lock == 0) {
SCTP_IPI_ADDR_WLOCK();
} else {
SCTP_IPI_ADDR_WLOCK_ASSERT();
}
LIST_REMOVE(sctp_ifnp, next_bucket);
LIST_REMOVE(sctp_ifnp, next_ifn); if (hold_addr_lock == 0) {
SCTP_IPI_ADDR_WUNLOCK();
} /* Take away the reference, and possibly free it */
sctp_free_ifn(sctp_ifnp);
}
/*- * Add an ifa to an ifn. * Register the interface as necessary. * NOTE: ADDR write lock MUST be held.
*/ staticvoid
sctp_add_ifa_to_ifn(struct sctp_ifn *sctp_ifnp, struct sctp_ifa *sctp_ifap)
{ int ifa_af;
/*- * Remove an ifa from its ifn. * If no more addresses exist, remove the ifn too. Otherwise, re-register * the interface based on the remaining address families left. * NOTE: ADDR write lock MUST be held.
*/ staticvoid
sctp_remove_ifa_from_ifn(struct sctp_ifa *sctp_ifap)
{
LIST_REMOVE(sctp_ifap, next_ifa); if (sctp_ifap->ifn_p) { /* update address counts */
sctp_ifap->ifn_p->ifa_count--; switch (sctp_ifap->address.sa.sa_family) { #ifdef INET case AF_INET:
sctp_ifap->ifn_p->num_v4--; break; #endif #ifdef INET6 case AF_INET6:
sctp_ifap->ifn_p->num_v6--; break; #endif default: break;
}
if (LIST_EMPTY(&sctp_ifap->ifn_p->ifalist)) { /* remove the ifn, possibly freeing it */
sctp_delete_ifn(sctp_ifap->ifn_p, SCTP_ADDR_LOCKED);
} else { /* re-register address family type, if needed */ if ((sctp_ifap->ifn_p->num_v6 == 0) &&
(sctp_ifap->ifn_p->registered_af == AF_INET6)) {
sctp_ifap->ifn_p->registered_af = AF_INET;
} elseif ((sctp_ifap->ifn_p->num_v4 == 0) &&
(sctp_ifap->ifn_p->registered_af == AF_INET)) {
sctp_ifap->ifn_p->registered_af = AF_INET6;
} /* free the ifn refcount */
sctp_free_ifn(sctp_ifap->ifn_p);
}
sctp_ifap->ifn_p = NULL;
}
}
if (dynamic_add) { /* Bump up the refcount so that when the timer * completes it will drop back down.
*/ struct sctp_laddr *wi;
atomic_add_int(&sctp_ifap->refcount, 1);
wi = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_laddr), struct sctp_laddr); if (wi == NULL) { /* * Gak, what can we do? We have lost an address * change can you say HOSED?
*/
SCTPDBG(SCTP_DEBUG_PCB4, "Lost an address change?\n"); /* Opps, must decrement the count */
sctp_del_addr_from_vrf(vrf_id, addr, ifn_index,
if_name); return (NULL);
}
SCTP_INCR_LADDR_COUNT();
memset(wi, 0, sizeof(*wi));
(void)SCTP_GETTIME_TIMEVAL(&wi->start_time);
wi->ifa = sctp_ifap;
wi->action = SCTP_ADD_IP_ADDRESS;
#ifdef SCTP_DEBUG
SCTPDBG(SCTP_DEBUG_PCB4, "vrf_id 0x%x: deleting address:", vrf_id);
SCTPDBG_ADDR(SCTP_DEBUG_PCB4, addr); #endif
sctp_ifap = sctp_find_ifa_by_addr(addr, vrf->vrf_id, SCTP_ADDR_LOCKED); if (sctp_ifap) { /* Validate the delete */ if (sctp_ifap->ifn_p) { int valid = 0; /*- * The name has priority over the ifn_index * if its given.
*/ if (if_name) { if (strncmp(if_name, sctp_ifap->ifn_p->ifn_name, SCTP_IFNAMSIZ) == 0) { /* They match its a correct delete */
valid = 1;
}
} if (!valid) { /* last ditch check ifn_index */ if (ifn_index == sctp_ifap->ifn_p->ifn_index) {
valid = 1;
}
} if (!valid) {
SCTPDBG(SCTP_DEBUG_PCB4, "ifn:%d ifname:%s does not match addresses\n",
ifn_index, ((if_name == NULL) ? "NULL" : if_name));
SCTPDBG(SCTP_DEBUG_PCB4, "ifn:%d ifname:%s - ignoring delete\n",
sctp_ifap->ifn_p->ifn_index, sctp_ifap->ifn_p->ifn_name);
SCTP_IPI_ADDR_WUNLOCK(); return;
}
}
SCTPDBG(SCTP_DEBUG_PCB4, "Deleting ifa %p\n", (void *)sctp_ifap);
sctp_ifap->localifa_flags &= SCTP_ADDR_VALID; /* * We don't set the flag. This means that the structure will * hang around in EP's that have bound specific to it until * they close. This gives us TCP like behavior if someone * removes an address (or for that matter adds it right back).
*/ /* sctp_ifap->localifa_flags |= SCTP_BEING_DELETED; */
vrf->total_ifa_count--;
LIST_REMOVE(sctp_ifap, next_bucket);
sctp_remove_ifa_from_ifn(sctp_ifap);
} #ifdef SCTP_DEBUG else {
SCTPDBG(SCTP_DEBUG_PCB4, "Del Addr-ifn:%d Could not find address:",
ifn_index);
SCTPDBG_ADDR(SCTP_DEBUG_PCB1, addr);
} #endif
out_now:
SCTP_IPI_ADDR_WUNLOCK(); if (sctp_ifap) { struct sctp_laddr *wi;
wi = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_laddr), struct sctp_laddr); if (wi == NULL) { /* * Gak, what can we do? We have lost an address * change can you say HOSED?
*/
SCTPDBG(SCTP_DEBUG_PCB4, "Lost an address change?\n");
/* Oops, must decrement the count */
sctp_free_ifa(sctp_ifap); return;
}
SCTP_INCR_LADDR_COUNT();
memset(wi, 0, sizeof(*wi));
(void)SCTP_GETTIME_TIMEVAL(&wi->start_time);
wi->ifa = sctp_ifap;
wi->action = SCTP_DEL_IP_ADDRESS;
SCTP_WQ_ADDR_LOCK(); /* * Should this really be a tailq? As it is we will process the * newest first :-0
*/
LIST_INSERT_HEAD(&SCTP_BASE_INFO(addr_wq), wi, sctp_nxt_addr);
sctp_timer_start(SCTP_TIMER_TYPE_ADDR_WQ,
(struct sctp_inpcb *)NULL,
(struct sctp_tcb *)NULL,
(struct sctp_nets *)NULL);
SCTP_WQ_ADDR_UNLOCK();
} return;
}
staticint
sctp_does_stcb_own_this_addr(struct sctp_tcb *stcb, struct sockaddr *to)
{ int loopback_scope; #ifdefined(INET) int ipv4_local_scope, ipv4_addr_legal; #endif #ifdefined(INET6) int local_scope, site_scope, ipv6_addr_legal; #endif #ifdefined(__Userspace__) int conn_addr_legal; #endif struct sctp_vrf *vrf; struct sctp_ifn *sctp_ifn; struct sctp_ifa *sctp_ifa;
SCTP_IPI_ADDR_RLOCK();
vrf = sctp_find_vrf(stcb->asoc.vrf_id); if (vrf == NULL) { /* no vrf, no addresses */
SCTP_IPI_ADDR_RUNLOCK(); return (0);
}
if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) { if ((loopback_scope == 0) &&
SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) { continue;
}
LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) { if (sctp_is_addr_restricted(stcb, sctp_ifa) &&
(!sctp_is_addr_pending(stcb, sctp_ifa))) { /* We allow pending addresses, where we * have sent an asconf-add to be considered * valid.
*/ continue;
} if (sctp_ifa->address.sa.sa_family != to->sa_family) { continue;
} switch (sctp_ifa->address.sa.sa_family) { #ifdef INET case AF_INET: if (ipv4_addr_legal) { struct sockaddr_in *sin, *rsin;
sin = &sctp_ifa->address.sin;
rsin = (struct sockaddr_in *)to; if ((ipv4_local_scope == 0) &&
IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) { continue;
} #ifdefined(__FreeBSD__) && !defined(__Userspace__) if (prison_check_ip4(stcb->sctp_ep->ip_inp.inp.inp_cred,
&sin->sin_addr) != 0) { continue;
} #endif if (sin->sin_addr.s_addr == rsin->sin_addr.s_addr) {
SCTP_IPI_ADDR_RUNLOCK(); return (1);
}
} break; #endif #ifdef INET6 case AF_INET6: if (ipv6_addr_legal) { struct sockaddr_in6 *sin6, *rsin6; #ifdefined(SCTP_EMBEDDED_V6_SCOPE) && !defined(SCTP_KAME) struct sockaddr_in6 lsa6; #endif
sin6 = &sctp_ifa->address.sin6;
rsin6 = (struct sockaddr_in6 *)to; #ifdefined(__FreeBSD__) && !defined(__Userspace__) if (prison_check_ip6(stcb->sctp_ep->ip_inp.inp.inp_cred,
&sin6->sin6_addr) != 0) { continue;
} #endif if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { if (local_scope == 0) continue; #ifdefined(SCTP_EMBEDDED_V6_SCOPE) if (sin6->sin6_scope_id == 0) { #ifdef SCTP_KAME if (sa6_recoverscope(sin6) != 0) continue; #else
lsa6 = *sin6; if (in6_recoverscope(&lsa6,
&lsa6.sin6_addr,
NULL)) continue;
sin6 = &lsa6; #endif/* SCTP_KAME */
} #endif/* SCTP_EMBEDDED_V6_SCOPE */
} if ((site_scope == 0) &&
(IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) { continue;
} if (SCTP6_ARE_ADDR_EQUAL(sin6, rsin6)) {
SCTP_IPI_ADDR_RUNLOCK(); return (1);
}
} break; #endif #ifdefined(__Userspace__) case AF_CONN: if (conn_addr_legal) { struct sockaddr_conn *sconn, *rsconn;
LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) {
SCTPDBG(SCTP_DEBUG_PCB1, "ifa being deleted\n"); continue;
} if (sctp_is_addr_restricted(stcb, laddr->ifa) &&
(!sctp_is_addr_pending(stcb, laddr->ifa))) { /* We allow pending addresses, where we * have sent an asconf-add to be considered * valid.
*/ continue;
} if (laddr->ifa->address.sa.sa_family != to->sa_family) { continue;
} switch (to->sa_family) { #ifdef INET case AF_INET:
{ struct sockaddr_in *sin, *rsin;
sin = &laddr->ifa->address.sin;
rsin = (struct sockaddr_in *)to; if (sin->sin_addr.s_addr == rsin->sin_addr.s_addr) {
SCTP_IPI_ADDR_RUNLOCK(); return (1);
} break;
} #endif #ifdef INET6 case AF_INET6:
{ struct sockaddr_in6 *sin6, *rsin6;
staticstruct sctp_tcb *
sctp_tcb_special_locate(struct sctp_inpcb **inp_p, struct sockaddr *from, struct sockaddr *to, struct sctp_nets **netp, uint32_t vrf_id)
{ /**** ASSUMES THE CALLER holds the INP_INFO_RLOCK */ /* * If we support the TCP model, then we must now dig through to see * if we can find our endpoint in the list of tcp ep's.
*/
uint16_t lport, rport; struct sctppcbhead *ephead; struct sctp_inpcb *inp; struct sctp_laddr *laddr; struct sctp_tcb *stcb; struct sctp_nets *net; #ifdef SCTP_MVRF int fnd, i; #endif
switch (to->sa_family) { #ifdef INET case AF_INET: if (from->sa_family == AF_INET) {
lport = ((struct sockaddr_in *)to)->sin_port;
rport = ((struct sockaddr_in *)from)->sin_port;
} else { return (NULL);
} break; #endif #ifdef INET6 case AF_INET6: if (from->sa_family == AF_INET6) {
lport = ((struct sockaddr_in6 *)to)->sin6_port;
rport = ((struct sockaddr_in6 *)from)->sin6_port;
} else { return (NULL);
} break; #endif #ifdefined(__Userspace__) case AF_CONN: if (from->sa_family == AF_CONN) {
lport = ((struct sockaddr_conn *)to)->sconn_port;
rport = ((struct sockaddr_conn *)from)->sconn_port;
} else { return (NULL);
} break; #endif default: return (NULL);
}
ephead = &SCTP_BASE_INFO(sctp_tcpephash)[SCTP_PCBHASH_ALLADDR((lport | rport), SCTP_BASE_INFO(hashtcpmark))]; /* * Ok now for each of the guys in this bucket we must look and see: * - Does the remote port match. - Does there single association's * addresses match this address (to). If so we update p_ep to point * to this ep and return the tcb from it.
*/
LIST_FOREACH(inp, ephead, sctp_hash) {
SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
SCTP_INP_RUNLOCK(inp); continue;
} if (lport != inp->sctp_lport) {
SCTP_INP_RUNLOCK(inp); continue;
} #ifdefined(__FreeBSD__) && !defined(__Userspace__) switch (to->sa_family) { #ifdef INET case AF_INET:
{ struct sockaddr_in *sin;
sin = (struct sockaddr_in *)to; if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
&sin->sin_addr) != 0) {
SCTP_INP_RUNLOCK(inp); continue;
} break;
} #endif #ifdef INET6 case AF_INET6:
{ struct sockaddr_in6 *sin6;
sin6 = (struct sockaddr_in6 *)to; if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
&sin6->sin6_addr) != 0) {
SCTP_INP_RUNLOCK(inp); continue;
} break;
} #endif default:
SCTP_INP_RUNLOCK(inp); continue;
} #endif #ifdef SCTP_MVRF
fnd = 0; for (i = 0; i < inp->num_vrfs; i++) { if (inp->m_vrf_ids[i] == vrf_id) {
fnd = 1; break;
}
} if (fnd == 0) {
SCTP_INP_RUNLOCK(inp); continue;
} #else if (inp->def_vrf_id != vrf_id) {
SCTP_INP_RUNLOCK(inp); continue;
} #endif /* check to see if the ep has one of the addresses */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) { /* We are NOT bound all, so look further */ int match = 0;
LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == NULL) {
SCTPDBG(SCTP_DEBUG_PCB1, "%s: NULL ifa\n", __func__); continue;
} if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) {
SCTPDBG(SCTP_DEBUG_PCB1, "ifa being deleted\n"); continue;
} if (laddr->ifa->address.sa.sa_family ==
to->sa_family) { /* see if it matches */ #ifdef INET if (from->sa_family == AF_INET) { struct sockaddr_in *intf_addr, *sin;
intf_addr = &laddr->ifa->address.sin;
sin = (struct sockaddr_in *)to; if (sin->sin_addr.s_addr ==
intf_addr->sin_addr.s_addr) {
match = 1; break;
}
} #endif #ifdef INET6 if (from->sa_family == AF_INET6) { struct sockaddr_in6 *intf_addr6; struct sockaddr_in6 *sin6;
if (SCTP6_ARE_ADDR_EQUAL(sin6,
intf_addr6)) {
match = 1; break;
}
} #endif #ifdefined(__Userspace__) if (from->sa_family == AF_CONN) { struct sockaddr_conn *intf_addr, *sconn;
intf_addr = &laddr->ifa->address.sconn;
sconn = (struct sockaddr_conn *)to; if (sconn->sconn_addr ==
intf_addr->sconn_addr) {
match = 1; break;
}
} #endif
}
} if (match == 0) { /* This endpoint does not have this address */
SCTP_INP_RUNLOCK(inp); continue;
}
} /* * Ok if we hit here the ep has the address, does it hold * the tcb?
*/ /* XXX: Why don't we TAILQ_FOREACH through sctp_asoc_list? */
stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) {
SCTP_INP_RUNLOCK(inp); continue;
}
SCTP_TCB_LOCK(stcb); if (!sctp_does_stcb_own_this_addr(stcb, to)) {
SCTP_TCB_UNLOCK(stcb);
SCTP_INP_RUNLOCK(inp); continue;
} if (stcb->rport != rport) { /* remote port does not match. */
SCTP_TCB_UNLOCK(stcb);
SCTP_INP_RUNLOCK(inp); continue;
} if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
SCTP_TCB_UNLOCK(stcb);
SCTP_INP_RUNLOCK(inp); continue;
} if (!sctp_does_stcb_own_this_addr(stcb, to)) {
SCTP_TCB_UNLOCK(stcb);
SCTP_INP_RUNLOCK(inp); continue;
} /* Does this TCB have a matching address? */
TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if (net->ro._l_addr.sa.sa_family != from->sa_family) { /* not the same family, can't be a match */ continue;
} switch (from->sa_family) { #ifdef INET case AF_INET:
{ struct sockaddr_in *sin, *rsin;
sin = (struct sockaddr_in *)&net->ro._l_addr;
rsin = (struct sockaddr_in *)from; if (sin->sin_addr.s_addr ==
rsin->sin_addr.s_addr) { /* found it */ if (netp != NULL) {
*netp = net;
} /* Update the endpoint pointer */
*inp_p = inp;
SCTP_INP_RUNLOCK(inp); return (stcb);
} break;
} #endif #ifdef INET6 case AF_INET6:
{ struct sockaddr_in6 *sin6, *rsin6;
sin6 = (struct sockaddr_in6 *)&net->ro._l_addr;
rsin6 = (struct sockaddr_in6 *)from; if (SCTP6_ARE_ADDR_EQUAL(sin6,
rsin6)) { /* found it */ if (netp != NULL) {
*netp = net;
} /* Update the endpoint pointer */
*inp_p = inp;
SCTP_INP_RUNLOCK(inp); return (stcb);
} break;
} #endif #ifdefined(__Userspace__) case AF_CONN:
{ struct sockaddr_conn *sconn, *rsconn;
/* * rules for use * * 1) If I return a NULL you must decrement any INP ref cnt. 2) If I find an * stcb, both will be locked (locked_tcb and stcb) but decrement will be done * (if locked == NULL). 3) Decrement happens on return ONLY if locked == * NULL.
*/
inp = *inp_p; switch (remote->sa_family) { #ifdef INET case AF_INET:
rport = (((struct sockaddr_in *)remote)->sin_port); break; #endif #ifdef INET6 case AF_INET6:
rport = (((struct sockaddr_in6 *)remote)->sin6_port); break; #endif #ifdefined(__Userspace__) case AF_CONN:
rport = (((struct sockaddr_conn *)remote)->sconn_port); break; #endif default: return (NULL);
} if (locked_tcb) { /* * UN-lock so we can do proper locking here this occurs when * called from load_addresses_from_init.
*/
atomic_add_int(&locked_tcb->asoc.refcnt, 1);
SCTP_TCB_UNLOCK(locked_tcb);
}
SCTP_INP_INFO_RLOCK(); if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
(inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { /*- * Now either this guy is our listener or it's the * connector. If it is the one that issued the connect, then * it's only chance is to be the first TCB in the list. If * it is the acceptor, then do the special_lookup to hash * and find the real inp.
*/ if ((inp->sctp_socket) && SCTP_IS_LISTENING(inp)) { /* to is peer addr, from is my addr */ #ifndef SCTP_MVRF
stcb = sctp_tcb_special_locate(inp_p, remote, local,
netp, inp->def_vrf_id); if ((stcb != NULL) && (locked_tcb == NULL)) { /* we have a locked tcb, lower refcount */
SCTP_INP_DECR_REF(inp);
} if ((locked_tcb != NULL) && (locked_tcb != stcb)) {
SCTP_INP_RLOCK(locked_tcb->sctp_ep);
SCTP_TCB_LOCK(locked_tcb);
atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
SCTP_INP_RUNLOCK(locked_tcb->sctp_ep);
} #else /*- * MVRF is tricky, we must look in every VRF * the endpoint has.
*/ int i;
for (i = 0; i < inp->num_vrfs; i++) {
stcb = sctp_tcb_special_locate(inp_p, remote, local,
netp, inp->m_vrf_ids[i]); if ((stcb != NULL) && (locked_tcb == NULL)) { /* we have a locked tcb, lower refcount */
SCTP_INP_DECR_REF(inp); break;
} if ((locked_tcb != NULL) && (locked_tcb != stcb)) {
SCTP_INP_RLOCK(locked_tcb->sctp_ep);
SCTP_TCB_LOCK(locked_tcb);
atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
SCTP_INP_RUNLOCK(locked_tcb->sctp_ep); break;
}
} #endif
SCTP_INP_INFO_RUNLOCK(); return (stcb);
} else {
SCTP_INP_WLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { goto null_return;
}
stcb = LIST_FIRST(&inp->sctp_asoc_list); if (stcb == NULL) { goto null_return;
}
SCTP_TCB_LOCK(stcb);
if (stcb->rport != rport) { /* remote port does not match. */
SCTP_TCB_UNLOCK(stcb); goto null_return;
} if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
SCTP_TCB_UNLOCK(stcb); goto null_return;
} if (local && !sctp_does_stcb_own_this_addr(stcb, local)) {
SCTP_TCB_UNLOCK(stcb); goto null_return;
} /* now look at the list of remote addresses */
TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { #ifdef INVARIANTS if (net == (TAILQ_NEXT(net, sctp_next))) {
panic("Corrupt net list");
} #endif if (net->ro._l_addr.sa.sa_family !=
remote->sa_family) { /* not the same family */ continue;
} switch (remote->sa_family) { #ifdef INET case AF_INET:
{ struct sockaddr_in *sin, *rsin;
sin = (struct sockaddr_in *)
&net->ro._l_addr;
rsin = (struct sockaddr_in *)remote; if (sin->sin_addr.s_addr ==
rsin->sin_addr.s_addr) { /* found it */ if (netp != NULL) {
*netp = net;
} if (locked_tcb == NULL) {
SCTP_INP_DECR_REF(inp);
} elseif (locked_tcb != stcb) {
SCTP_TCB_LOCK(locked_tcb);
} if (locked_tcb) {
atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
}
sin6 = (struct sockaddr_in6 *)&net->ro._l_addr;
rsin6 = (struct sockaddr_in6 *)remote; if (SCTP6_ARE_ADDR_EQUAL(sin6,
rsin6)) { /* found it */ if (netp != NULL) {
*netp = net;
} if (locked_tcb == NULL) {
SCTP_INP_DECR_REF(inp);
} elseif (locked_tcb != stcb) {
SCTP_TCB_LOCK(locked_tcb);
} if (locked_tcb) {
atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
}
SCTP_INP_WUNLOCK(inp);
SCTP_INP_INFO_RUNLOCK(); return (stcb);
} break;
} #endif #ifdefined(__Userspace__) case AF_CONN:
{ struct sockaddr_conn *sconn, *rsconn;
sconn = (struct sockaddr_conn *)&net->ro._l_addr;
rsconn = (struct sockaddr_conn *)remote; if (sconn->sconn_addr == rsconn->sconn_addr) { /* found it */ if (netp != NULL) {
*netp = net;
} if (locked_tcb == NULL) {
SCTP_INP_DECR_REF(inp);
} elseif (locked_tcb != stcb) {
SCTP_TCB_LOCK(locked_tcb);
} if (locked_tcb) {
atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
}
SCTP_INP_WUNLOCK(inp);
SCTP_INP_INFO_RUNLOCK(); return (stcb);
} break;
} #endif default: /* TSNH */ break;
}
}
SCTP_TCB_UNLOCK(stcb);
}
} else {
SCTP_INP_WLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { goto null_return;
}
head = &inp->sctp_tcbhash[SCTP_PCBHASH_ALLADDR(rport,
inp->sctp_hashmark)];
LIST_FOREACH(stcb, head, sctp_tcbhash) { if (stcb->rport != rport) { /* remote port does not match */ continue;
}
SCTP_TCB_LOCK(stcb); if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
SCTP_TCB_UNLOCK(stcb); continue;
} if (local && !sctp_does_stcb_own_this_addr(stcb, local)) {
SCTP_TCB_UNLOCK(stcb); continue;
} /* now look at the list of remote addresses */
TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { #ifdef INVARIANTS if (net == (TAILQ_NEXT(net, sctp_next))) {
panic("Corrupt net list");
} #endif if (net->ro._l_addr.sa.sa_family !=
remote->sa_family) { /* not the same family */ continue;
} switch (remote->sa_family) { #ifdef INET case AF_INET:
{ struct sockaddr_in *sin, *rsin;
sin = (struct sockaddr_in *)
&net->ro._l_addr;
rsin = (struct sockaddr_in *)remote; if (sin->sin_addr.s_addr ==
rsin->sin_addr.s_addr) { /* found it */ if (netp != NULL) {
*netp = net;
} if (locked_tcb == NULL) {
SCTP_INP_DECR_REF(inp);
} elseif (locked_tcb != stcb) {
SCTP_TCB_LOCK(locked_tcb);
} if (locked_tcb) {
atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
}
SCTP_INP_WUNLOCK(inp);
SCTP_INP_INFO_RUNLOCK(); return (stcb);
} break;
} #endif #ifdef INET6 case AF_INET6:
{ struct sockaddr_in6 *sin6, *rsin6;
sin6 = (struct sockaddr_in6 *)
&net->ro._l_addr;
rsin6 = (struct sockaddr_in6 *)remote; if (SCTP6_ARE_ADDR_EQUAL(sin6,
rsin6)) { /* found it */ if (netp != NULL) {
*netp = net;
} if (locked_tcb == NULL) {
SCTP_INP_DECR_REF(inp);
} elseif (locked_tcb != stcb) {
SCTP_TCB_LOCK(locked_tcb);
} if (locked_tcb) {
atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
}
SCTP_INP_WUNLOCK(inp);
SCTP_INP_INFO_RUNLOCK(); return (stcb);
} break;
} #endif #ifdefined(__Userspace__) case AF_CONN:
{ struct sockaddr_conn *sconn, *rsconn;
sconn = (struct sockaddr_conn *)&net->ro._l_addr;
rsconn = (struct sockaddr_conn *)remote; if (sconn->sconn_addr == rsconn->sconn_addr) { /* found it */ if (netp != NULL) {
*netp = net;
} if (locked_tcb == NULL) {
SCTP_INP_DECR_REF(inp);
} elseif (locked_tcb != stcb) {
SCTP_TCB_LOCK(locked_tcb);
} if (locked_tcb) {
atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
}
SCTP_INP_WUNLOCK(inp);
SCTP_INP_INFO_RUNLOCK(); return (stcb);
} break;
} #endif default: /* TSNH */ break;
}
}
SCTP_TCB_UNLOCK(stcb);
}
}
null_return: /* clean up for returning null */ if (locked_tcb) {
SCTP_TCB_LOCK(locked_tcb);
atomic_subtract_int(&locked_tcb->asoc.refcnt, 1);
}
SCTP_INP_WUNLOCK(inp);
SCTP_INP_INFO_RUNLOCK(); /* not found */ return (NULL);
}
/* * Find an association for a specific endpoint using the association id given * out in the COMM_UP notification
*/ struct sctp_tcb *
sctp_findasoc_ep_asocid_locked(struct sctp_inpcb *inp, sctp_assoc_t asoc_id, int want_lock)
{ /* * Use my the assoc_id to find a endpoint
*/ struct sctpasochead *head; struct sctp_tcb *stcb;
uint32_t id;
if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
SCTP_PRINTF("TSNH ep_associd0\n"); return (NULL);
}
id = (uint32_t)asoc_id;
head = &inp->sctp_asocidhash[SCTP_PCBHASH_ASOC(id, inp->hashasocidmark)]; if (head == NULL) { /* invalid id TSNH */
SCTP_PRINTF("TSNH ep_associd1\n"); return (NULL);
}
LIST_FOREACH(stcb, head, sctp_tcbasocidhash) { if (stcb->asoc.assoc_id == id) { if (inp != stcb->sctp_ep) { /* * some other guy has the same id active (id * collision ??).
*/
SCTP_PRINTF("TSNH ep_associd2\n"); continue;
} if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { continue;
} if (want_lock) {
SCTP_TCB_LOCK(stcb);
} return (stcb);
}
} return (NULL);
}
#ifdef INET
sin = NULL; #endif #ifdef INET6
sin6 = NULL; #endif #ifdefined(__Userspace__)
sconn = NULL; #endif switch (nam->sa_family) { #ifdef INET case AF_INET:
sin = (struct sockaddr_in *)nam; break; #endif #ifdef INET6 case AF_INET6:
sin6 = (struct sockaddr_in6 *)nam; break; #endif #ifdefined(__Userspace__) case AF_CONN:
sconn = (struct sockaddr_conn *)nam; break; #endif default: /* unsupported family */ return (NULL);
}
if (head == NULL) return (NULL);
LIST_FOREACH(inp, head, sctp_hash) {
SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
SCTP_INP_RUNLOCK(inp); continue;
} if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) &&
(inp->sctp_lport == lport)) { /* got it */ switch (nam->sa_family) { #ifdef INET case AF_INET: if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
SCTP_IPV6_V6ONLY(inp)) { /* IPv4 on a IPv6 socket with ONLY IPv6 set */
SCTP_INP_RUNLOCK(inp); continue;
} #ifdefined(__FreeBSD__) && !defined(__Userspace__) if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
&sin->sin_addr) != 0) {
SCTP_INP_RUNLOCK(inp); continue;
} #endif break; #endif #ifdef INET6 case AF_INET6: /* A V6 address and the endpoint is NOT bound V6 */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) {
SCTP_INP_RUNLOCK(inp); continue;
} #ifdefined(__FreeBSD__) && !defined(__Userspace__) if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
&sin6->sin6_addr) != 0) {
SCTP_INP_RUNLOCK(inp); continue;
} #endif break; #endif default: break;
} /* does a VRF id match? */
fnd = 0; #ifdef SCTP_MVRF for (i = 0; i < inp->num_vrfs; i++) { if (inp->m_vrf_ids[i] == vrf_id) {
fnd = 1; break;
}
} #else if (inp->def_vrf_id == vrf_id)
fnd = 1; #endif
SCTP_INP_RUNLOCK(inp); if (!fnd) continue; return (inp);
}
SCTP_INP_RUNLOCK(inp);
} switch (nam->sa_family) { #ifdef INET case AF_INET: if (sin->sin_addr.s_addr == INADDR_ANY) { /* Can't hunt for one that has no address specified */ return (NULL);
} break; #endif #ifdef INET6 case AF_INET6: if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { /* Can't hunt for one that has no address specified */ return (NULL);
} break; #endif #ifdefined(__Userspace__) case AF_CONN: if (sconn->sconn_addr == NULL) { return (NULL);
} break; #endif default: break;
} /* * ok, not bound to all so see if we can find a EP bound to this * address.
*/
LIST_FOREACH(inp, head, sctp_hash) {
SCTP_INP_RLOCK(inp); if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
SCTP_INP_RUNLOCK(inp); continue;
} if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL)) {
SCTP_INP_RUNLOCK(inp); continue;
} /* * Ok this could be a likely candidate, look at all of its * addresses
*/ if (inp->sctp_lport != lport) {
SCTP_INP_RUNLOCK(inp); continue;
} /* does a VRF id match? */
fnd = 0; #ifdef SCTP_MVRF for (i = 0; i < inp->num_vrfs; i++) { if (inp->m_vrf_ids[i] == vrf_id) {
fnd = 1; break;
}
} #else if (inp->def_vrf_id == vrf_id)
fnd = 1;
#endif if (!fnd) {
SCTP_INP_RUNLOCK(inp); continue;
}
LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == NULL) {
SCTPDBG(SCTP_DEBUG_PCB1, "%s: NULL ifa\n",
__func__); continue;
}
SCTPDBG(SCTP_DEBUG_PCB1, "Ok laddr->ifa:%p is possible, ",
(void *)laddr->ifa); if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED) {
SCTPDBG(SCTP_DEBUG_PCB1, "Huh IFA being deleted\n"); continue;
} if (laddr->ifa->address.sa.sa_family == nam->sa_family) { /* possible, see if it matches */ switch (nam->sa_family) { #ifdef INET case AF_INET: #ifdefined(__APPLE__) && !defined(__Userspace__) if (sin == NULL) { /* TSNH */ break;
} #endif if (sin->sin_addr.s_addr ==
laddr->ifa->address.sin.sin_addr.s_addr) {
SCTP_INP_RUNLOCK(inp); return (inp);
} break; #endif #ifdef INET6 case AF_INET6:
intf_addr6 = &laddr->ifa->address.sin6; if (SCTP6_ARE_ADDR_EQUAL(sin6,
intf_addr6)) {
SCTP_INP_RUNLOCK(inp); return (inp);
} break; #endif #ifdefined(__Userspace__) case AF_CONN: if (sconn->sconn_addr == laddr->ifa->address.sconn.sconn_addr) {
SCTP_INP_RUNLOCK(inp); return (inp);
} break; #endif
}
}
}
SCTP_INP_RUNLOCK(inp);
} return (NULL);
}
head = &SCTP_BASE_INFO(sctp_ephash)[SCTP_PCBHASH_ALLADDR(lport,
SCTP_BASE_INFO(hashmark))];
LIST_FOREACH(t_inp, head, sctp_hash) { if (t_inp->sctp_lport != lport) { continue;
} /* is it in the VRF in question */
fnd = 0; #ifdef SCTP_MVRF for (i = 0; i < inp->num_vrfs; i++) { if (t_inp->m_vrf_ids[i] == vrf_id) {
fnd = 1; break;
}
} #else if (t_inp->def_vrf_id == vrf_id)
fnd = 1; #endif if (!fnd) continue;
/* This one is in use. */ /* check the v6/v4 binding issue */ if ((t_inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
SCTP_IPV6_V6ONLY(t_inp)) { if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { /* collision in V6 space */ return (t_inp);
} else { /* inp is BOUND_V4 no conflict */ continue;
}
} elseif (t_inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) { /* t_inp is bound v4 and v6, conflict always */ return (t_inp);
} else { /* t_inp is bound only V4 */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
SCTP_IPV6_V6ONLY(inp)) { /* no conflict */ continue;
} /* else fall through to conflict */
} return (t_inp);
} return (NULL);
}
int
sctp_swap_inpcb_for_listen(struct sctp_inpcb *inp)
{ /* For 1-2-1 with port reuse */ struct sctppcbhead *head; struct sctp_inpcb *tinp, *ninp;
if (sctp_is_feature_off(inp, SCTP_PCB_FLAGS_PORTREUSE)) { /* only works with port reuse on */ return (-1);
} if ((inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) == 0) { return (0);
}
SCTP_INP_WUNLOCK(inp);
head = &SCTP_BASE_INFO(sctp_ephash)[SCTP_PCBHASH_ALLADDR(inp->sctp_lport,
SCTP_BASE_INFO(hashmark))]; /* Kick out all non-listeners to the TCP hash */
LIST_FOREACH_SAFE(tinp, head, sctp_hash, ninp) { if (tinp->sctp_lport != inp->sctp_lport) { continue;
} if (tinp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { continue;
} if (tinp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { continue;
} if (SCTP_IS_LISTENING(tinp)) { continue;
}
SCTP_INP_WLOCK(tinp);
LIST_REMOVE(tinp, sctp_hash);
head = &SCTP_BASE_INFO(sctp_tcpephash)[SCTP_PCBHASH_ALLADDR(tinp->sctp_lport, SCTP_BASE_INFO(hashtcpmark))];
tinp->sctp_flags |= SCTP_PCB_FLAGS_IN_TCPPOOL;
LIST_INSERT_HEAD(head, tinp, sctp_hash);
SCTP_INP_WUNLOCK(tinp);
}
SCTP_INP_WLOCK(inp); /* Pull from where he was */
LIST_REMOVE(inp, sctp_hash);
inp->sctp_flags &= ~SCTP_PCB_FLAGS_IN_TCPPOOL;
head = &SCTP_BASE_INFO(sctp_ephash)[SCTP_PCBHASH_ALLADDR(inp->sctp_lport, SCTP_BASE_INFO(hashmark))];
LIST_INSERT_HEAD(head, inp, sctp_hash); return (0);
}
struct sctp_inpcb *
sctp_pcb_findep(struct sockaddr *nam, int find_tcp_pool, int have_lock,
uint32_t vrf_id)
{ /* * First we check the hash table to see if someone has this port * bound with just the port.
*/ struct sctp_inpcb *inp; struct sctppcbhead *head; int lport; unsignedint i; #ifdef INET struct sockaddr_in *sin; #endif #ifdef INET6 struct sockaddr_in6 *sin6; #endif #ifdefined(__Userspace__) struct sockaddr_conn *sconn; #endif
switch (nam->sa_family) { #ifdef INET case AF_INET:
sin = (struct sockaddr_in *)nam;
lport = sin->sin_port; break; #endif #ifdef INET6 case AF_INET6:
sin6 = (struct sockaddr_in6 *)nam;
lport = sin6->sin6_port; break; #endif #ifdefined(__Userspace__) case AF_CONN:
sconn = (struct sockaddr_conn *)nam;
lport = sconn->sconn_port; break; #endif default: return (NULL);
} /* * I could cheat here and just cast to one of the types but we will * do it right. It also provides the check against an Unsupported * type too.
*/ /* Find the head of the ALLADDR chain */ if (have_lock == 0) {
SCTP_INP_INFO_RLOCK();
}
head = &SCTP_BASE_INFO(sctp_ephash)[SCTP_PCBHASH_ALLADDR(lport,
SCTP_BASE_INFO(hashmark))];
inp = sctp_endpoint_probe(nam, head, lport, vrf_id);
/* * If the TCP model exists it could be that the main listening * endpoint is gone but there still exists a connected socket for this * guy. If so we can return the first one that we find. This may NOT * be the correct one so the caller should be wary on the returned INP. * Currently the only caller that sets find_tcp_pool is in bindx where * we are verifying that a user CAN bind the address. He either * has bound it already, or someone else has, or its open to bind, * so this is good enough.
*/ if (inp == NULL && find_tcp_pool) { for (i = 0; i < SCTP_BASE_INFO(hashtcpmark) + 1; i++) {
head = &SCTP_BASE_INFO(sctp_tcpephash)[i];
inp = sctp_endpoint_probe(nam, head, lport, vrf_id); if (inp) { break;
}
}
} if (inp) {
SCTP_INP_INCR_REF(inp);
} if (have_lock == 0) {
SCTP_INP_INFO_RUNLOCK();
} return (inp);
}
/* * Find an association for an endpoint with the pointer to whom you want to * send to and the endpoint pointer. The address can be IPv4 or IPv6. We may * need to change the *to to some other struct like a mbuf...
*/ struct sctp_tcb *
sctp_findassociation_addr_sa(struct sockaddr *from, struct sockaddr *to, struct sctp_inpcb **inp_p, struct sctp_nets **netp, int find_tcp_pool,
uint32_t vrf_id)
{ struct sctp_inpcb *inp = NULL; struct sctp_tcb *stcb;
SCTP_INP_INFO_RLOCK(); if (find_tcp_pool) { if (inp_p != NULL) {
stcb = sctp_tcb_special_locate(inp_p, from, to, netp,
vrf_id);
} else {
stcb = sctp_tcb_special_locate(&inp, from, to, netp,
vrf_id);
} if (stcb != NULL) {
SCTP_INP_INFO_RUNLOCK(); return (stcb);
}
}
inp = sctp_pcb_findep(to, 0, 1, vrf_id); if (inp_p != NULL) {
*inp_p = inp;
}
SCTP_INP_INFO_RUNLOCK(); if (inp == NULL) { return (NULL);
} /* * ok, we have an endpoint, now lets find the assoc for it (if any) * we now place the source address or from in the to of the find * endpoint call. Since in reality this chain is used from the * inbound packet side.
*/ if (inp_p != NULL) {
stcb = sctp_findassociation_ep_addr(inp_p, from, netp, to,
NULL);
} else {
stcb = sctp_findassociation_ep_addr(&inp, from, netp, to,
NULL);
} return (stcb);
}
/* * This routine will grub through the mbuf that is a INIT or INIT-ACK and * find all addresses that the sender has specified in any address list. Each * address will be used to lookup the TCB and see if one exits.
*/ staticstruct sctp_tcb *
sctp_findassociation_special_addr(struct mbuf *m, int offset, struct sctphdr *sh, struct sctp_inpcb **inp_p, struct sctp_nets **netp, struct sockaddr *dst)
{ struct sctp_paramhdr *phdr, param_buf; #ifdefined(INET) || defined(INET6) struct sctp_tcb *stcb;
uint16_t ptype; #endif
uint16_t plen; #ifdef INET struct sockaddr_in sin4; #endif #ifdef INET6 struct sockaddr_in6 sin6; #endif
phdr = sctp_get_next_param(m, offset, ¶m_buf, sizeof(param_buf)); while (phdr != NULL) { /* now we must see if we want the parameter */ #ifdefined(INET) || defined(INET6)
ptype = ntohs(phdr->param_type); #endif
plen = ntohs(phdr->param_length); if (plen == 0) { break;
} #ifdef INET if (ptype == SCTP_IPV4_ADDRESS &&
plen == sizeof(struct sctp_ipv4addr_param)) { /* Get the rest of the address */ struct sctp_ipv4addr_param ip4_param, *p4;
phdr = sctp_get_next_param(m, offset,
(struct sctp_paramhdr *)&ip4_param, sizeof(ip4_param)); if (phdr == NULL) { return (NULL);
}
p4 = (struct sctp_ipv4addr_param *)phdr;
memcpy(&sin4.sin_addr, &p4->addr, sizeof(p4->addr)); /* look it up */
stcb = sctp_findassociation_ep_addr(inp_p,
(struct sockaddr *)&sin4, netp, dst, NULL); if (stcb != NULL) { return (stcb);
}
} #endif #ifdef INET6 if (ptype == SCTP_IPV6_ADDRESS &&
plen == sizeof(struct sctp_ipv6addr_param)) { /* Get the rest of the address */ struct sctp_ipv6addr_param ip6_param, *p6;
staticstruct sctp_tcb *
sctp_findassoc_by_vtag(struct sockaddr *from, struct sockaddr *to, uint32_t vtag, struct sctp_inpcb **inp_p, struct sctp_nets **netp, uint16_t rport,
uint16_t lport, int skip_src_check, uint32_t vrf_id, uint32_t remote_tag)
{ /* * Use my vtag to hash. If we find it we then verify the source addr * is in the assoc. If all goes well we save a bit on rec of a * packet.
*/ struct sctpasochead *head; struct sctp_nets *net; struct sctp_tcb *stcb; #ifdef SCTP_MVRF unsignedint i; #endif
SCTP_INP_INFO_RLOCK();
head = &SCTP_BASE_INFO(sctp_asochash)[SCTP_PCBHASH_ASOC(vtag,
SCTP_BASE_INFO(hashasocmark))];
LIST_FOREACH(stcb, head, sctp_asocs) {
SCTP_INP_RLOCK(stcb->sctp_ep); if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) {
SCTP_INP_RUNLOCK(stcb->sctp_ep); continue;
} #ifdef SCTP_MVRF for (i = 0; i < stcb->sctp_ep->num_vrfs; i++) { if (stcb->sctp_ep->m_vrf_ids[i] == vrf_id) { break;
}
} if (i == stcb->sctp_ep->num_vrfs) {
SCTP_INP_RUNLOCK(inp); continue;
} #else if (stcb->sctp_ep->def_vrf_id != vrf_id) {
SCTP_INP_RUNLOCK(stcb->sctp_ep); continue;
} #endif
SCTP_TCB_LOCK(stcb);
SCTP_INP_RUNLOCK(stcb->sctp_ep); if (stcb->asoc.my_vtag == vtag) { /* candidate */ if (stcb->rport != rport) {
SCTP_TCB_UNLOCK(stcb); continue;
} if (stcb->sctp_ep->sctp_lport != lport) {
SCTP_TCB_UNLOCK(stcb); continue;
} if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
SCTP_TCB_UNLOCK(stcb); continue;
} /* RRS:Need toaddr check here */ if (sctp_does_stcb_own_this_addr(stcb, to) == 0) { /* Endpoint does not own this address */
SCTP_TCB_UNLOCK(stcb); continue;
} if (remote_tag) { /* If we have both vtags that's all we match on */ if (stcb->asoc.peer_vtag == remote_tag) { /* If both tags match we consider it conclusive * and check NO source/destination addresses
*/ goto conclusive;
}
} if (skip_src_check) {
conclusive: if (from) {
*netp = sctp_findnet(stcb, from);
} else {
*netp = NULL; /* unknown */
} if (inp_p)
*inp_p = stcb->sctp_ep;
SCTP_INP_INFO_RUNLOCK(); return (stcb);
}
net = sctp_findnet(stcb, from); if (net) { /* yep its him. */
*netp = net;
SCTP_STAT_INCR(sctps_vtagexpress);
*inp_p = stcb->sctp_ep;
SCTP_INP_INFO_RUNLOCK(); return (stcb);
} else { /* * not him, this should only happen in rare * cases so I peg it.
*/
SCTP_STAT_INCR(sctps_vtagbogus);
}
}
SCTP_TCB_UNLOCK(stcb);
}
SCTP_INP_INFO_RUNLOCK(); return (NULL);
}
/* * Find an association with the pointer to the inbound IP packet. This can be * a IPv4 or IPv6 packet.
*/ struct sctp_tcb *
sctp_findassociation_addr(struct mbuf *m, int offset, struct sockaddr *src, struct sockaddr *dst, struct sctphdr *sh, struct sctp_chunkhdr *ch, struct sctp_inpcb **inp_p, struct sctp_nets **netp, uint32_t vrf_id)
{ struct sctp_tcb *stcb; struct sctp_inpcb *inp;
if (sh->v_tag) { /* we only go down this path if vtag is non-zero */
stcb = sctp_findassoc_by_vtag(src, dst, ntohl(sh->v_tag),
inp_p, netp, sh->src_port, sh->dest_port, 0, vrf_id, 0); if (stcb) { return (stcb);
}
}
if (inp_p) {
stcb = sctp_findassociation_addr_sa(src, dst, inp_p, netp,
1, vrf_id);
inp = *inp_p;
} else {
stcb = sctp_findassociation_addr_sa(src, dst, &inp, netp,
1, vrf_id);
}
SCTPDBG(SCTP_DEBUG_PCB1, "stcb:%p inp:%p\n", (void *)stcb, (void *)inp); if (stcb == NULL && inp) { /* Found a EP but not this address */ if ((ch->chunk_type == SCTP_INITIATION) ||
(ch->chunk_type == SCTP_INITIATION_ACK)) { /*- * special hook, we do NOT return linp or an * association that is linked to an existing * association that is under the TCP pool (i.e. no * listener exists). The endpoint finding routine * will always find a listener before examining the * TCP pool.
*/ if (inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL) { if (inp_p) {
*inp_p = NULL;
} return (NULL);
}
stcb = sctp_findassociation_special_addr(m,
offset, sh, &inp, netp, dst); if (inp_p != NULL) {
*inp_p = inp;
}
}
}
SCTPDBG(SCTP_DEBUG_PCB1, "stcb is %p\n", (void *)stcb); return (stcb);
}
/* * lookup an association by an ASCONF lookup address. * if the lookup address is 0.0.0.0 or ::0, use the vtag to do the lookup
*/ struct sctp_tcb *
sctp_findassociation_ep_asconf(struct mbuf *m, int offset, struct sockaddr *dst, struct sctphdr *sh, struct sctp_inpcb **inp_p, struct sctp_nets **netp, uint32_t vrf_id)
{ struct sctp_tcb *stcb; union sctp_sockstore remote_store; struct sctp_paramhdr param_buf, *phdr; int ptype; int zero_address = 0; #ifdef INET struct sockaddr_in *sin; #endif #ifdef INET6 struct sockaddr_in6 *sin6; #endif
memset(&remote_store, 0, sizeof(remote_store));
phdr = sctp_get_next_param(m, offset + sizeof(struct sctp_asconf_chunk),
¶m_buf, sizeof(struct sctp_paramhdr)); if (phdr == NULL) {
SCTPDBG(SCTP_DEBUG_INPUT3, "%s: failed to get asconf lookup addr\n",
__func__); return NULL;
}
ptype = (int)((uint32_t) ntohs(phdr->param_type)); /* get the correlation address */ switch (ptype) { #ifdef INET6 case SCTP_IPV6_ADDRESS:
{ /* ipv6 address param */ struct sctp_ipv6addr_param *p6, p6_buf;
/* * allocate a sctp_inpcb and setup a temporary binding to a port/all * addresses. This way if we don't get a bind we by default pick a ephemeral * port with all addresses bound.
*/ int
sctp_inpcb_alloc(struct socket *so, uint32_t vrf_id)
{ /* * we get called when a new endpoint starts up. We need to allocate * the sctp_inpcb structure from the zone and init it. Mark it as * unbound and find a port that we can use as an ephemeral with * INADDR_ANY. If the user binds later no problem we can then add in * the specific addresses. And setup the default parameters for the * EP.
*/ int i, error; struct sctp_inpcb *inp; struct sctp_pcb *m; struct timeval time;
sctp_sharedkey_t *null_key;
error = 0;
SCTP_INP_INFO_WLOCK();
inp = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_ep), struct sctp_inpcb); if (inp == NULL) {
SCTP_PRINTF("Out of SCTP-INPCB structures - no resources\n");
SCTP_INP_INFO_WUNLOCK();
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, ENOBUFS); return (ENOBUFS);
} /* zap it */
memset(inp, 0, sizeof(*inp));
m->sctp_default_cc_module = SCTP_BASE_SYSCTL(sctp_default_cc_module);
m->sctp_default_ss_module = SCTP_BASE_SYSCTL(sctp_default_ss_module);
m->max_open_streams_intome = SCTP_BASE_SYSCTL(sctp_nr_incoming_streams_default); /* number of streams to pre-open on a association */
m->pre_open_stream_count = SCTP_BASE_SYSCTL(sctp_nr_outgoing_streams_default);
/* make it so new data pours into the new socket */
stcb->sctp_socket = new_inp->sctp_socket;
stcb->sctp_ep = new_inp;
/* Copy the port across */
lport = new_inp->sctp_lport = old_inp->sctp_lport;
rport = stcb->rport; /* Pull the tcb from the old association */
LIST_REMOVE(stcb, sctp_tcbhash);
LIST_REMOVE(stcb, sctp_tcblist); if (stcb->asoc.in_asocid_hash) {
LIST_REMOVE(stcb, sctp_tcbasocidhash);
} /* Now insert the new_inp into the TCP connected hash */
head = &SCTP_BASE_INFO(sctp_tcpephash)[SCTP_PCBHASH_ALLADDR((lport | rport), SCTP_BASE_INFO(hashtcpmark))];
LIST_INSERT_HEAD(head, new_inp, sctp_hash); /* Its safe to access */
new_inp->sctp_flags &= ~SCTP_PCB_FLAGS_UNBOUND;
/* Now move the tcb into the endpoint list */
LIST_INSERT_HEAD(&new_inp->sctp_asoc_list, stcb, sctp_tcblist); /* * Question, do we even need to worry about the ep-hash since we * only have one connection? Probably not :> so lets get rid of it * and not suck up any kernel memory in that.
*/ if (stcb->asoc.in_asocid_hash) { struct sctpasochead *lhd;
lhd = &new_inp->sctp_asocidhash[SCTP_PCBHASH_ASOC(stcb->asoc.assoc_id,
new_inp->hashasocidmark)];
LIST_INSERT_HEAD(lhd, stcb, sctp_tcbasocidhash);
} /* Ok. Let's restart timer. */
TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, new_inp,
stcb, net);
}
SCTP_INP_INFO_WUNLOCK(); if (new_inp->sctp_tcbhash != NULL) {
SCTP_HASH_FREE(new_inp->sctp_tcbhash, new_inp->sctp_hashmark);
new_inp->sctp_tcbhash = NULL;
} if ((new_inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) { /* Subset bound, so copy in the laddr list from the old_inp */
LIST_FOREACH(oladdr, &old_inp->sctp_addr_list, sctp_nxt_addr) {
laddr = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_laddr), struct sctp_laddr); if (laddr == NULL) { /* * Gak, what can we do? This assoc is really * HOSED. We probably should send an abort * here.
*/
SCTPDBG(SCTP_DEBUG_PCB1, "Association hosed in TCP model, out of laddr memory\n"); continue;
}
SCTP_INCR_LADDR_COUNT();
memset(laddr, 0, sizeof(*laddr));
(void)SCTP_GETTIME_TIMEVAL(&laddr->start_time);
laddr->ifa = oladdr->ifa;
atomic_add_int(&laddr->ifa->refcount, 1);
LIST_INSERT_HEAD(&new_inp->sctp_addr_list, laddr,
sctp_nxt_addr);
new_inp->laddr_count++; if (oladdr == stcb->asoc.last_used_address) {
stcb->asoc.last_used_address = laddr;
}
}
} /* Now any running timers need to be adjusted. */ if (stcb->asoc.dack_timer.ep == old_inp) {
SCTP_INP_DECR_REF(old_inp);
stcb->asoc.dack_timer.ep = new_inp;
SCTP_INP_INCR_REF(new_inp);
} if (stcb->asoc.asconf_timer.ep == old_inp) {
SCTP_INP_DECR_REF(old_inp);
stcb->asoc.asconf_timer.ep = new_inp;
SCTP_INP_INCR_REF(new_inp);
} if (stcb->asoc.strreset_timer.ep == old_inp) {
SCTP_INP_DECR_REF(old_inp);
stcb->asoc.strreset_timer.ep = new_inp;
SCTP_INP_INCR_REF(new_inp);
} if (stcb->asoc.shut_guard_timer.ep == old_inp) {
SCTP_INP_DECR_REF(old_inp);
stcb->asoc.shut_guard_timer.ep = new_inp;
SCTP_INP_INCR_REF(new_inp);
} if (stcb->asoc.autoclose_timer.ep == old_inp) {
SCTP_INP_DECR_REF(old_inp);
stcb->asoc.autoclose_timer.ep = new_inp;
SCTP_INP_INCR_REF(new_inp);
} if (stcb->asoc.delete_prim_timer.ep == old_inp) {
SCTP_INP_DECR_REF(old_inp);
stcb->asoc.delete_prim_timer.ep = new_inp;
SCTP_INP_INCR_REF(new_inp);
} /* now what about the nets? */
TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if (net->pmtu_timer.ep == old_inp) {
SCTP_INP_DECR_REF(old_inp);
net->pmtu_timer.ep = new_inp;
SCTP_INP_INCR_REF(new_inp);
} if (net->hb_timer.ep == old_inp) {
SCTP_INP_DECR_REF(old_inp);
net->hb_timer.ep = new_inp;
SCTP_INP_INCR_REF(new_inp);
} if (net->rxt_timer.ep == old_inp) {
SCTP_INP_DECR_REF(old_inp);
net->rxt_timer.ep = new_inp;
SCTP_INP_INCR_REF(new_inp);
}
}
SCTP_INP_WUNLOCK(new_inp);
SCTP_INP_WUNLOCK(old_inp);
}
/* * insert an laddr entry with the given ifa for the desired list
*/ staticint
sctp_insert_laddr(struct sctpladdr *list, struct sctp_ifa *ifa, uint32_t act)
{ struct sctp_laddr *laddr;
/* * Remove an laddr entry from the local address list (on an assoc)
*/ staticvoid
sctp_remove_laddr(struct sctp_laddr *laddr)
{
/* remove from the list */
LIST_REMOVE(laddr, sctp_nxt_addr);
sctp_free_ifa(laddr->ifa);
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_laddr), laddr);
SCTP_DECR_LADDR_COUNT();
} #if !(defined(__FreeBSD__) || defined(__APPLE__) || defined(__Userspace__))
/* * Don't know why, but without this there is an unknown reference when * compiling NetBSD... hmm
*/ externvoid in6_sin6_2_sin(struct sockaddr_in *, struct sockaddr_in6 *sin6); #endif
/* * Bind the socket, with the PCB and global info locks held. Note, if a * socket address is specified, the PCB lock may be dropped and re-acquired. * * sctp_ifap is used to bypass normal local address validation checks.
*/ int #ifdefined(__FreeBSD__) && !defined(__Userspace__)
sctp_inpcb_bind_locked(struct sctp_inpcb *inp, struct sockaddr *addr, struct sctp_ifa *sctp_ifap, struct thread *td) #elifdefined(_WIN32) && !defined(__Userspace__)
sctp_inpcb_bind_locked(struct sctp_inpcb *inp, struct sockaddr *addr, struct sctp_ifa *sctp_ifap, PKTHREAD p) #else
sctp_inpcb_bind_locked(struct sctp_inpcb *inp, struct sockaddr *addr, struct sctp_ifa *sctp_ifap, struct proc *p) #endif
{ /* bind a ep to a socket address */ struct sctppcbhead *head; struct sctp_inpcb *inp_tmp; #if (defined(__FreeBSD__) || defined(__APPLE__)) && !defined(__Userspace__) struct inpcb *ip_inp; #endif int port_reuse_active = 0; int bindall; #ifdef SCTP_MVRF int i; #endif
uint16_t lport; int error;
uint32_t vrf_id;
#ifdef HAVE_SA_LEN if (addr->sa_len != sizeof(struct sockaddr_conn)) {
error = EINVAL;
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error); goto out;
} #endif
sconn = (struct sockaddr_conn *)addr;
lport = sconn->sconn_port; if (sconn->sconn_addr != NULL) {
bindall = 0;
} break;
} #endif default:
error = EAFNOSUPPORT;
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error); goto out;
}
} /* Setup a vrf_id to be the default for the non-bind-all case. */
vrf_id = inp->def_vrf_id;
if (lport) { /* * Did the caller specify a port? if so we must see if an ep * already has this one bound.
*/ /* got to be root to get at low ports */ #if !(defined(_WIN32) && !defined(__Userspace__)) if (ntohs(lport) < IPPORT_RESERVED && #ifdefined(__FreeBSD__) && !defined(__Userspace__)
(error = priv_check(td, PRIV_NETINET_RESERVEDPORT)) != 0) { #elifdefined(__APPLE__) && !defined(__Userspace__)
(error = suser(p->p_ucred, &p->p_acflag)) != 0) { #elifdefined(__Userspace__) /* TODO ensure uid is 0, etc... */
0) { #else
(error = suser(p, 0)) != 0) { #endif goto out;
} #endif
SCTP_INP_INCR_REF(inp);
SCTP_INP_WUNLOCK(inp); if (bindall) { #ifdef SCTP_MVRF for (i = 0; i < inp->num_vrfs; i++) {
vrf_id = inp->m_vrf_ids[i]; #else
vrf_id = inp->def_vrf_id; #endif
inp_tmp = sctp_pcb_findep(addr, 0, 1, vrf_id); if (inp_tmp != NULL) { /* * lock guy returned and lower count * note that we are not bound so * inp_tmp should NEVER be inp. And * it is this inp (inp_tmp) that gets * the reference bump, so we must * lower it.
*/
SCTP_INP_DECR_REF(inp_tmp); /* unlock info */ if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PORTREUSE)) &&
(sctp_is_feature_on(inp_tmp, SCTP_PCB_FLAGS_PORTREUSE))) { /* Ok, must be one-2-one and allowing port re-use */
port_reuse_active = 1; goto continue_anyway;
}
SCTP_INP_WLOCK(inp);
SCTP_INP_DECR_REF(inp);
error = EADDRINUSE;
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error); goto out;
} #ifdef SCTP_MVRF
} #endif
} else {
inp_tmp = sctp_pcb_findep(addr, 0, 1, vrf_id); if (inp_tmp != NULL) { /* * lock guy returned and lower count note * that we are not bound so inp_tmp should * NEVER be inp. And it is this inp (inp_tmp) * that gets the reference bump, so we must * lower it.
*/
SCTP_INP_DECR_REF(inp_tmp); /* unlock info */ if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PORTREUSE)) &&
(sctp_is_feature_on(inp_tmp, SCTP_PCB_FLAGS_PORTREUSE))) { /* Ok, must be one-2-one and allowing port re-use */
port_reuse_active = 1; goto continue_anyway;
}
SCTP_INP_WLOCK(inp);
SCTP_INP_DECR_REF(inp);
error = EADDRINUSE;
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error); goto out;
}
}
continue_anyway:
SCTP_INP_WLOCK(inp);
SCTP_INP_DECR_REF(inp); if (bindall) { /* verify that no lport is not used by a singleton */ if ((port_reuse_active == 0) &&
(inp_tmp = sctp_isport_inuse(inp, lport, vrf_id))) { /* Sorry someone already has this one bound */ if ((sctp_is_feature_on(inp, SCTP_PCB_FLAGS_PORTREUSE)) &&
(sctp_is_feature_on(inp_tmp, SCTP_PCB_FLAGS_PORTREUSE))) {
port_reuse_active = 1;
} else {
error = EADDRINUSE;
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error); goto out;
}
}
}
} else {
uint16_t first, last, candidate;
uint16_t count;
#ifdefined(__Userspace__)
first = MODULE_GLOBAL(ipport_firstauto);
last = MODULE_GLOBAL(ipport_lastauto); #elifdefined(_WIN32)
first = 1;
last = 0xffff; #elifdefined(__FreeBSD__) || defined(__APPLE__) if (ip_inp->inp_flags & INP_HIGHPORT) {
first = MODULE_GLOBAL(ipport_hifirstauto);
last = MODULE_GLOBAL(ipport_hilastauto);
} elseif (ip_inp->inp_flags & INP_LOWPORT) { #ifdefined(__FreeBSD__) if ((error = priv_check(td, PRIV_NETINET_RESERVEDPORT)) != 0) { #else if ((error = suser(p->p_ucred, &p->p_acflag)) != 0) { #endif
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error); goto out;
}
first = MODULE_GLOBAL(ipport_lowfirstauto);
last = MODULE_GLOBAL(ipport_lowlastauto);
} else {
first = MODULE_GLOBAL(ipport_firstauto);
last = MODULE_GLOBAL(ipport_lastauto);
} #endif if (first > last) {
uint16_t temp;
temp = first;
first = last;
last = temp;
}
count = last - first + 1; /* number of candidates */
candidate = first + sctp_select_initial_TSN(&inp->sctp_ep) % (count);
for (;;) { #ifdef SCTP_MVRF for (i = 0; i < inp->num_vrfs; i++) { if (sctp_isport_inuse(inp, htons(candidate), inp->m_vrf_ids[i]) != NULL) { break;
}
} if (i == inp->num_vrfs) {
lport = htons(candidate); break;
} #else if (sctp_isport_inuse(inp, htons(candidate), inp->def_vrf_id) == NULL) {
lport = htons(candidate); break;
} #endif if (--count == 0) {
error = EADDRINUSE;
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error); goto out;
} if (candidate == last)
candidate = first; else
candidate = candidate + 1;
}
} if (inp->sctp_flags & (SCTP_PCB_FLAGS_SOCKET_GONE |
SCTP_PCB_FLAGS_SOCKET_ALLGONE)) { /* * this really should not happen. The guy did a non-blocking * bind and then did a close at the same time.
*/
error = EINVAL;
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error); goto out;
} /* ok we look clear to give out this port, so lets setup the binding */ if (bindall) { /* binding to all addresses, so just set in the proper flags */
inp->sctp_flags |= SCTP_PCB_FLAGS_BOUNDALL; /* set the automatic addr changes from kernel flag */ if (SCTP_BASE_SYSCTL(sctp_auto_asconf) == 0) {
sctp_feature_off(inp, SCTP_PCB_FLAGS_DO_ASCONF);
sctp_feature_off(inp, SCTP_PCB_FLAGS_AUTO_ASCONF);
} else {
sctp_feature_on(inp, SCTP_PCB_FLAGS_DO_ASCONF);
sctp_feature_on(inp, SCTP_PCB_FLAGS_AUTO_ASCONF);
} if (SCTP_BASE_SYSCTL(sctp_multiple_asconfs) == 0) {
sctp_feature_off(inp, SCTP_PCB_FLAGS_MULTIPLE_ASCONFS);
} else {
sctp_feature_on(inp, SCTP_PCB_FLAGS_MULTIPLE_ASCONFS);
} /* set the automatic mobility_base from kernel flag (by micchie)
*/ if (SCTP_BASE_SYSCTL(sctp_mobility_base) == 0) {
sctp_mobility_feature_off(inp, SCTP_MOBILITY_BASE);
sctp_mobility_feature_off(inp, SCTP_MOBILITY_PRIM_DELETED);
} else {
sctp_mobility_feature_on(inp, SCTP_MOBILITY_BASE);
sctp_mobility_feature_off(inp, SCTP_MOBILITY_PRIM_DELETED);
} /* set the automatic mobility_fasthandoff from kernel flag (by micchie)
*/ if (SCTP_BASE_SYSCTL(sctp_mobility_fasthandoff) == 0) {
sctp_mobility_feature_off(inp, SCTP_MOBILITY_FASTHANDOFF);
sctp_mobility_feature_off(inp, SCTP_MOBILITY_PRIM_DELETED);
} else {
sctp_mobility_feature_on(inp, SCTP_MOBILITY_FASTHANDOFF);
sctp_mobility_feature_off(inp, SCTP_MOBILITY_PRIM_DELETED);
}
} else { /* * bind specific, make sure flags is off and add a new * address structure to the sctp_addr_list inside the ep * structure. * * We will need to allocate one and insert it at the head. The * socketopt call can just insert new addresses in there as * well. It will also have to do the embed scope kame hack * too (before adding).
*/ struct sctp_ifa *ifa; union sctp_sockstore store;
memset(&store, 0, sizeof(store)); switch (addr->sa_family) { #ifdef INET case AF_INET:
memcpy(&store.sin, addr, sizeof(struct sockaddr_in));
store.sin.sin_port = 0; break; #endif #ifdef INET6 case AF_INET6:
memcpy(&store.sin6, addr, sizeof(struct sockaddr_in6));
store.sin6.sin6_port = 0; break; #endif #ifdefined(__Userspace__) case AF_CONN:
memcpy(&store.sconn, addr, sizeof(struct sockaddr_conn));
store.sconn.sconn_port = 0; break; #endif default: break;
} /* * first find the interface with the bound address need to * zero out the port to find the address! yuck! can't do * this earlier since need port for sctp_pcb_findep()
*/ if (sctp_ifap != NULL) {
ifa = sctp_ifap;
} else { /* Note for BSD we hit here always other * O/S's will pass things in via the * sctp_ifap argument.
*/
ifa = sctp_find_ifa_by_addr(&store.sa,
vrf_id, SCTP_ADDR_NOT_LOCKED);
} if (ifa == NULL) {
error = EADDRNOTAVAIL; /* Can't find an interface with that address */
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error); goto out;
} #ifdef INET6 if (addr->sa_family == AF_INET6) { /* GAK, more FIXME IFA lock? */ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) { /* Can't bind a non-existent addr. */
error = EINVAL;
SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_PCB, error); goto out;
}
} #endif /* we're not bound all */
inp->sctp_flags &= ~SCTP_PCB_FLAGS_BOUNDALL; /* allow bindx() to send ASCONF's for binding changes */
sctp_feature_on(inp, SCTP_PCB_FLAGS_DO_ASCONF); /* clear automatic addr changes from kernel flag */
sctp_feature_off(inp, SCTP_PCB_FLAGS_AUTO_ASCONF);
/* add this address to the endpoint list */
error = sctp_insert_laddr(&inp->sctp_addr_list, ifa, 0); if (error != 0) goto out;
inp->laddr_count++;
} /* find the bucket */ if (port_reuse_active) { /* Put it into tcp 1-2-1 hash */
head = &SCTP_BASE_INFO(sctp_tcpephash)[SCTP_PCBHASH_ALLADDR(lport, SCTP_BASE_INFO(hashtcpmark))];
inp->sctp_flags |= SCTP_PCB_FLAGS_IN_TCPPOOL;
} else {
head = &SCTP_BASE_INFO(sctp_ephash)[SCTP_PCBHASH_ALLADDR(lport, SCTP_BASE_INFO(hashmark))];
} /* put it in the bucket */
LIST_INSERT_HEAD(head, inp, sctp_hash);
SCTPDBG(SCTP_DEBUG_PCB1, "Main hash to bind at head:%p, bound port:%d - in tcp_pool=%d\n",
(void *)head, ntohs(lport), port_reuse_active); /* set in the port */
inp->sctp_lport = lport;
/* turn off just the unbound flag */
KASSERT((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) != 0,
("%s: inp %p is already bound", __func__, inp));
inp->sctp_flags &= ~SCTP_PCB_FLAGS_UNBOUND;
out: return (error);
}
/* * We enter with the only the ITERATOR_LOCK in place and a write * lock on the inp_info stuff.
*/
it = sctp_it_ctl.cur_it; #ifdefined(__FreeBSD__) && !defined(__Userspace__) if (it && (it->vn != curvnet)) { /* Its not looking at our VNET */ return;
} #endif if (it && (it->inp == inp)) { /* * This is tricky and we hold the iterator lock, * but when it returns and gets the lock (when we * release it) the iterator will try to operate on * inp. We need to stop that from happening. But * of course the iterator has a reference on the * stcb and inp. We can mark it and it will stop. * * If its a single iterator situation, we * set the end iterator flag. Otherwise * we set the iterator to go to the next inp. *
*/ if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) {
sctp_it_ctl.iterator_flags |= SCTP_ITERATOR_STOP_CUR_IT;
} else {
sctp_it_ctl.iterator_flags |= SCTP_ITERATOR_STOP_CUR_INP;
}
} /* Now go through and remove any single reference to * our inp that may be still pending on the list
*/
SCTP_IPI_ITERATOR_WQ_LOCK();
TAILQ_FOREACH_SAFE(it, &sctp_it_ctl.iteratorhead, sctp_nxt_itr, nit) { #ifdefined(__FreeBSD__) && !defined(__Userspace__) if (it->vn != curvnet) { continue;
} #endif if (it->inp == inp) { /* This one points to me is it inp specific? */ if (it->iterator_flags & SCTP_ITERATOR_DO_SINGLE_INP) { /* Remove and free this one */
TAILQ_REMOVE(&sctp_it_ctl.iteratorhead,
it, sctp_nxt_itr); if (it->function_atend != NULL) {
(*it->function_atend) (it->pointer, it->val);
}
SCTP_FREE(it, SCTP_M_ITER);
} else {
it->inp = LIST_NEXT(it->inp, sctp_list); if (it->inp) {
SCTP_INP_INCR_REF(it->inp);
}
} /* When its put in the refcnt is incremented so decr it */
SCTP_INP_DECR_REF(inp);
}
}
SCTP_IPI_ITERATOR_WQ_UNLOCK();
}
/* release sctp_inpcb unbind the port */ void
sctp_inpcb_free(struct sctp_inpcb *inp, int immediate, int from)
{ /* * Here we free a endpoint. We must find it (if it is in the Hash * table) and remove it from there. Then we must also find it in the * overall list and remove it from there. After all removals are * complete then any timer has to be stopped. Then start the actual * freeing. a) Any local lists. b) Any associations. c) The hash of * all associations. d) finally the ep itself.
*/ struct sctp_tcb *stcb, *nstcb; struct sctp_laddr *laddr, *nladdr; struct inpcb *ip_pcb; struct socket *so; int being_refed = 0; struct sctp_queued_to_read *sq, *nsq; #if !defined(__Userspace__) #if !defined(__FreeBSD__)
sctp_rtentry_t *rt; #endif #endif int cnt;
sctp_sharedkey_t *shared_key, *nshared_key;
#ifdefined(__APPLE__) && !defined(__Userspace__)
sctp_lock_assert(SCTP_INP_SO(inp)); #endif #ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, NULL, 0); #endif
SCTP_ITERATOR_LOCK(); /* mark any iterators on the list or being processed */
sctp_iterator_inp_being_freed(inp);
SCTP_ITERATOR_UNLOCK();
SCTP_ASOC_CREATE_LOCK(inp);
SCTP_INP_INFO_WLOCK();
SCTP_INP_WLOCK(inp);
so = inp->sctp_socket;
KASSERT((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) != 0,
("%s: inp %p still has socket", __func__, inp));
KASSERT((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) == 0,
("%s: double free of inp %p", __func__, inp)); if (from == SCTP_CALLED_AFTER_CMPSET_OFCLOSE) {
inp->sctp_flags &= ~SCTP_PCB_FLAGS_CLOSE_IP; /* socket is gone, so no more wakeups allowed */
inp->sctp_flags |= SCTP_PCB_FLAGS_DONT_WAKE;
inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEINPUT;
inp->sctp_flags &= ~SCTP_PCB_FLAGS_WAKEOUTPUT;
} /* First time through we have the socket lock, after that no more. */
sctp_timer_stop(SCTP_TIMER_TYPE_NEWCOOKIE, inp, NULL, NULL,
SCTP_FROM_SCTP_PCB + SCTP_LOC_1);
if (inp->control) {
sctp_m_freem(inp->control);
inp->control = NULL;
} if (inp->pkt) {
sctp_m_freem(inp->pkt);
inp->pkt = NULL;
}
ip_pcb = &inp->ip_inp.inp; /* we could just cast the main pointer * here but I will be nice :> (i.e.
* ip_pcb = ep;) */ if (immediate == SCTP_FREE_SHOULD_USE_GRACEFUL_CLOSE) { int cnt_in_sd;
cnt_in_sd = 0;
LIST_FOREACH_SAFE(stcb, &inp->sctp_asoc_list, sctp_tcblist, nstcb) {
SCTP_TCB_LOCK(stcb); /* Disconnect the socket please. */
stcb->sctp_socket = NULL;
SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_CLOSED_SOCKET); if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { /* Skip guys being freed */
cnt_in_sd++; if (stcb->asoc.state & SCTP_STATE_IN_ACCEPT_QUEUE) { /* * Special case - we did not start a kill * timer on the asoc due to it was not * closed. So go ahead and start it now.
*/
SCTP_CLEAR_SUBSTATE(stcb, SCTP_STATE_IN_ACCEPT_QUEUE);
sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL);
}
SCTP_TCB_UNLOCK(stcb); continue;
} if (((SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_WAIT) ||
(SCTP_GET_STATE(stcb) == SCTP_STATE_COOKIE_ECHOED)) &&
(stcb->asoc.total_output_queue_size == 0)) { /* If we have data in queue, we don't want to just * free since the app may have done, send()/close * or connect/send/close. And it wants the data * to get across first.
*/ /* Just abandon things in the front states */ if (sctp_free_assoc(inp, stcb, SCTP_PCBFREE_NOFORCE,
SCTP_FROM_SCTP_PCB + SCTP_LOC_2) == 0) {
cnt_in_sd++;
} continue;
} if ((stcb->asoc.size_on_reasm_queue > 0) ||
(stcb->asoc.size_on_all_streams > 0) ||
((so != NULL) && (SCTP_SBAVAIL(&so->so_rcv) > 0))) { /* Left with Data unread */ struct mbuf *op_err;
/* * there is nothing queued to send, * so I send shutdown
*/ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
(SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
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);
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_SHUT_TMR, SCTP_SO_LOCKED);
}
} else { /* mark into shutdown pending */
SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_SHUTDOWN_PENDING); if ((*stcb->asoc.ss_functions.sctp_ss_is_user_msgs_incomplete)(stcb, &stcb->asoc)) {
SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_PARTIAL_MSG_LEFT);
} if (TAILQ_EMPTY(&stcb->asoc.send_queue) &&
TAILQ_EMPTY(&stcb->asoc.sent_queue) &&
(stcb->asoc.state & SCTP_STATE_PARTIAL_MSG_LEFT)) { struct mbuf *op_err;
abort_anyway:
op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_5;
sctp_send_abort_tcb(stcb, op_err, SCTP_SO_LOCKED);
SCTP_STAT_INCR_COUNTER32(sctps_aborted); if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
(SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
SCTP_STAT_DECR_GAUGE32(sctps_currestab);
} if (sctp_free_assoc(inp, stcb,
SCTP_PCBFREE_NOFORCE,
SCTP_FROM_SCTP_PCB + SCTP_LOC_6) == 0) {
cnt_in_sd++;
} continue;
} else {
sctp_chunk_output(inp, stcb, SCTP_OUTPUT_FROM_CLOSING, SCTP_SO_LOCKED);
}
}
cnt_in_sd++;
SCTP_TCB_UNLOCK(stcb);
} /* now is there some left in our SHUTDOWN state? */ if (cnt_in_sd) { #ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, NULL, 2); #endif
inp->sctp_socket = NULL;
SCTP_INP_WUNLOCK(inp);
SCTP_ASOC_CREATE_UNLOCK(inp);
SCTP_INP_INFO_WUNLOCK(); return;
}
}
inp->sctp_socket = NULL; if ((inp->sctp_flags & SCTP_PCB_FLAGS_UNBOUND) == 0) { /* * ok, this guy has been bound. It's port is * somewhere in the SCTP_BASE_INFO(hash table). Remove * it!
*/
LIST_REMOVE(inp, sctp_hash);
inp->sctp_flags |= SCTP_PCB_FLAGS_UNBOUND;
}
/* If there is a timer running to kill us, * forget it, since it may have a contest * on the INP lock.. which would cause us * to die ...
*/
cnt = 0;
LIST_FOREACH_SAFE(stcb, &inp->sctp_asoc_list, sctp_tcblist, nstcb) {
SCTP_TCB_LOCK(stcb); if (immediate != SCTP_FREE_SHOULD_USE_GRACEFUL_CLOSE) { /* Disconnect the socket please */
stcb->sctp_socket = NULL;
SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_CLOSED_SOCKET);
} if (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) { if (stcb->asoc.state & SCTP_STATE_IN_ACCEPT_QUEUE) {
SCTP_CLEAR_SUBSTATE(stcb, SCTP_STATE_IN_ACCEPT_QUEUE);
sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL);
}
cnt++;
SCTP_TCB_UNLOCK(stcb); continue;
} /* Free associations that are NOT killing us */ if ((SCTP_GET_STATE(stcb) != SCTP_STATE_COOKIE_WAIT) &&
((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0)) { struct mbuf *op_err;
op_err = sctp_generate_cause(SCTP_CAUSE_USER_INITIATED_ABT, "");
stcb->sctp_ep->last_abort_code = SCTP_FROM_SCTP_PCB + SCTP_LOC_7;
sctp_send_abort_tcb(stcb, op_err, SCTP_SO_LOCKED);
SCTP_STAT_INCR_COUNTER32(sctps_aborted);
} elseif (stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) {
cnt++;
SCTP_TCB_UNLOCK(stcb); continue;
} if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
(SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
SCTP_STAT_DECR_GAUGE32(sctps_currestab);
} if (sctp_free_assoc(inp, stcb, SCTP_PCBFREE_FORCE,
SCTP_FROM_SCTP_PCB + SCTP_LOC_8) == 0) {
cnt++;
}
} if (cnt) { /* Ok we have someone out there that will kill us */ #ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, NULL, 3); #endif
SCTP_INP_WUNLOCK(inp);
SCTP_ASOC_CREATE_UNLOCK(inp);
SCTP_INP_INFO_WUNLOCK(); return;
} if (SCTP_INP_LOCK_CONTENDED(inp))
being_refed++; if (SCTP_INP_READ_CONTENDED(inp))
being_refed++; if (SCTP_ASOC_CREATE_LOCK_CONTENDED(inp))
being_refed++; /* NOTE: 0 refcount also means no timers are referencing us. */ if ((inp->refcount) ||
(being_refed) ||
(inp->sctp_flags & SCTP_PCB_FLAGS_CLOSE_IP)) { #ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, NULL, 4); #endif
sctp_timer_start(SCTP_TIMER_TYPE_INPKILL, inp, NULL, NULL);
SCTP_INP_WUNLOCK(inp);
SCTP_ASOC_CREATE_UNLOCK(inp);
SCTP_INP_INFO_WUNLOCK(); return;
}
inp->sctp_ep.signature_change.type = 0;
inp->sctp_flags |= SCTP_PCB_FLAGS_SOCKET_ALLGONE; /* Remove it from the list .. last thing we need a * lock for.
*/
LIST_REMOVE(inp, sctp_list);
SCTP_INP_WUNLOCK(inp);
SCTP_ASOC_CREATE_UNLOCK(inp);
SCTP_INP_INFO_WUNLOCK();
#ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, NULL, 5); #endif #if !(defined(_WIN32) || defined(__Userspace__)) #if !(defined(__FreeBSD__) && !defined(__Userspace__))
rt = ip_pcb->inp_route.ro_rt; #endif #endif if ((inp->sctp_asocidhash) != NULL) {
SCTP_HASH_FREE(inp->sctp_asocidhash, inp->hashasocidmark);
inp->sctp_asocidhash = NULL;
} /*sa_ignore FREED_MEMORY*/
TAILQ_FOREACH_SAFE(sq, &inp->read_queue, next, nsq) { /* Its only abandoned if it had data left */ if (sq->length)
SCTP_STAT_INCR(sctps_left_abandon);
TAILQ_REMOVE(&inp->read_queue, sq, next);
sctp_free_remote_addr(sq->whoFrom); if (so)
SCTP_SB_DECR(&so->so_rcv, sq->length); if (sq->data) {
sctp_m_freem(sq->data);
sq->data = NULL;
} /* * no need to free the net count, since at this point all * assoc's are gone.
*/
sctp_free_a_readq(NULL, sq);
} /* Now the sctp_pcb things */ /* * free each asoc if it is not already closed/free. we can't use the * macro here since le_next will get freed as part of the * sctp_free_assoc() call.
*/ if (ip_pcb->inp_options) {
(void)sctp_m_free(ip_pcb->inp_options);
ip_pcb->inp_options = 0;
} #if !(defined(_WIN32) || defined(__Userspace__)) #if !defined(__FreeBSD__) if (rt) {
RTFREE(rt);
ip_pcb->inp_route.ro_rt = 0;
} #endif #endif #ifdef INET6 #if !(defined(_WIN32) || defined(__Userspace__)) #if (defined(__FreeBSD__) || defined(__APPLE__) && !defined(__Userspace__)) if (ip_pcb->inp_vflag & INP_IPV6) { #else if (inp->inp_vflag & INP_IPV6) { #endif
ip6_freepcbopts(ip_pcb->in6p_outputopts);
} #endif #endif/* INET6 */
ip_pcb->inp_vflag = 0; /* free up authentication fields */ if (inp->sctp_ep.local_auth_chunks != NULL)
sctp_free_chunklist(inp->sctp_ep.local_auth_chunks); if (inp->sctp_ep.local_hmacs != NULL)
sctp_free_hmaclist(inp->sctp_ep.local_hmacs);
#ifdefined(__APPLE__) && !defined(__Userspace__)
inp->ip_inp.inp.inp_state = INPCB_STATE_DEAD; if (in_pcb_checkstate(&inp->ip_inp.inp, WNT_STOPUSING, 1) != WNT_STOPUSING) { #ifdef INVARIANTS
panic("sctp_inpcb_free inp = %p couldn't set to STOPUSING", (void *)inp); #else
SCTP_PRINTF("sctp_inpcb_free inp = %p couldn't set to STOPUSING\n", (void *)inp); #endif
}
inp->ip_inp.inp.inp_socket->so_flags |= SOF_PCBCLEARING; #endif /* * if we have an address list the following will free the list of * ifaddr's that are set into this ep. Again macro limitations here, * since the LIST_FOREACH could be a bad idea.
*/
LIST_FOREACH_SAFE(laddr, &inp->sctp_addr_list, sctp_nxt_addr, nladdr) {
sctp_remove_laddr(laddr);
}
#ifdef SCTP_TRACK_FREED_ASOCS /* TEMP CODE */
LIST_FOREACH_SAFE(stcb, &inp->sctp_asoc_free_list, sctp_tcblist, nstcb) {
LIST_REMOVE(stcb, sctp_tcblist);
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), stcb);
SCTP_DECR_ASOC_COUNT();
} /* *** END TEMP CODE ****/ #endif #ifdef SCTP_MVRF
SCTP_FREE(inp->m_vrf_ids, SCTP_M_MVRF); #endif /* Now lets see about freeing the EP hash table. */ if (inp->sctp_tcbhash != NULL) {
SCTP_HASH_FREE(inp->sctp_tcbhash, inp->sctp_hashmark);
inp->sctp_tcbhash = NULL;
} /* Now we must put the ep memory back into the zone pool */ #ifdefined(__FreeBSD__) && !defined(__Userspace__)
crfree(inp->ip_inp.inp.inp_cred);
INP_LOCK_DESTROY(&inp->ip_inp.inp); #endif
SCTP_INP_LOCK_DESTROY(inp);
SCTP_INP_READ_LOCK_DESTROY(inp);
SCTP_ASOC_CREATE_LOCK_DESTROY(inp); #if !(defined(__APPLE__) && !defined(__Userspace__))
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_ep), inp);
SCTP_DECR_EP_COUNT(); #else /* For Tiger, we will do this later... */ #endif
}
/* * add's a remote endpoint address, done with the INIT/INIT-ACK as well as * when a ASCONF arrives that adds it. It will also initialize all the cwnd * stats of stuff.
*/ int
sctp_add_remote_addr(struct sctp_tcb *stcb, struct sockaddr *newaddr, struct sctp_nets **netp, uint16_t port, int set_scope, int from)
{ /* * The following is redundant to the same lines in the * sctp_aloc_assoc() but is needed since others call the add * address function
*/ struct sctp_nets *net, *netfirst; int addr_inscope;
SCTPDBG(SCTP_DEBUG_PCB1, "Adding an address (from:%d) to the peer: ",
from);
SCTPDBG_ADDR(SCTP_DEBUG_PCB1, newaddr);
netfirst = sctp_findnet(stcb, newaddr); if (netfirst) { /* * Lie and return ok, we don't want to make the association * go away for this behavior. It will happen in the TCP * model in a connected socket. It does not reach the hash * table until after the association is built so it can't be * found. Mark as reachable, since the initial creation will * have been cleared and the NOT_IN_ASSOC flag will have * been added... and we don't want to end up removing it * back out.
*/ if (netfirst->dest_state & SCTP_ADDR_UNCONFIRMED) {
netfirst->dest_state = (SCTP_ADDR_REACHABLE |
SCTP_ADDR_UNCONFIRMED);
} else {
netfirst->dest_state = SCTP_ADDR_REACHABLE;
}
sin = (struct sockaddr_in *)newaddr; if (sin->sin_addr.s_addr == 0) { /* Invalid address */ return (-1);
} /* zero out the zero area */
memset(&sin->sin_zero, 0, sizeof(sin->sin_zero));
/* assure len is set */ #ifdef HAVE_SIN_LEN
sin->sin_len = sizeof(struct sockaddr_in); #endif if (set_scope) { if (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) {
stcb->asoc.scope.ipv4_local_scope = 1;
}
} else { /* Validate the address is in scope */ if ((IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) &&
(stcb->asoc.scope.ipv4_local_scope == 0)) {
addr_inscope = 0;
}
} break;
} #endif #ifdef INET6 case AF_INET6:
{ struct sockaddr_in6 *sin6;
sin6 = (struct sockaddr_in6 *)newaddr; if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { /* Invalid address */ return (-1);
} /* assure len is set */ #ifdef HAVE_SIN6_LEN
sin6->sin6_len = sizeof(struct sockaddr_in6); #endif if (set_scope) { if (sctp_is_address_on_local_host(newaddr, stcb->asoc.vrf_id)) {
stcb->asoc.scope.loopback_scope = 1;
stcb->asoc.scope.local_scope = 0;
stcb->asoc.scope.ipv4_local_scope = 1;
stcb->asoc.scope.site_scope = 1;
} elseif (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { /* * If the new destination is a LINK_LOCAL we * must have common site scope. Don't set * the local scope since we may not share * all links, only loopback can do this. * Links on the local network would also be * on our private network for v4 too.
*/
stcb->asoc.scope.ipv4_local_scope = 1;
stcb->asoc.scope.site_scope = 1;
} elseif (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) { /* * If the new destination is SITE_LOCAL then * we must have site scope in common.
*/
stcb->asoc.scope.site_scope = 1;
}
} else { /* Validate the address is in scope */ if (IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr) &&
(stcb->asoc.scope.loopback_scope == 0)) {
addr_inscope = 0;
} elseif (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) &&
(stcb->asoc.scope.local_scope == 0)) {
addr_inscope = 0;
} elseif (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr) &&
(stcb->asoc.scope.site_scope == 0)) {
addr_inscope = 0;
}
} break;
} #endif #ifdefined(__Userspace__) case AF_CONN:
{ struct sockaddr_conn *sconn;
/* Now generate a route for this guy */ #ifdef INET6 #ifdef SCTP_EMBEDDED_V6_SCOPE /* KAME hack: embed scopeid */ if (newaddr->sa_family == AF_INET6) { struct sockaddr_in6 *sin6;
/* JRS - Use the congestion control given in the CC module */ if (stcb->asoc.cc_functions.sctp_set_initial_cc_param != NULL)
(*stcb->asoc.cc_functions.sctp_set_initial_cc_param)(stcb, net);
/* * CMT: CUC algo - set find_pseudo_cumack to TRUE (1) at beginning * of assoc (2005/06/27, iyengar@cis.udel.edu)
*/
net->find_pseudo_cumack = 1;
net->find_rtx_pseudo_cumack = 1; #ifdefined(__FreeBSD__) && !defined(__Userspace__) /* Choose an initial flowid. */
net->flowid = stcb->asoc.my_vtag ^
ntohs(stcb->rport) ^
ntohs(stcb->sctp_ep->sctp_lport);
net->flowtype = M_HASHTYPE_OPAQUE_HASH; #endif if (netp) {
*netp = net;
}
netfirst = TAILQ_FIRST(&stcb->asoc.nets); #ifdefined(__FreeBSD__) && !defined(__Userspace__) if (net->ro.ro_nh == NULL) { #else if (net->ro.ro_rt == NULL) { #endif /* Since we have no route put it at the back */
TAILQ_INSERT_TAIL(&stcb->asoc.nets, net, sctp_next);
} elseif (netfirst == NULL) { /* We are the first one in the pool. */
TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next); #ifdefined(__FreeBSD__) && !defined(__Userspace__)
} elseif (netfirst->ro.ro_nh == NULL) { #else
} elseif (netfirst->ro.ro_rt == NULL) { #endif /* * First one has NO route. Place this one ahead of the first * one.
*/
TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next); #ifdefined(__FreeBSD__) && !defined(__Userspace__)
} elseif (net->ro.ro_nh->nh_ifp != netfirst->ro.ro_nh->nh_ifp) { #else
} elseif (net->ro.ro_rt->rt_ifp != netfirst->ro.ro_rt->rt_ifp) { #endif /* * This one has a different interface than the one at the * top of the list. Place it ahead.
*/
TAILQ_INSERT_HEAD(&stcb->asoc.nets, net, sctp_next);
} else { /* * Ok we have the same interface as the first one. Move * forward until we find either a) one with a NULL route... * insert ahead of that b) one with a different ifp.. insert * after that. c) end of the list.. insert at the tail.
*/ struct sctp_nets *netlook;
do {
netlook = TAILQ_NEXT(netfirst, sctp_next); if (netlook == NULL) { /* End of the list */
TAILQ_INSERT_TAIL(&stcb->asoc.nets, net, sctp_next); break; #ifdefined(__FreeBSD__) && !defined(__Userspace__)
} elseif (netlook->ro.ro_nh == NULL) { #else
} elseif (netlook->ro.ro_rt == NULL) { #endif /* next one has NO route */
TAILQ_INSERT_BEFORE(netfirst, net, sctp_next); break; #ifdefined(__FreeBSD__) && !defined(__Userspace__)
} elseif (netlook->ro.ro_nh->nh_ifp != net->ro.ro_nh->nh_ifp) { #else
} elseif (netlook->ro.ro_rt->rt_ifp != net->ro.ro_rt->rt_ifp) { #endif
TAILQ_INSERT_AFTER(&stcb->asoc.nets, netlook,
net, sctp_next); break;
} /* Shift forward */
netfirst = netlook;
} while (netlook != NULL);
}
/* got to have a primary set */ if (stcb->asoc.primary_destination == 0) {
stcb->asoc.primary_destination = net; #ifdefined(__FreeBSD__) && !defined(__Userspace__)
} elseif ((stcb->asoc.primary_destination->ro.ro_nh == NULL) &&
(net->ro.ro_nh) && #else
} elseif ((stcb->asoc.primary_destination->ro.ro_rt == NULL) &&
(net->ro.ro_rt) && #endif
((net->dest_state & SCTP_ADDR_UNCONFIRMED) == 0)) { /* No route to current primary adopt new primary */
stcb->asoc.primary_destination = net;
} /* Validate primary is first */
net = TAILQ_FIRST(&stcb->asoc.nets); if ((net != stcb->asoc.primary_destination) &&
(stcb->asoc.primary_destination)) { /* first one on the list is NOT the primary * sctp_cmpaddr() is much more efficient if * the primary is the first on the list, make it * so.
*/
TAILQ_REMOVE(&stcb->asoc.nets,
stcb->asoc.primary_destination, sctp_next);
TAILQ_INSERT_HEAD(&stcb->asoc.nets,
stcb->asoc.primary_destination, sctp_next);
} return (0);
}
try_again: if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { /* TSNH */ return (0);
} /* * We don't allow assoc id to be one of SCTP_FUTURE_ASSOC, * SCTP_CURRENT_ASSOC and SCTP_ALL_ASSOC.
*/ if (inp->sctp_associd_counter <= SCTP_ALL_ASSOC) {
inp->sctp_associd_counter = SCTP_ALL_ASSOC + 1;
}
id = inp->sctp_associd_counter;
inp->sctp_associd_counter++;
lstcb = sctp_findasoc_ep_asocid_locked(inp, (sctp_assoc_t)id, 0); if (lstcb) { goto try_again;
}
head = &inp->sctp_asocidhash[SCTP_PCBHASH_ASOC(id, inp->hashasocidmark)];
LIST_INSERT_HEAD(head, stcb, sctp_tcbasocidhash);
stcb->asoc.in_asocid_hash = 1; return (id);
}
/* * allocate an association and add it to the endpoint. The caller must be * careful to add all additional addresses once they are know right away or * else the assoc will be may experience a blackout scenario.
*/ staticstruct sctp_tcb *
sctp_aloc_assoc_locked(struct sctp_inpcb *inp, struct sockaddr *firstaddr, int *error, uint32_t override_tag, uint32_t initial_tsn,
uint32_t vrf_id, uint16_t o_streams, uint16_t port, #ifdefined(__FreeBSD__) && !defined(__Userspace__) struct thread *p, #elifdefined(_WIN32) && !defined(__Userspace__)
PKTHREAD p, #else #ifdefined(__Userspace__) /* __Userspace__ NULL proc is going to be passed here. See sctp_lower_sosend */ #endif struct proc *p, #endif int initialize_auth_params)
{ /* note the p argument is only valid in unbound sockets */
asoc->assoc_id = sctp_aloc_a_assoc_id(inp, stcb); /* now that my_vtag is set, add it to the hash */
head = &SCTP_BASE_INFO(sctp_asochash)[SCTP_PCBHASH_ASOC(stcb->asoc.my_vtag, SCTP_BASE_INFO(hashasocmark))]; /* put it in the bucket in the vtag hash of assoc's for the system */
LIST_INSERT_HEAD(head, stcb, sctp_asocs);
LIST_INSERT_HEAD(&inp->sctp_asoc_list, stcb, sctp_tcblist); /* now file the port under the hash as well */ if (inp->sctp_tcbhash != NULL) {
head = &inp->sctp_tcbhash[SCTP_PCBHASH_ALLADDR(stcb->rport,
inp->sctp_hashmark)];
LIST_INSERT_HEAD(head, stcb, sctp_tcbhash);
} if (initialize_auth_params == SCTP_INITIALIZE_AUTH_PARAMS) {
sctp_initialize_auth_params(inp, stcb);
}
SCTPDBG(SCTP_DEBUG_PCB1, "Association %p now allocated\n", (void *)stcb); return (stcb);
}
lnet = TAILQ_FIRST(&asoc->nets); /* Mobility adaptation Ideally, if deleted destination is the primary, it becomes a fast retransmission trigger by the subsequent SET PRIMARY. (by micchie)
*/ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
SCTP_MOBILITY_BASE) ||
sctp_is_mobility_feature_on(stcb->sctp_ep,
SCTP_MOBILITY_FASTHANDOFF)) {
SCTPDBG(SCTP_DEBUG_ASCONF1, "remove_net: primary dst is deleting\n"); if (asoc->deleted_primary != NULL) {
SCTPDBG(SCTP_DEBUG_ASCONF1, "remove_net: deleted primary may be already stored\n"); goto out;
}
asoc->deleted_primary = net;
atomic_add_int(&net->ref_count, 1);
memset(&net->lastsa, 0, sizeof(net->lastsa));
memset(&net->lastsv, 0, sizeof(net->lastsv));
sctp_mobility_feature_on(stcb->sctp_ep,
SCTP_MOBILITY_PRIM_DELETED);
sctp_timer_start(SCTP_TIMER_TYPE_PRIM_DELETED,
stcb->sctp_ep, stcb, NULL);
}
out: /* Try to find a confirmed primary */
asoc->primary_destination = sctp_find_alternate_net(stcb, lnet, 0);
} if (net == asoc->last_data_chunk_from) { /* Reset primary */
asoc->last_data_chunk_from = TAILQ_FIRST(&asoc->nets);
} if (net == asoc->last_control_chunk_from) { /* Clear net */
asoc->last_control_chunk_from = NULL;
} if (net == asoc->last_net_cmt_send_started) { /* Clear net */
asoc->last_net_cmt_send_started = NULL;
} if (net == stcb->asoc.alternate) {
sctp_free_remote_addr(stcb->asoc.alternate);
stcb->asoc.alternate = NULL;
}
sctp_timer_stop(SCTP_TIMER_TYPE_PATHMTURAISE, inp, stcb, net,
SCTP_FROM_SCTP_PCB + SCTP_LOC_9);
sctp_timer_stop(SCTP_TIMER_TYPE_HEARTBEAT, inp, stcb, net,
SCTP_FROM_SCTP_PCB + SCTP_LOC_10);
net->dest_state |= SCTP_ADDR_BEING_DELETED;
sctp_free_remote_addr(net);
}
/* * remove a remote endpoint address from an association, it will fail if the * address does not exist.
*/ int
sctp_del_remote_addr(struct sctp_tcb *stcb, struct sockaddr *remaddr)
{ /* * Here we need to remove a remote address. This is quite simple, we * first find it in the list of address for the association * (tasoc->asoc.nets) and then if it is there, we do a LIST_REMOVE * on that item. Note we do not allow it to be removed if there are * no other addresses.
*/ struct sctp_association *asoc; struct sctp_nets *net, *nnet;
asoc = &stcb->asoc;
/* locate the address */
TAILQ_FOREACH_SAFE(net, &asoc->nets, sctp_next, nnet) { if (net->ro._l_addr.sa.sa_family != remaddr->sa_family) { continue;
} if (sctp_cmpaddr((struct sockaddr *)&net->ro._l_addr,
remaddr)) { /* we found the guy */ if (asoc->numnets < 2) { /* Must have at LEAST two remote addresses */ return (-1);
} else {
sctp_remove_net(stcb, net); return (0);
}
}
} /* not found. */ return (-2);
}
SCTP_INP_INFO_WLOCK_ASSERT();
(void)SCTP_GETTIME_TIMEVAL(&now);
time = now.tv_sec + SCTP_BASE_SYSCTL(sctp_vtag_time_wait);
chain = &SCTP_BASE_INFO(vtag_timewait)[(tag % SCTP_STACK_VTAG_HASH_SIZE)];
set = false;
LIST_FOREACH(twait_block, chain, sctp_nxt_tagblock) { /* Block(s) present, lets find space, and expire on the fly */ for (i = 0; i < SCTP_NUMBER_IN_VTAG_BLOCK; i++) { if ((twait_block->vtag_block[i].v_tag == 0) && !set) {
sctp_set_vtag_block(twait_block->vtag_block + i, time, tag, lport, rport);
set = true; continue;
} if ((twait_block->vtag_block[i].v_tag != 0) &&
(twait_block->vtag_block[i].tv_sec_at_expire < now.tv_sec)) { if (set) { /* Audit expires this guy */
sctp_set_vtag_block(twait_block->vtag_block + i, 0, 0, 0, 0);
} else { /* Reuse it for the new tag */
sctp_set_vtag_block(twait_block->vtag_block + i, time, tag, lport, rport);
set = true;
}
}
} if (set) { /* * We only do up to the block where we can * place our tag for audits
*/ break;
}
} /* Need to add a new block to chain */ if (!set) {
SCTP_MALLOC(twait_block, struct sctp_tagblock *, sizeof(struct sctp_tagblock), SCTP_M_TIMW); if (twait_block == NULL) { return;
}
memset(twait_block, 0, sizeof(struct sctp_tagblock));
LIST_INSERT_HEAD(chain, twait_block, sctp_nxt_tagblock);
sctp_set_vtag_block(twait_block->vtag_block, time, tag, lport, rport);
}
}
TAILQ_FOREACH_SAFE(control, rh, next_instrm, ncontrol) {
TAILQ_REMOVE(rh, control, next_instrm);
control->on_strm_q = 0; if (control->on_read_q == 0) {
sctp_free_remote_addr(control->whoFrom); if (control->data) {
sctp_m_freem(control->data);
control->data = NULL;
}
} /* Reassembly free? */
TAILQ_FOREACH_SAFE(chk, &control->reasm, sctp_next, nchk) {
TAILQ_REMOVE(&control->reasm, chk, sctp_next); if (chk->data) {
sctp_m_freem(chk->data);
chk->data = NULL;
} if (chk->holds_key_ref)
sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
sctp_free_remote_addr(chk->whoTo);
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
SCTP_DECR_CHK_COUNT(); /*sa_ignore FREED_MEMORY*/
} /* * We don't free the address here * since all the net's were freed * above.
*/ if (control->on_read_q == 0) {
sctp_free_a_readq(stcb, control);
}
}
}
/*- * Free the association after un-hashing the remote port. This * function ALWAYS returns holding NO LOCK on the stcb. It DOES * expect that the input to this function IS a locked TCB. * It will return 0, if it did NOT destroy the association (instead * it unlocks it. It will return NON-zero if it either destroyed the * association OR the association is already destroyed.
*/ int
sctp_free_assoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb, int from_inpcbfree, int from_location)
{ int i; struct sctp_association *asoc; struct sctp_nets *net, *nnet; struct sctp_laddr *laddr, *naddr; struct sctp_tmit_chunk *chk, *nchk; struct sctp_asconf_addr *aparam, *naparam; struct sctp_asconf_ack *aack, *naack; struct sctp_stream_reset_list *strrst, *nstrrst; struct sctp_queued_to_read *sq, *nsq; struct sctp_stream_queue_pending *sp, *nsp;
sctp_sharedkey_t *shared_key, *nshared_key; struct socket *so;
/* first, lets purge the entry from the hash table. */ #ifdefined(__APPLE__) && !defined(__Userspace__)
sctp_lock_assert(SCTP_INP_SO(inp)); #endif
SCTP_TCB_LOCK_ASSERT(stcb);
#ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, stcb, 6); #endif if (stcb->asoc.state == 0) { #ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, NULL, 7); #endif /* there is no asoc, really TSNH :-0 */ return (1);
} if (stcb->asoc.alternate) {
sctp_free_remote_addr(stcb->asoc.alternate);
stcb->asoc.alternate = NULL;
} #if !(defined(__APPLE__) && !defined(__Userspace__)) /* TEMP CODE */ if (stcb->freed_from_where == 0) { /* Only record the first place free happened from */
stcb->freed_from_where = from_location;
} /* TEMP CODE */ #endif
asoc = &stcb->asoc; if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
(inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) /* nothing around */
so = NULL; else
so = inp->sctp_socket;
/* * We used timer based freeing if a reader or writer is in the way. * So we first check if we are actually being called from a timer, * if so we abort early if a reader or writer is still in the way.
*/ if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) &&
(from_inpcbfree == SCTP_NORMAL_PROC)) { /* * is it the timer driving us? if so are the reader/writers * gone?
*/ if (stcb->asoc.refcnt) { /* nope, reader or writer in the way */
sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL); /* no asoc destroyed */
SCTP_TCB_UNLOCK(stcb); #ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, stcb, 8); #endif return (0);
}
} /* Now clean up any other timers */
sctp_stop_association_timers(stcb, false); /* Now the read queue needs to be cleaned up (only once) */ if ((stcb->asoc.state & SCTP_STATE_ABOUT_TO_BE_FREED) == 0) {
SCTP_ADD_SUBSTATE(stcb, SCTP_STATE_ABOUT_TO_BE_FREED);
SCTP_INP_READ_LOCK(inp);
TAILQ_FOREACH(sq, &inp->read_queue, next) { if (sq->stcb == stcb) {
sq->do_not_ref_stcb = 1;
sq->sinfo_cumtsn = stcb->asoc.cumulative_tsn; /* If there is no end, there never * will be now.
*/ if (sq->end_added == 0) { /* Held for PD-API, clear that. */
sq->pdapi_aborted = 1;
sq->held_length = 0; if (sctp_stcb_is_feature_on(inp, stcb, SCTP_PCB_FLAGS_PDAPIEVNT) && (so != NULL)) {
sctp_ulp_notify(SCTP_NOTIFY_PARTIAL_DELVIERY_INDICATION,
stcb,
SCTP_PARTIAL_DELIVERY_ABORTED,
(void *)sq,
SCTP_SO_LOCKED);
} /* Add an end to wake them */
sq->end_added = 1;
}
}
}
SCTP_INP_READ_UNLOCK(inp); if (stcb->block_entry) {
SCTP_LTRACE_ERR_RET(inp, stcb, NULL, SCTP_FROM_SCTP_PCB, ECONNRESET);
stcb->block_entry->error = ECONNRESET;
stcb->block_entry = NULL;
}
} if ((stcb->asoc.refcnt) || (stcb->asoc.state & SCTP_STATE_IN_ACCEPT_QUEUE)) { /* Someone holds a reference OR the socket is unaccepted yet.
*/ if ((stcb->asoc.refcnt) ||
(inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
(inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) {
SCTP_CLEAR_SUBSTATE(stcb, SCTP_STATE_IN_ACCEPT_QUEUE);
sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL);
} if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
(inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) /* nothing around */
so = NULL; if (so) { /* Wake any reader/writers */
sctp_sorwakeup(inp, so);
sctp_sowwakeup(inp, so);
}
SCTP_TCB_UNLOCK(stcb);
#ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, stcb, 9); #endif /* no asoc destroyed */ return (0);
} #ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, stcb, 10); #endif /* When I reach here, no others want * to kill the assoc yet.. and I own * the lock. Now its possible an abort * comes in when I do the lock exchange * below to grab all the locks to do * the final take out. to prevent this * we increment the count, which will * start a timer and blow out above thus * assuring us that we hold exclusive * killing of the asoc. Note that * after getting back the TCB lock * we will go ahead and increment the * counter back up and stop any timer * a passing stranger may have started :-S
*/ if (from_inpcbfree == SCTP_NORMAL_PROC) {
atomic_add_int(&stcb->asoc.refcnt, 1);
SCTP_TCB_UNLOCK(stcb);
SCTP_INP_INFO_WLOCK();
SCTP_INP_WLOCK(inp);
SCTP_TCB_LOCK(stcb);
} /* Double check the GONE flag */ if ((inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) ||
(inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE)) /* nothing around */
so = NULL;
if ((inp->sctp_flags & SCTP_PCB_FLAGS_TCPTYPE) ||
(inp->sctp_flags & SCTP_PCB_FLAGS_IN_TCPPOOL)) { /* * For TCP type we need special handling when we are * connected. We also include the peel'ed off ones to.
*/ if (inp->sctp_flags & SCTP_PCB_FLAGS_CONNECTED) {
inp->sctp_flags &= ~SCTP_PCB_FLAGS_CONNECTED;
inp->sctp_flags |= SCTP_PCB_FLAGS_WAS_CONNECTED; if (so) {
SOCKBUF_LOCK(&so->so_rcv);
so->so_state &= ~(SS_ISCONNECTING |
SS_ISDISCONNECTING | #if !(defined(__FreeBSD__) && !defined(__Userspace__))
SS_ISCONFIRMING | #endif
SS_ISCONNECTED);
so->so_state |= SS_ISDISCONNECTED; #ifdefined(__APPLE__) && !defined(__Userspace__)
socantrcvmore(so); #else
socantrcvmore_locked(so); #endif
socantsendmore(so);
sctp_sowwakeup(inp, so);
sctp_sorwakeup(inp, so);
SCTP_SOWAKEUP(so);
}
}
}
/* Make it invalid too, that way if its * about to run it will abort and return.
*/ /* re-increment the lock */ if (from_inpcbfree == SCTP_NORMAL_PROC) {
atomic_subtract_int(&stcb->asoc.refcnt, 1);
} if (stcb->asoc.refcnt) {
SCTP_CLEAR_SUBSTATE(stcb, SCTP_STATE_IN_ACCEPT_QUEUE);
sctp_timer_start(SCTP_TIMER_TYPE_ASOCKILL, inp, stcb, NULL); if (from_inpcbfree == SCTP_NORMAL_PROC) {
SCTP_INP_INFO_WUNLOCK();
SCTP_INP_WUNLOCK(inp);
}
SCTP_TCB_UNLOCK(stcb); return (0);
}
asoc->state = 0; if (inp->sctp_tcbhash) {
LIST_REMOVE(stcb, sctp_tcbhash);
} if (stcb->asoc.in_asocid_hash) {
LIST_REMOVE(stcb, sctp_tcbasocidhash);
} if (inp->sctp_socket == NULL) {
stcb->sctp_socket = NULL;
} /* Now lets remove it from the list of ALL associations in the EP */
LIST_REMOVE(stcb, sctp_tcblist); if (from_inpcbfree == SCTP_NORMAL_PROC) {
SCTP_INP_INCR_REF(inp);
SCTP_INP_WUNLOCK(inp);
} /* pull from vtag hash */
LIST_REMOVE(stcb, sctp_asocs);
sctp_add_vtag_to_timewait(asoc->my_vtag, inp->sctp_lport, stcb->rport);
/* Now restop the timers to be sure * this is paranoia at is finest!
*/
sctp_stop_association_timers(stcb, true);
/* * The chunk lists and such SHOULD be empty but we check them just * in case.
*/ /* anything on the wheel needs to be removed */ for (i = 0; i < asoc->streamoutcnt; i++) { struct sctp_stream_out *outs;
outs = &asoc->strmout[i]; /* now clean up any chunks here */
TAILQ_FOREACH_SAFE(sp, &outs->outqueue, next, nsp) {
atomic_subtract_int(&asoc->stream_queue_cnt, 1);
TAILQ_REMOVE(&outs->outqueue, sp, next);
stcb->asoc.ss_functions.sctp_ss_remove_from_stream(stcb, asoc, outs, sp);
sctp_free_spbufspace(stcb, asoc, sp); if (sp->data) { if (so) { /* Still an open socket - report */
sctp_ulp_notify(SCTP_NOTIFY_SPECIAL_SP_FAIL, stcb,
0, (void *)sp, SCTP_SO_LOCKED);
} if (sp->data) {
sctp_m_freem(sp->data);
sp->data = NULL;
sp->tail_mbuf = NULL;
sp->length = 0;
}
} if (sp->net) {
sctp_free_remote_addr(sp->net);
sp->net = NULL;
}
sctp_free_a_strmoq(stcb, sp, SCTP_SO_LOCKED);
}
} /*sa_ignore FREED_MEMORY*/
TAILQ_FOREACH_SAFE(strrst, &asoc->resetHead, next_resp, nstrrst) {
TAILQ_REMOVE(&asoc->resetHead, strrst, next_resp);
SCTP_FREE(strrst, SCTP_M_STRESET);
}
TAILQ_FOREACH_SAFE(sq, &asoc->pending_reply_queue, next, nsq) {
TAILQ_REMOVE(&asoc->pending_reply_queue, sq, next); if (sq->data) {
sctp_m_freem(sq->data);
sq->data = NULL;
}
sctp_free_remote_addr(sq->whoFrom);
sq->whoFrom = NULL;
sq->stcb = NULL; /* Free the ctl entry */
sctp_free_a_readq(stcb, sq); /*sa_ignore FREED_MEMORY*/
}
TAILQ_FOREACH_SAFE(chk, &asoc->free_chunks, sctp_next, nchk) {
TAILQ_REMOVE(&asoc->free_chunks, chk, sctp_next); if (chk->data) {
sctp_m_freem(chk->data);
chk->data = NULL;
} if (chk->holds_key_ref)
sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
SCTP_DECR_CHK_COUNT();
atomic_subtract_int(&SCTP_BASE_INFO(ipi_free_chunks), 1);
asoc->free_chunk_cnt--; /*sa_ignore FREED_MEMORY*/
} /* pending send queue SHOULD be empty */
TAILQ_FOREACH_SAFE(chk, &asoc->send_queue, sctp_next, nchk) { if (asoc->strmout[chk->rec.data.sid].chunks_on_queues > 0) {
asoc->strmout[chk->rec.data.sid].chunks_on_queues--; #ifdef INVARIANTS
} else {
panic("No chunks on the queues for sid %u.", chk->rec.data.sid); #endif
}
TAILQ_REMOVE(&asoc->send_queue, chk, sctp_next); if (chk->data) { if (so) { /* Still a socket? */
sctp_ulp_notify(SCTP_NOTIFY_UNSENT_DG_FAIL, stcb,
0, chk, SCTP_SO_LOCKED);
} if (chk->data) {
sctp_m_freem(chk->data);
chk->data = NULL;
}
} if (chk->holds_key_ref)
sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED); if (chk->whoTo) {
sctp_free_remote_addr(chk->whoTo);
chk->whoTo = NULL;
}
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
SCTP_DECR_CHK_COUNT(); /*sa_ignore FREED_MEMORY*/
} /* sent queue SHOULD be empty */
TAILQ_FOREACH_SAFE(chk, &asoc->sent_queue, sctp_next, nchk) { if (chk->sent != SCTP_DATAGRAM_NR_ACKED) { if (asoc->strmout[chk->rec.data.sid].chunks_on_queues > 0) {
asoc->strmout[chk->rec.data.sid].chunks_on_queues--; #ifdef INVARIANTS
} else {
panic("No chunks on the queues for sid %u.", chk->rec.data.sid); #endif
}
}
TAILQ_REMOVE(&asoc->sent_queue, chk, sctp_next); if (chk->data) { if (so) { /* Still a socket? */
sctp_ulp_notify(SCTP_NOTIFY_SENT_DG_FAIL, stcb,
0, chk, SCTP_SO_LOCKED);
} if (chk->data) {
sctp_m_freem(chk->data);
chk->data = NULL;
}
} if (chk->holds_key_ref)
sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
sctp_free_remote_addr(chk->whoTo);
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
SCTP_DECR_CHK_COUNT(); /*sa_ignore FREED_MEMORY*/
} #ifdef INVARIANTS for (i = 0; i < stcb->asoc.streamoutcnt; i++) { if (stcb->asoc.strmout[i].chunks_on_queues > 0) {
panic("%u chunks left for stream %u.", stcb->asoc.strmout[i].chunks_on_queues, i);
}
} #endif /* control queue MAY not be empty */
TAILQ_FOREACH_SAFE(chk, &asoc->control_send_queue, sctp_next, nchk) {
TAILQ_REMOVE(&asoc->control_send_queue, chk, sctp_next); if (chk->data) {
sctp_m_freem(chk->data);
chk->data = NULL;
} if (chk->holds_key_ref)
sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
sctp_free_remote_addr(chk->whoTo);
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
SCTP_DECR_CHK_COUNT(); /*sa_ignore FREED_MEMORY*/
} /* ASCONF queue MAY not be empty */
TAILQ_FOREACH_SAFE(chk, &asoc->asconf_send_queue, sctp_next, nchk) {
TAILQ_REMOVE(&asoc->asconf_send_queue, chk, sctp_next); if (chk->data) {
sctp_m_freem(chk->data);
chk->data = NULL;
} if (chk->holds_key_ref)
sctp_auth_key_release(stcb, chk->auth_keyid, SCTP_SO_LOCKED);
sctp_free_remote_addr(chk->whoTo);
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_chunk), chk);
SCTP_DECR_CHK_COUNT(); /*sa_ignore FREED_MEMORY*/
} if (asoc->mapping_array) {
SCTP_FREE(asoc->mapping_array, SCTP_M_MAP);
asoc->mapping_array = NULL;
} if (asoc->nr_mapping_array) {
SCTP_FREE(asoc->nr_mapping_array, SCTP_M_MAP);
asoc->nr_mapping_array = NULL;
} /* the stream outs */ if (asoc->strmout) {
SCTP_FREE(asoc->strmout, SCTP_M_STRMO);
asoc->strmout = NULL;
}
asoc->strm_realoutsize = asoc->streamoutcnt = 0; if (asoc->strmin) { for (i = 0; i < asoc->streamincnt; i++) {
sctp_clean_up_stream(stcb, &asoc->strmin[i].inqueue);
sctp_clean_up_stream(stcb, &asoc->strmin[i].uno_inqueue);
}
SCTP_FREE(asoc->strmin, SCTP_M_STRMI);
asoc->strmin = NULL;
}
asoc->streamincnt = 0;
TAILQ_FOREACH_SAFE(net, &asoc->nets, sctp_next, nnet) { #ifdef INVARIANTS if (SCTP_BASE_INFO(ipi_count_raddr) == 0) {
panic("no net's left alloc'ed, or list points to itself");
} #endif
TAILQ_REMOVE(&asoc->nets, net, sctp_next);
sctp_free_remote_addr(net);
}
LIST_FOREACH_SAFE(laddr, &asoc->sctp_restricted_addrs, sctp_nxt_addr, naddr) { /*sa_ignore FREED_MEMORY*/
sctp_remove_laddr(laddr);
}
/* Get rid of LOCK */
SCTP_TCB_UNLOCK(stcb);
SCTP_TCB_LOCK_DESTROY(stcb); if (from_inpcbfree == SCTP_NORMAL_PROC) {
SCTP_INP_INFO_WUNLOCK();
SCTP_INP_RLOCK(inp);
} #ifdefined(__APPLE__) && !defined(__Userspace__) /* TEMP CODE */
stcb->freed_from_where = from_location; #endif #ifdef SCTP_TRACK_FREED_ASOCS if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { /* now clean up the tasoc itself */
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), stcb);
SCTP_DECR_ASOC_COUNT();
} else {
LIST_INSERT_HEAD(&inp->sctp_asoc_free_list, stcb, sctp_tcblist);
} #else
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asoc), stcb);
SCTP_DECR_ASOC_COUNT(); #endif if (from_inpcbfree == SCTP_NORMAL_PROC) { if (inp->sctp_flags & SCTP_PCB_FLAGS_SOCKET_GONE) { /* If its NOT the inp_free calling us AND * sctp_close as been called, we * call back...
*/
SCTP_INP_RUNLOCK(inp); /* This will start the kill timer (if we are * the last one) since we hold an increment yet. But * this is the only safe way to do this * since otherwise if the socket closes * at the same time we are here we might * collide in the cleanup.
*/
sctp_inpcb_free(inp,
SCTP_FREE_SHOULD_USE_GRACEFUL_CLOSE,
SCTP_CALLED_DIRECTLY_NOCMPSET);
SCTP_INP_DECR_REF(inp);
} else { /* The socket is still open. */
SCTP_INP_DECR_REF(inp);
SCTP_INP_RUNLOCK(inp);
}
} /* destroyed the asoc */ #ifdef SCTP_LOG_CLOSING
sctp_log_closing(inp, NULL, 11); #endif return (1);
}
/* * determine if a destination is "reachable" based upon the addresses bound * to the current endpoint (e.g. only v4 or v6 currently bound)
*/ /* * FIX: if we allow assoc-level bindx(), then this needs to be fixed to use * assoc level v4/v6 flags, as the assoc *may* not have the same address * types bound as its endpoint
*/ int
sctp_destination_is_reachable(struct sctp_tcb *stcb, struct sockaddr *destaddr)
{ struct sctp_inpcb *inp; int answer;
/* * No locks here, the TCB, in all cases is already locked and an * assoc is up. There is either a INP lock by the caller applied (in * asconf case when deleting an address) or NOT in the HB case, * however if HB then the INP increment is up and the INP will not * be removed (on top of the fact that we have a TCB lock). So we * only want to read the sctp_flags, which is either bound-all or * not.. no protection needed since once an assoc is up you can't be * changing your binding.
*/
inp = stcb->sctp_ep; if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { /* if bound all, destination is not restricted */ /* * RRS: Question during lock work: Is this correct? If you * are bound-all you still might need to obey the V4--V6 * flags??? IMO this bound-all stuff needs to be removed!
*/ return (1);
} /* NOTE: all "scope" checks are done when local addresses are added */ switch (destaddr->sa_family) { #ifdef INET6 case AF_INET6:
answer = inp->ip_inp.inp.inp_vflag & INP_IPV6; break; #endif #ifdef INET case AF_INET:
answer = inp->ip_inp.inp.inp_vflag & INP_IPV4; break; #endif #ifdefined(__Userspace__) case AF_CONN:
answer = inp->ip_inp.inp.inp_vflag & INP_CONN; break; #endif default: /* invalid family, so it's unreachable */
answer = 0; break;
} return (answer);
}
/* * update the inp_vflags on an endpoint
*/ staticvoid
sctp_update_ep_vflag(struct sctp_inpcb *inp)
{ struct sctp_laddr *laddr;
/* first clear the flag */
inp->ip_inp.inp.inp_vflag = 0; /* set the flag based on addresses on the ep list */
LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == NULL) {
SCTPDBG(SCTP_DEBUG_PCB1, "%s: NULL ifa\n",
__func__); continue;
}
/* * Add the address to the endpoint local address list There is nothing to be * done if we are bound to all addresses
*/ void
sctp_add_local_addr_ep(struct sctp_inpcb *inp, struct sctp_ifa *ifa, uint32_t action)
{ struct sctp_laddr *laddr; struct sctp_tcb *stcb; int fnd, error = 0;
fnd = 0;
if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { /* You are already bound to all. You have it already */ return;
} #ifdef INET6 if (ifa->address.sa.sa_family == AF_INET6) { if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) { /* Can't bind a non-useable addr. */ return;
}
} #endif /* first, is it already present? */
LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == ifa) {
fnd = 1; break;
}
}
if (fnd == 0) { /* Not in the ep list */
error = sctp_insert_laddr(&inp->sctp_addr_list, ifa, action); if (error != 0) return;
inp->laddr_count++; /* update inp_vflag flags */ switch (ifa->address.sa.sa_family) { #ifdef INET6 case AF_INET6:
inp->ip_inp.inp.inp_vflag |= INP_IPV6; break; #endif #ifdef INET case AF_INET:
inp->ip_inp.inp.inp_vflag |= INP_IPV4; break; #endif #ifdefined(__Userspace__) case AF_CONN:
inp->ip_inp.inp.inp_vflag |= INP_CONN; break; #endif default: break;
}
LIST_FOREACH(stcb, &inp->sctp_asoc_list, sctp_tcblist) {
sctp_add_local_addr_restricted(stcb, ifa);
}
} return;
}
/* * select a new (hopefully reachable) destination net (should only be used * when we deleted an ep addr that is the only usable source address to reach * the destination net)
*/ staticvoid
sctp_select_primary_destination(struct sctp_tcb *stcb)
{ struct sctp_nets *net;
TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { /* for now, we'll just pick the first reachable one we find */ if (net->dest_state & SCTP_ADDR_UNCONFIRMED) continue; if (sctp_destination_is_reachable(stcb,
(struct sockaddr *)&net->ro._l_addr)) { /* found a reachable destination */
stcb->asoc.primary_destination = net;
}
} /* I can't there from here! ...we're gonna die shortly... */
}
/* * Delete the address from the endpoint local address list. There is nothing * to be done if we are bound to all addresses
*/ void
sctp_del_local_addr_ep(struct sctp_inpcb *inp, struct sctp_ifa *ifa)
{ struct sctp_laddr *laddr; int fnd;
fnd = 0; if (inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) { /* You are already bound to all. You have it already */ return;
}
LIST_FOREACH(laddr, &inp->sctp_addr_list, sctp_nxt_addr) { if (laddr->ifa == ifa) {
fnd = 1; break;
}
} if (fnd && (inp->laddr_count < 2)) { /* can't delete unless there are at LEAST 2 addresses */ return;
} if (fnd) { /* * clean up any use of this address go through our * associations and clear any last_used_address that match * this one for each assoc, see if a new primary_destination * is needed
*/ struct sctp_tcb *stcb;
/* clean up "next_addr_touse" */ if (inp->next_addr_touse == laddr) /* delete this address */
inp->next_addr_touse = NULL;
SCTP_TCB_LOCK(stcb); if (stcb->asoc.last_used_address == laddr) /* delete this address */
stcb->asoc.last_used_address = NULL; /* Now spin through all the nets and purge any ref to laddr */
TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { if (net->ro._s_addr == laddr->ifa) { /* Yep, purge src address selected */ #ifdefined(__FreeBSD__) && !defined(__Userspace__)
RO_NHFREE(&net->ro); #else
sctp_rtentry_t *rt;
/* delete this address if cached */
rt = net->ro.ro_rt; if (rt != NULL) {
RTFREE(rt);
net->ro.ro_rt = NULL;
} #endif
sctp_free_ifa(net->ro._s_addr);
net->ro._s_addr = NULL;
net->src_addr_selected = 0;
}
}
SCTP_TCB_UNLOCK(stcb);
} /* for each tcb */ /* remove it from the ep list */
sctp_remove_laddr(laddr);
inp->laddr_count--; /* update inp_vflag flags */
sctp_update_ep_vflag(inp);
} return;
}
/* * Add the address to the TCB local address restricted list. * This is a "pending" address list (eg. addresses waiting for an * ASCONF-ACK response) and cannot be used as a valid source address.
*/ void
sctp_add_local_addr_restricted(struct sctp_tcb *stcb, struct sctp_ifa *ifa)
{ struct sctp_laddr *laddr; struct sctpladdr *list;
/* * Assumes TCB is locked.. and possibly the INP. May need to * confirm/fix that if we need it and is not the case.
*/
list = &stcb->asoc.sctp_restricted_addrs;
#ifdef INET6 if (ifa->address.sa.sa_family == AF_INET6) { if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) { /* Can't bind a non-existent addr. */ return;
}
} #endif /* does the address already exist? */
LIST_FOREACH(laddr, list, sctp_nxt_addr) { if (laddr->ifa == ifa) { return;
}
}
/* add to the list */
(void)sctp_insert_laddr(list, ifa, 0); return;
}
/* * Remove a local address from the TCB local address restricted list
*/ void
sctp_del_local_addr_restricted(struct sctp_tcb *stcb, struct sctp_ifa *ifa)
{ struct sctp_inpcb *inp; struct sctp_laddr *laddr;
/* * This is called by asconf work. It is assumed that a) The TCB is * locked and b) The INP is locked. This is true in as much as I can * trace through the entry asconf code where I did these locks. * Again, the ASCONF code is a bit different in that it does lock * the INP during its work often times. This must be since we don't * want other proc's looking up things while what they are looking * up is changing :-D
*/
inp = stcb->sctp_ep; /* if subset bound and don't allow ASCONF's, can't delete last */ if (((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) &&
sctp_is_feature_off(inp, SCTP_PCB_FLAGS_DO_ASCONF)) { if (stcb->sctp_ep->laddr_count < 2) { /* can't delete last address */ return;
}
}
LIST_FOREACH(laddr, &stcb->asoc.sctp_restricted_addrs, sctp_nxt_addr) { /* remove the address if it exists */ if (laddr->ifa == NULL) continue; if (laddr->ifa == ifa) {
sctp_remove_laddr(laddr); return;
}
}
void
sctp_queue_to_mcore(struct mbuf *m, int off, int cpu_to_use)
{ /* Queue a packet to a processor for the specified core */ struct sctp_mcore_queue *qent; struct sctp_mcore_ctrl *wkq; int need_wake = 0;
if (sctp_mcore_workers == NULL) { /* Something went way bad during setup */
sctp_input_with_port(m, off, 0); return;
}
SCTP_MALLOC(qent, struct sctp_mcore_queue *,
(sizeof(struct sctp_mcore_queue)),
SCTP_M_MCORE); if (qent == NULL) { /* This is trouble */
sctp_input_with_port(m, off, 0); return;
}
qent->vn = curvnet;
qent->m = m;
qent->off = off;
qent->v6 = 0;
wkq = &sctp_mcore_workers[cpu_to_use];
SCTP_MCORE_QLOCK(wkq);
TAILQ_INSERT_TAIL(&wkq->que, qent, next); if (wkq->running == 0) {
need_wake = 1;
}
SCTP_MCORE_QUNLOCK(wkq); if (need_wake) {
wakeup(&wkq->running);
}
}
#endif void #ifdefined(__Userspace__)
sctp_pcb_init(int start_threads) #else
sctp_pcb_init(void) #endif
{ /* * SCTP initialization for the PCB structures should be called by * the sctp_init() function.
*/ int i; struct timeval tv;
if (SCTP_BASE_VAR(sctp_pcb_initialized) != 0) { /* error I was called twice */ return;
}
SCTP_BASE_VAR(sctp_pcb_initialized) = 1;
SCTP_BASE_INFO(vrf_ifn_hash) = SCTP_HASH_INIT(SCTP_VRF_IFN_HASH_SIZE,
&SCTP_BASE_INFO(vrf_ifn_hashmark)); /* init the zones */ /* * FIX ME: Should check for NULL returns, but if it does fail we are * doomed to panic anyways... add later maybe.
*/
SCTP_ZONE_INIT(SCTP_BASE_INFO(ipi_zone_ep), "sctp_ep", sizeof(struct sctp_inpcb), maxsockets);
SCTP_WQ_ADDR_INIT(); /* not sure if we need all the counts */
SCTP_BASE_INFO(ipi_count_ep) = 0; /* assoc/tcb zone info */
SCTP_BASE_INFO(ipi_count_asoc) = 0; /* local addrlist zone info */
SCTP_BASE_INFO(ipi_count_laddr) = 0; /* remote addrlist zone info */
SCTP_BASE_INFO(ipi_count_raddr) = 0; /* chunk info */
SCTP_BASE_INFO(ipi_count_chunk) = 0;
/* socket queue zone info */
SCTP_BASE_INFO(ipi_count_readq) = 0;
/* stream out queue cont */
SCTP_BASE_INFO(ipi_count_strmoq) = 0;
/* * INIT the default VRF which for BSD is the only one, other O/S's * may have more. But initially they must start with one and then * add the VRF's as addresses are added.
*/
sctp_init_vrf_list(SCTP_DEFAULT_VRF); #ifdefined(__FreeBSD__) && !defined(__Userspace__) && defined(SCTP_NOT_YET) if (ip_register_flow_handler(sctp_netisr_hdlr, IPPROTO_SCTP)) {
SCTP_PRINTF("***SCTP- Error can't register netisr handler***\n");
} #endif #ifdefined(_SCTP_NEEDS_CALLOUT_) || defined(_USER_SCTP_NEEDS_CALLOUT_) /* allocate the lock for the callout/timer queue */
SCTP_TIMERQ_LOCK_INIT();
TAILQ_INIT(&SCTP_BASE_INFO(callqueue)); #endif #ifdefined(__Userspace__)
mbuf_initialize(NULL);
atomic_init(); #ifdefined(INET) || defined(INET6) if (start_threads)
recv_thread_init(); #endif #endif
}
/* * Assumes that the SCTP_BASE_INFO() lock is NOT held.
*/ void
sctp_pcb_finish(void)
{ struct sctp_vrflist *vrf_bucket; struct sctp_vrf *vrf, *nvrf; struct sctp_ifn *ifn, *nifn; struct sctp_ifa *ifa, *nifa; struct sctpvtaghead *chain; struct sctp_tagblock *twait_block, *prev_twait_block; struct sctp_laddr *wi, *nwi; int i; struct sctp_iterator *it, *nit;
if (SCTP_BASE_VAR(sctp_pcb_initialized) == 0) {
SCTP_PRINTF("%s: race condition on teardown.\n", __func__); return;
}
SCTP_BASE_VAR(sctp_pcb_initialized) = 0; #if !(defined(__FreeBSD__) && !defined(__Userspace__)) /* Notify the iterator to exit. */
SCTP_IPI_ITERATOR_WQ_LOCK();
sctp_it_ctl.iterator_flags |= SCTP_ITERATOR_MUST_EXIT;
sctp_wakeup_iterator();
SCTP_IPI_ITERATOR_WQ_UNLOCK(); #endif #ifdefined(__APPLE__) && !defined(__Userspace__) #if !defined(APPLE_LEOPARD) && !defined(APPLE_SNOWLEOPARD) && !defined(APPLE_LION) && !defined(APPLE_MOUNTAINLION)
in_pcbinfo_detach(&SCTP_BASE_INFO(sctbinfo)); #endif
SCTP_IPI_ITERATOR_WQ_LOCK(); do {
msleep(&sctp_it_ctl.iterator_flags,
sctp_it_ctl.ipi_iterator_wq_mtx,
0, "waiting_for_work", 0);
} while ((sctp_it_ctl.iterator_flags & SCTP_ITERATOR_EXITED) == 0);
thread_deallocate(sctp_it_ctl.thread_proc);
SCTP_IPI_ITERATOR_WQ_UNLOCK(); #endif #ifdefined(_WIN32) && !defined(__Userspace__) if (sctp_it_ctl.iterator_thread_obj != NULL) {
NTSTATUS status = STATUS_SUCCESS;
KeSetEvent(&sctp_it_ctl.iterator_wakeup[1], IO_NO_INCREMENT, FALSE);
status = KeWaitForSingleObject(sctp_it_ctl.iterator_thread_obj,
Executive,
KernelMode, FALSE,
NULL);
ObDereferenceObject(sctp_it_ctl.iterator_thread_obj);
} #endif #ifdefined(__Userspace__) if (SCTP_BASE_VAR(iterator_thread_started)) { #ifdefined(_WIN32)
WaitForSingleObject(sctp_it_ctl.thread_proc, INFINITE);
CloseHandle(sctp_it_ctl.thread_proc);
sctp_it_ctl.thread_proc = NULL; #else
pthread_join(sctp_it_ctl.thread_proc, NULL);
sctp_it_ctl.thread_proc = 0; #endif
} #endif #ifdefined(SCTP_PROCESS_LEVEL_LOCKS) #ifdefined(_WIN32)
DeleteConditionVariable(&sctp_it_ctl.iterator_wakeup); #else
pthread_cond_destroy(&sctp_it_ctl.iterator_wakeup);
pthread_mutexattr_destroy(&SCTP_BASE_VAR(mtx_attr));
pthread_rwlockattr_destroy(&SCTP_BASE_VAR(rwlock_attr)); #endif #endif /* In FreeBSD the iterator thread never exits * but we do clean up. * The only way FreeBSD reaches here is if we have VRF's * but we still add the ifdef to make it compile on old versions.
*/ #ifdefined(__FreeBSD__) && !defined(__Userspace__)
retry: #endif
SCTP_IPI_ITERATOR_WQ_LOCK(); #ifdefined(__FreeBSD__) && !defined(__Userspace__) /* * sctp_iterator_worker() might be working on an it entry without * holding the lock. We won't find it on the list either and * continue and free/destroy it. While holding the lock, spin, to * avoid the race condition as sctp_iterator_worker() will have to * wait to re-acquire the lock.
*/ if (sctp_it_ctl.iterator_running != 0 || sctp_it_ctl.cur_it != NULL) {
SCTP_IPI_ITERATOR_WQ_UNLOCK();
SCTP_PRINTF("%s: Iterator running while we held the lock. Retry. " "cur_it=%p\n", __func__, sctp_it_ctl.cur_it);
DELAY(10); goto retry;
} #endif
TAILQ_FOREACH_SAFE(it, &sctp_it_ctl.iteratorhead, sctp_nxt_itr, nit) { #ifdefined(__FreeBSD__) && !defined(__Userspace__) if (it->vn != curvnet) { continue;
} #endif
TAILQ_REMOVE(&sctp_it_ctl.iteratorhead, it, sctp_nxt_itr); if (it->function_atend != NULL) {
(*it->function_atend) (it->pointer, it->val);
}
SCTP_FREE(it,SCTP_M_ITER);
}
SCTP_IPI_ITERATOR_WQ_UNLOCK(); #ifdefined(__FreeBSD__) && !defined(__Userspace__)
SCTP_ITERATOR_LOCK(); if ((sctp_it_ctl.cur_it) &&
(sctp_it_ctl.cur_it->vn == curvnet)) {
sctp_it_ctl.iterator_flags |= SCTP_ITERATOR_STOP_CUR_IT;
}
SCTP_ITERATOR_UNLOCK(); #endif #if !(defined(__FreeBSD__) && !defined(__Userspace__))
SCTP_IPI_ITERATOR_WQ_DESTROY();
SCTP_ITERATOR_LOCK_DESTROY(); #endif
SCTP_OS_TIMER_STOP_DRAIN(&SCTP_BASE_INFO(addr_wq_timer.timer));
SCTP_WQ_ADDR_LOCK();
LIST_FOREACH_SAFE(wi, &SCTP_BASE_INFO(addr_wq), sctp_nxt_addr, nwi) {
LIST_REMOVE(wi, sctp_nxt_addr);
SCTP_DECR_LADDR_COUNT(); if (wi->action == SCTP_DEL_IP_ADDRESS) {
SCTP_FREE(wi->ifa, SCTP_M_IFA);
}
SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_laddr), wi);
}
SCTP_WQ_ADDR_UNLOCK();
/* free the TIMEWAIT list elements malloc'd in the function * sctp_add_vtag_to_timewait()...
*/ for (i = 0; i < SCTP_STACK_VTAG_HASH_SIZE; i++) {
chain = &SCTP_BASE_INFO(vtag_timewait)[i]; if (!LIST_EMPTY(chain)) {
prev_twait_block = NULL;
LIST_FOREACH(twait_block, chain, sctp_nxt_tagblock) { if (prev_twait_block) {
SCTP_FREE(prev_twait_block, SCTP_M_TIMW);
}
prev_twait_block = twait_block;
}
SCTP_FREE(prev_twait_block, SCTP_M_TIMW);
}
}
int
sctp_load_addresses_from_init(struct sctp_tcb *stcb, struct mbuf *m, int offset, int limit, struct sockaddr *src, struct sockaddr *dst, struct sockaddr *altsa, uint16_t port)
{ /* * grub through the INIT pulling addresses and loading them to the * nets structure in the asoc. The from address in the mbuf should * also be loaded (if it is not already). This routine can be called * with either INIT or INIT-ACK's as long as the m points to the IP * packet and the offset points to the beginning of the parameters.
*/ struct sctp_inpcb *inp; struct sctp_nets *net, *nnet, *net_tmp; struct sctp_paramhdr *phdr, param_buf; struct sctp_tcb *stcb_tmp;
uint16_t ptype, plen; struct sockaddr *sa;
uint8_t random_store[SCTP_PARAM_BUFFER_SIZE]; struct sctp_auth_random *p_random = NULL;
uint16_t random_len = 0;
uint8_t hmacs_store[SCTP_PARAM_BUFFER_SIZE]; struct sctp_auth_hmac_algo *hmacs = NULL;
uint16_t hmacs_len = 0;
uint8_t saw_asconf = 0;
uint8_t saw_asconf_ack = 0;
uint8_t chunks_store[SCTP_PARAM_BUFFER_SIZE]; struct sctp_auth_chunk_list *chunks = NULL;
uint16_t num_chunks = 0;
sctp_key_t *new_key;
uint32_t keylen; int got_random = 0, got_hmacs = 0, got_chklist = 0;
uint8_t peer_supports_ecn;
uint8_t peer_supports_prsctp;
uint8_t peer_supports_auth;
uint8_t peer_supports_asconf;
uint8_t peer_supports_asconf_ack;
uint8_t peer_supports_reconfig;
uint8_t peer_supports_nrsack;
uint8_t peer_supports_pktdrop;
uint8_t peer_supports_idata; #ifdef INET struct sockaddr_in sin; #endif #ifdef INET6 struct sockaddr_in6 sin6; #endif
/* First get the destination address setup too. */ #ifdef INET
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET; #ifdef HAVE_SIN_LEN
sin.sin_len = sizeof(sin); #endif
sin.sin_port = stcb->rport; #endif #ifdef INET6
memset(&sin6, 0, sizeof(sin6));
sin6.sin6_family = AF_INET6; #ifdef HAVE_SIN6_LEN
sin6.sin6_len = sizeof(struct sockaddr_in6); #endif
sin6.sin6_port = stcb->rport; #endif if (altsa) {
sa = altsa;
} else {
sa = src;
}
peer_supports_idata = 0;
peer_supports_ecn = 0;
peer_supports_prsctp = 0;
peer_supports_auth = 0;
peer_supports_asconf = 0;
peer_supports_asconf_ack = 0;
peer_supports_reconfig = 0;
peer_supports_nrsack = 0;
peer_supports_pktdrop = 0;
TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) { /* mark all addresses that we have currently on the list */
net->dest_state |= SCTP_ADDR_NOT_IN_ASSOC;
} /* does the source address already exist? if so skip it */
inp = stcb->sctp_ep;
atomic_add_int(&stcb->asoc.refcnt, 1);
stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net_tmp, dst, stcb);
atomic_subtract_int(&stcb->asoc.refcnt, 1);
if ((stcb_tmp == NULL && inp == stcb->sctp_ep) || inp == NULL) { /* we must add the source address */ /* no scope set here since we have a tcb already. */ switch (sa->sa_family) { #ifdef INET case AF_INET: if (stcb->asoc.scope.ipv4_addr_legal) { if (sctp_add_remote_addr(stcb, sa, NULL, port, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_2)) { return (-1);
}
} break; #endif #ifdef INET6 case AF_INET6: if (stcb->asoc.scope.ipv6_addr_legal) { if (sctp_add_remote_addr(stcb, sa, NULL, port, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_3)) { return (-2);
}
} break; #endif #ifdefined(__Userspace__) case AF_CONN: if (stcb->asoc.scope.conn_addr_legal) { if (sctp_add_remote_addr(stcb, sa, NULL, port, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_3)) { return (-2);
}
} break; #endif default: break;
}
} else { if (net_tmp != NULL && stcb_tmp == stcb) {
net_tmp->dest_state &= ~SCTP_ADDR_NOT_IN_ASSOC;
} elseif (stcb_tmp != stcb) { /* It belongs to another association? */ if (stcb_tmp)
SCTP_TCB_UNLOCK(stcb_tmp); return (-3);
}
} if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-4);
} /* now we must go through each of the params. */
phdr = sctp_get_next_param(m, offset, ¶m_buf, sizeof(param_buf)); while (phdr) {
ptype = ntohs(phdr->param_type);
plen = ntohs(phdr->param_length); /* * SCTP_PRINTF("ptype => %0x, plen => %d\n", (uint32_t)ptype, * (int)plen);
*/ if (offset + plen > limit) { break;
} if (plen < sizeof(struct sctp_paramhdr)) { break;
} #ifdef INET if (ptype == SCTP_IPV4_ADDRESS) { if (stcb->asoc.scope.ipv4_addr_legal) { struct sctp_ipv4addr_param *p4, p4_buf;
/* ok get the v4 address and check/add */
phdr = sctp_get_next_param(m, offset,
(struct sctp_paramhdr *)&p4_buf, sizeof(p4_buf)); if (plen != sizeof(struct sctp_ipv4addr_param) ||
phdr == NULL) { return (-5);
}
p4 = (struct sctp_ipv4addr_param *)phdr;
sin.sin_addr.s_addr = p4->addr; if (IN_MULTICAST(ntohl(sin.sin_addr.s_addr))) { /* Skip multi-cast addresses */ goto next_param;
} if ((sin.sin_addr.s_addr == INADDR_BROADCAST) ||
(sin.sin_addr.s_addr == INADDR_ANY)) { goto next_param;
}
sa = (struct sockaddr *)&sin;
inp = stcb->sctp_ep;
atomic_add_int(&stcb->asoc.refcnt, 1);
stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net,
dst, stcb);
atomic_subtract_int(&stcb->asoc.refcnt, 1);
if ((stcb_tmp == NULL && inp == stcb->sctp_ep) ||
inp == NULL) { /* we must add the source address */ /* * no scope set since we have a tcb * already
*/
/* * we must validate the state again * here
*/
add_it_now: if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-7);
} if (sctp_add_remote_addr(stcb, sa, NULL, port, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_4)) { return (-8);
}
} elseif (stcb_tmp == stcb) { if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-10);
} if (net != NULL) { /* clear flag */
net->dest_state &=
~SCTP_ADDR_NOT_IN_ASSOC;
}
} else { /* * strange, address is in another * assoc? straighten out locks.
*/ if (stcb_tmp) { if (SCTP_GET_STATE(stcb_tmp) == SCTP_STATE_COOKIE_WAIT) { struct mbuf *op_err; char msg[SCTP_DIAG_INFO_LEN];
/* in setup state we abort this guy */
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);
sctp_abort_an_association(stcb_tmp->sctp_ep,
stcb_tmp, op_err, false,
SCTP_SO_NOT_LOCKED); goto add_it_now;
}
SCTP_TCB_UNLOCK(stcb_tmp);
}
if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-12);
} return (-13);
}
}
} else #endif #ifdef INET6 if (ptype == SCTP_IPV6_ADDRESS) { if (stcb->asoc.scope.ipv6_addr_legal) { /* ok get the v6 address and check/add */ struct sctp_ipv6addr_param *p6, p6_buf;
phdr = sctp_get_next_param(m, offset,
(struct sctp_paramhdr *)&p6_buf, sizeof(p6_buf)); if (plen != sizeof(struct sctp_ipv6addr_param) ||
phdr == NULL) { return (-14);
}
p6 = (struct sctp_ipv6addr_param *)phdr;
memcpy((caddr_t)&sin6.sin6_addr, p6->addr, sizeof(p6->addr)); if (IN6_IS_ADDR_MULTICAST(&sin6.sin6_addr)) { /* Skip multi-cast addresses */ goto next_param;
} if (IN6_IS_ADDR_LINKLOCAL(&sin6.sin6_addr)) { /* Link local make no sense without scope */ goto next_param;
}
sa = (struct sockaddr *)&sin6;
inp = stcb->sctp_ep;
atomic_add_int(&stcb->asoc.refcnt, 1);
stcb_tmp = sctp_findassociation_ep_addr(&inp, sa, &net,
dst, stcb);
atomic_subtract_int(&stcb->asoc.refcnt, 1); if (stcb_tmp == NULL &&
(inp == stcb->sctp_ep || inp == NULL)) { /* * we must validate the state again * here
*/
add_it_now6: if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-16);
} /* * we must add the address, no scope * set
*/ if (sctp_add_remote_addr(stcb, sa, NULL, port, SCTP_DONOT_SETSCOPE, SCTP_LOAD_ADDR_5)) { return (-17);
}
} elseif (stcb_tmp == stcb) { /* * we must validate the state again * here
*/ if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-19);
} if (net != NULL) { /* clear flag */
net->dest_state &=
~SCTP_ADDR_NOT_IN_ASSOC;
}
} else { /* * strange, address is in another * assoc? straighten out locks.
*/ if (stcb_tmp) { if (SCTP_GET_STATE(stcb_tmp) == SCTP_STATE_COOKIE_WAIT) { struct mbuf *op_err; char msg[SCTP_DIAG_INFO_LEN];
/* in setup state we abort this guy */
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);
sctp_abort_an_association(stcb_tmp->sctp_ep,
stcb_tmp, op_err, false,
SCTP_SO_NOT_LOCKED); goto add_it_now6;
}
SCTP_TCB_UNLOCK(stcb_tmp);
} if (stcb->asoc.state == 0) { /* the assoc was freed? */ return (-21);
} return (-22);
}
}
} else #endif if (ptype == SCTP_ECN_CAPABLE) {
peer_supports_ecn = 1;
} elseif (ptype == SCTP_ULP_ADAPTATION) { if (stcb->asoc.state != SCTP_STATE_OPEN) { struct sctp_adaptation_layer_indication ai, *aip;
phdr = sctp_get_next_param(m, offset,
(struct sctp_paramhdr *)&zero_chksum, sizeof(struct sctp_zero_checksum_acceptable)); if (phdr != NULL) { /* * Only send zero checksums if the upper layer * has enabled the support for the same method * as allowed by the peer.
*/
zero_chksum_p = (struct sctp_zero_checksum_acceptable *)phdr; if ((ntohl(zero_chksum_p->edmid) != SCTP_EDMID_NONE) &&
(ntohl(zero_chksum_p->edmid) == stcb->asoc.rcv_edmid)) {
stcb->asoc.snd_edmid = stcb->asoc.rcv_edmid;
}
}
} elseif (ptype == SCTP_SUPPORTED_CHUNK_EXT) { /* A supported extension chunk */ struct sctp_supported_chunk_types_param *pr_supported;
uint8_t local_store[SCTP_PARAM_BUFFER_SIZE]; int num_ent, i;
if (plen > sizeof(local_store)) { return (-35);
}
phdr = sctp_get_next_param(m, offset,
(struct sctp_paramhdr *)&local_store, plen); if (phdr == NULL) { return (-25);
}
pr_supported = (struct sctp_supported_chunk_types_param *)phdr;
num_ent = plen - sizeof(struct sctp_paramhdr); for (i = 0; i < num_ent; i++) { switch (pr_supported->chunk_types[i]) { case SCTP_ASCONF:
peer_supports_asconf = 1; break; case SCTP_ASCONF_ACK:
peer_supports_asconf_ack = 1; break; case SCTP_FORWARD_CUM_TSN:
peer_supports_prsctp = 1; break; case SCTP_PACKET_DROPPED:
peer_supports_pktdrop = 1; break; case SCTP_NR_SELECTIVE_ACK:
peer_supports_nrsack = 1; break; case SCTP_STREAM_RESET:
peer_supports_reconfig = 1; break; case SCTP_AUTHENTICATION:
peer_supports_auth = 1; break; case SCTP_IDATA:
peer_supports_idata = 1; break; default: /* one I have not learned yet */ break;
}
}
} elseif (ptype == SCTP_RANDOM) { if (plen > sizeof(random_store)) break; if (got_random) { /* already processed a RANDOM */ goto next_param;
}
phdr = sctp_get_next_param(m, offset,
(struct sctp_paramhdr *)random_store,
plen); if (phdr == NULL) return (-26);
p_random = (struct sctp_auth_random *)phdr;
random_len = plen - sizeof(*p_random); /* enforce the random length */ if (random_len != SCTP_AUTH_RANDOM_SIZE_REQUIRED) {
SCTPDBG(SCTP_DEBUG_AUTH1, "SCTP: invalid RANDOM len\n"); return (-27);
}
got_random = 1;
} elseif (ptype == SCTP_HMAC_LIST) {
uint16_t num_hmacs;
uint16_t i;
if (plen > sizeof(hmacs_store)) break; if (got_hmacs) { /* already processed a HMAC list */ goto next_param;
}
phdr = sctp_get_next_param(m, offset,
(struct sctp_paramhdr *)hmacs_store,
plen); if (phdr == NULL) return (-28);
hmacs = (struct sctp_auth_hmac_algo *)phdr;
hmacs_len = plen - sizeof(*hmacs);
num_hmacs = hmacs_len / sizeof(hmacs->hmac_ids[0]); /* validate the hmac list */ if (sctp_verify_hmac_param(hmacs, num_hmacs)) { return (-29);
} if (stcb->asoc.peer_hmacs != NULL)
sctp_free_hmaclist(stcb->asoc.peer_hmacs);
stcb->asoc.peer_hmacs = sctp_alloc_hmaclist(num_hmacs); if (stcb->asoc.peer_hmacs != NULL) { for (i = 0; i < num_hmacs; i++) {
(void)sctp_auth_add_hmacid(stcb->asoc.peer_hmacs,
ntohs(hmacs->hmac_ids[i]));
}
}
got_hmacs = 1;
} elseif (ptype == SCTP_CHUNK_LIST) { int i;
if (plen > sizeof(chunks_store)) break; if (got_chklist) { /* already processed a Chunks list */ goto next_param;
}
phdr = sctp_get_next_param(m, offset,
(struct sctp_paramhdr *)chunks_store,
plen); if (phdr == NULL) return (-30);
chunks = (struct sctp_auth_chunk_list *)phdr;
num_chunks = plen - sizeof(*chunks); if (stcb->asoc.peer_auth_chunks != NULL)
sctp_clear_chunklist(stcb->asoc.peer_auth_chunks); else
stcb->asoc.peer_auth_chunks = sctp_alloc_chunklist(); for (i = 0; i < num_chunks; i++) {
(void)sctp_auth_add_chunk(chunks->chunk_types[i],
stcb->asoc.peer_auth_chunks); /* record asconf/asconf-ack if listed */ if (chunks->chunk_types[i] == SCTP_ASCONF)
saw_asconf = 1; if (chunks->chunk_types[i] == SCTP_ASCONF_ACK)
saw_asconf_ack = 1;
}
got_chklist = 1;
} elseif ((ptype == SCTP_HEARTBEAT_INFO) ||
(ptype == SCTP_STATE_COOKIE) ||
(ptype == SCTP_UNRECOG_PARAM) ||
(ptype == SCTP_COOKIE_PRESERVE) ||
(ptype == SCTP_SUPPORTED_ADDRTYPE) ||
(ptype == SCTP_ADD_IP_ADDRESS) ||
(ptype == SCTP_DEL_IP_ADDRESS) ||
(ptype == SCTP_ERROR_CAUSE_IND) ||
(ptype == SCTP_SUCCESS_REPORT)) { /* don't care */
} else { if ((ptype & 0x8000) == 0x0000) { /* * must stop processing the rest of the * param's. Any report bits were handled * with the call to * sctp_arethere_unrecognized_parameters() * when the INIT or INIT-ACK was first seen.
*/ break;
}
}
next_param:
offset += SCTP_SIZE32(plen); if (offset >= limit) { break;
}
phdr = sctp_get_next_param(m, offset, ¶m_buf, sizeof(param_buf));
} /* Now check to see if we need to purge any addresses */
TAILQ_FOREACH_SAFE(net, &stcb->asoc.nets, sctp_next, nnet) { if ((net->dest_state & SCTP_ADDR_NOT_IN_ASSOC) ==
SCTP_ADDR_NOT_IN_ASSOC) { /* This address has been removed from the asoc */ /* remove and free it */
stcb->asoc.numnets--;
TAILQ_REMOVE(&stcb->asoc.nets, net, sctp_next); if (net == stcb->asoc.alternate) {
sctp_free_remote_addr(stcb->asoc.alternate);
stcb->asoc.alternate = NULL;
} if (net == stcb->asoc.primary_destination) {
stcb->asoc.primary_destination = NULL;
sctp_select_primary_destination(stcb);
}
sctp_free_remote_addr(net);
}
} if ((stcb->asoc.ecn_supported == 1) &&
(peer_supports_ecn == 0)) {
stcb->asoc.ecn_supported = 0;
} if ((stcb->asoc.prsctp_supported == 1) &&
(peer_supports_prsctp == 0)) {
stcb->asoc.prsctp_supported = 0;
} if ((stcb->asoc.auth_supported == 1) &&
((peer_supports_auth == 0) ||
(got_random == 0) || (got_hmacs == 0))) {
stcb->asoc.auth_supported = 0;
} if ((stcb->asoc.asconf_supported == 1) &&
((peer_supports_asconf == 0) || (peer_supports_asconf_ack == 0) ||
(stcb->asoc.auth_supported == 0) ||
(saw_asconf == 0) || (saw_asconf_ack == 0))) {
stcb->asoc.asconf_supported = 0;
} if ((stcb->asoc.reconfig_supported == 1) &&
(peer_supports_reconfig == 0)) {
stcb->asoc.reconfig_supported = 0;
} if ((stcb->asoc.idata_supported == 1) &&
(peer_supports_idata == 0)) {
stcb->asoc.idata_supported = 0;
} if ((stcb->asoc.nrsack_supported == 1) &&
(peer_supports_nrsack == 0)) {
stcb->asoc.nrsack_supported = 0;
} if ((stcb->asoc.pktdrop_supported == 1) &&
(peer_supports_pktdrop == 0)) {
stcb->asoc.pktdrop_supported = 0;
} /* validate authentication required parameters */ if ((peer_supports_auth == 0) && (got_chklist == 1)) { /* peer does not support auth but sent a chunks list? */ return (-31);
} if ((peer_supports_asconf == 1) && (peer_supports_auth == 0)) { /* peer supports asconf but not auth? */ return (-32);
} elseif ((peer_supports_asconf == 1) &&
(peer_supports_auth == 1) &&
((saw_asconf == 0) || (saw_asconf_ack == 0))) { return (-33);
} /* concatenate the full random key */
keylen = sizeof(*p_random) + random_len + sizeof(*hmacs) + hmacs_len; if (chunks != NULL) {
keylen += sizeof(*chunks) + num_chunks;
}
new_key = sctp_alloc_key(keylen); if (new_key != NULL) { /* copy in the RANDOM */ if (p_random != NULL) {
keylen = sizeof(*p_random) + random_len;
memcpy(new_key->key, p_random, keylen);
} else {
keylen = 0;
} /* append in the AUTH chunks */ if (chunks != NULL) {
memcpy(new_key->key + keylen, chunks, sizeof(*chunks) + num_chunks);
keylen += sizeof(*chunks) + num_chunks;
} /* append in the HMACs */ if (hmacs != NULL) {
memcpy(new_key->key + keylen, hmacs, sizeof(*hmacs) + hmacs_len);
}
} else { /* failed to get memory for the key */ return (-34);
} if (stcb->asoc.authinfo.peer_random != NULL)
sctp_free_key(stcb->asoc.authinfo.peer_random);
stcb->asoc.authinfo.peer_random = new_key;
sctp_clear_cachedkeys(stcb, stcb->asoc.authinfo.assoc_keyid);
sctp_clear_cachedkeys(stcb, stcb->asoc.authinfo.recv_keyid);
return (0);
}
int
sctp_set_primary_addr(struct sctp_tcb *stcb, struct sockaddr *sa, struct sctp_nets *net)
{ /* make sure the requested primary address exists in the assoc */ if (net == NULL && sa)
net = sctp_findnet(stcb, sa);
if (net == NULL) { /* didn't find the requested primary address! */ return (-1);
} else { /* set the primary address */ if (net->dest_state & SCTP_ADDR_UNCONFIRMED) { /* Must be confirmed, so queue to set */
net->dest_state |= SCTP_ADDR_REQ_PRIMARY; return (0);
}
stcb->asoc.primary_destination = net; if (((net->dest_state & SCTP_ADDR_PF) == 0) &&
(stcb->asoc.alternate != NULL)) {
sctp_free_remote_addr(stcb->asoc.alternate);
stcb->asoc.alternate = NULL;
}
net = TAILQ_FIRST(&stcb->asoc.nets); if (net != stcb->asoc.primary_destination) { /* first one on the list is NOT the primary * sctp_cmpaddr() is much more efficient if * the primary is the first on the list, make it * so.
*/
TAILQ_REMOVE(&stcb->asoc.nets, stcb->asoc.primary_destination, sctp_next);
TAILQ_INSERT_HEAD(&stcb->asoc.nets, stcb->asoc.primary_destination, sctp_next);
} return (0);
}
}
head = &SCTP_BASE_INFO(sctp_asochash)[SCTP_PCBHASH_ASOC(tag, SCTP_BASE_INFO(hashasocmark))];
LIST_FOREACH(stcb, head, sctp_asocs) { /* We choose not to lock anything here. TCB's can't be * removed since we have the read lock, so they can't * be freed on us, same thing for the INP. I may * be wrong with this assumption, but we will go * with it for now :-)
*/ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_SOCKET_ALLGONE) { continue;
} if (stcb->asoc.my_vtag == tag) { /* candidate */ if (stcb->rport != rport) { continue;
} if (stcb->sctp_ep->sctp_lport != lport) { continue;
} /* The tag is currently used, so don't use it. */ return (false);
}
} return (!sctp_is_in_timewait(tag, lport, rport, now->tv_sec));
}
staticvoid
sctp_drain_mbufs(struct sctp_tcb *stcb)
{ /* * We must hunt this association for MBUF's past the cumack (i.e. * out of order data that we can renege on).
*/ struct sctp_association *asoc; struct sctp_tmit_chunk *chk, *nchk;
uint32_t cumulative_tsn_p1; struct sctp_queued_to_read *control, *ncontrol; int cnt, strmat;
uint32_t gap, i; int fnd = 0;
/* We look for anything larger than the cum-ack + 1 */
asoc = &stcb->asoc; if (asoc->cumulative_tsn == asoc->highest_tsn_inside_map) { /* none we can reneg on. */ return;
}
SCTP_STAT_INCR(sctps_protocol_drains_done);
cumulative_tsn_p1 = asoc->cumulative_tsn + 1;
cnt = 0; /* Ok that was fun, now we will drain all the inbound streams? */ for (strmat = 0; strmat < asoc->streamincnt; strmat++) {
TAILQ_FOREACH_SAFE(control, &asoc->strmin[strmat].inqueue, next_instrm, ncontrol) { #ifdef INVARIANTS if (control->on_strm_q != SCTP_ON_ORDERED) {
panic("Huh control: %p on_q: %d -- not ordered?",
control, control->on_strm_q);
} #endif if (SCTP_TSN_GT(control->sinfo_tsn, cumulative_tsn_p1)) { /* Yep it is above cum-ack */
cnt++;
SCTP_CALC_TSN_TO_GAP(gap, control->sinfo_tsn, asoc->mapping_array_base_tsn);
KASSERT(control->length > 0, ("control has zero length")); if (asoc->size_on_all_streams >= control->length) {
asoc->size_on_all_streams -= control->length;
} else { #ifdef INVARIANTS
panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length); #else
asoc->size_on_all_streams = 0; #endif
}
sctp_ucount_decr(asoc->cnt_on_all_streams);
SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap); if (control->on_read_q) {
TAILQ_REMOVE(&stcb->sctp_ep->read_queue, control, next);
control->on_read_q = 0;
}
TAILQ_REMOVE(&asoc->strmin[strmat].inqueue, control, next_instrm);
control->on_strm_q = 0; if (control->data) {
sctp_m_freem(control->data);
control->data = NULL;
}
sctp_free_remote_addr(control->whoFrom); /* Now its reasm? */
TAILQ_FOREACH_SAFE(chk, &control->reasm, sctp_next, nchk) {
cnt++;
SCTP_CALC_TSN_TO_GAP(gap, chk->rec.data.tsn, asoc->mapping_array_base_tsn);
KASSERT(chk->send_size > 0, ("chunk has zero length")); if (asoc->size_on_reasm_queue >= chk->send_size) {
asoc->size_on_reasm_queue -= chk->send_size;
} else { #ifdef INVARIANTS
panic("size_on_reasm_queue = %u smaller than chunk length %u", asoc->size_on_reasm_queue, chk->send_size); #else
asoc->size_on_reasm_queue = 0; #endif
}
sctp_ucount_decr(asoc->cnt_on_reasm_queue);
SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap);
TAILQ_REMOVE(&control->reasm, chk, sctp_next); if (chk->data) {
sctp_m_freem(chk->data);
chk->data = NULL;
}
sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
}
sctp_free_a_readq(stcb, control);
}
}
TAILQ_FOREACH_SAFE(control, &asoc->strmin[strmat].uno_inqueue, next_instrm, ncontrol) { #ifdef INVARIANTS if (control->on_strm_q != SCTP_ON_UNORDERED) {
panic("Huh control: %p on_q: %d -- not unordered?",
control, control->on_strm_q);
} #endif if (SCTP_TSN_GT(control->sinfo_tsn, cumulative_tsn_p1)) { /* Yep it is above cum-ack */
cnt++;
SCTP_CALC_TSN_TO_GAP(gap, control->sinfo_tsn, asoc->mapping_array_base_tsn);
KASSERT(control->length > 0, ("control has zero length")); if (asoc->size_on_all_streams >= control->length) {
asoc->size_on_all_streams -= control->length;
} else { #ifdef INVARIANTS
panic("size_on_all_streams = %u smaller than control length %u", asoc->size_on_all_streams, control->length); #else
asoc->size_on_all_streams = 0; #endif
}
sctp_ucount_decr(asoc->cnt_on_all_streams);
SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap); if (control->on_read_q) {
TAILQ_REMOVE(&stcb->sctp_ep->read_queue, control, next);
control->on_read_q = 0;
}
TAILQ_REMOVE(&asoc->strmin[strmat].uno_inqueue, control, next_instrm);
control->on_strm_q = 0; if (control->data) {
sctp_m_freem(control->data);
control->data = NULL;
}
sctp_free_remote_addr(control->whoFrom); /* Now its reasm? */
TAILQ_FOREACH_SAFE(chk, &control->reasm, sctp_next, nchk) {
cnt++;
SCTP_CALC_TSN_TO_GAP(gap, chk->rec.data.tsn, asoc->mapping_array_base_tsn);
KASSERT(chk->send_size > 0, ("chunk has zero length")); if (asoc->size_on_reasm_queue >= chk->send_size) {
asoc->size_on_reasm_queue -= chk->send_size;
} else { #ifdef INVARIANTS
panic("size_on_reasm_queue = %u smaller than chunk length %u", asoc->size_on_reasm_queue, chk->send_size); #else
asoc->size_on_reasm_queue = 0; #endif
}
sctp_ucount_decr(asoc->cnt_on_reasm_queue);
SCTP_UNSET_TSN_PRESENT(asoc->mapping_array, gap);
TAILQ_REMOVE(&control->reasm, chk, sctp_next); if (chk->data) {
sctp_m_freem(chk->data);
chk->data = NULL;
}
sctp_free_a_chunk(stcb, chk, SCTP_SO_NOT_LOCKED);
}
sctp_free_a_readq(stcb, control);
}
}
} if (cnt) { /* We must back down to see what the new highest is */ for (i = asoc->highest_tsn_inside_map; SCTP_TSN_GE(i, asoc->mapping_array_base_tsn); i--) {
SCTP_CALC_TSN_TO_GAP(gap, i, asoc->mapping_array_base_tsn); if (SCTP_IS_TSN_PRESENT(asoc->mapping_array, gap)) {
asoc->highest_tsn_inside_map = i;
fnd = 1; break;
}
} if (!fnd) {
asoc->highest_tsn_inside_map = asoc->mapping_array_base_tsn - 1;
}
/* * Question, should we go through the delivery queue? The only * reason things are on here is the app not reading OR a p-d-api up. * An attacker COULD send enough in to initiate the PD-API and then * send a bunch of stuff to other streams... these would wind up on * the delivery queue.. and then we would not get to them. But in * order to do this I then have to back-track and un-deliver * sequence numbers in streams.. el-yucko. I think for now we will * NOT look at the delivery queue and leave it to be something to * consider later. An alternative would be to abort the P-D-API with * a notification and then deliver the data.... Or another method * might be to keep track of how many times the situation occurs and * if we see a possible attack underway just abort the association.
*/ #ifdef SCTP_DEBUG
SCTPDBG(SCTP_DEBUG_PCB1, "Freed %d chunks from reneg harvest\n", cnt); #endif /* * Now do we need to find a new * asoc->highest_tsn_inside_map?
*/
asoc->last_revoke_count = cnt;
sctp_timer_stop(SCTP_TIMER_TYPE_RECV, stcb->sctp_ep, stcb, NULL,
SCTP_FROM_SCTP_PCB + SCTP_LOC_11); /*sa_ignore NO_NULL_CHK*/
sctp_send_sack(stcb, SCTP_SO_NOT_LOCKED);
sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_DRAIN, SCTP_SO_NOT_LOCKED);
} /* * Another issue, in un-setting the TSN's in the mapping array we * DID NOT adjust the highest_tsn marker. This will cause one of two * things to occur. It may cause us to do extra work in checking for * our mapping array movement. More importantly it may cause us to * SACK every datagram. This may not be a bad thing though since we * will recover once we get our cum-ack above and all this stuff we * dumped recovered.
*/
}
SCTP_STAT_INCR(sctps_protocol_drain_calls); if (SCTP_BASE_SYSCTL(sctp_do_drain) == 0) { return;
} #endif /* * We must walk the PCB lists for ALL associations here. The system * is LOW on MBUF's and needs help. This is where reneging will * occur. We really hope this does NOT happen!
*/ #ifdefined(__FreeBSD__) && !defined(__Userspace__)
VNET_LIST_RLOCK_NOSLEEP();
VNET_FOREACH(vnet_iter) {
CURVNET_SET(vnet_iter); struct sctp_inpcb *inp; struct sctp_tcb *stcb; #endif
/* * start a new iterator * iterates through all endpoints and associations based on the pcb_state * flags and asoc_state. "af" (mandatory) is executed for all matching * assocs and "ef" (optional) is executed when the iterator completes. * "inpf" (optional) is executed for each new endpoint as it is being * iterated through. inpe (optional) is called when the inp completes * its way through all the stcbs.
*/ int
sctp_initiate_iterator(inp_func inpf,
asoc_func af,
inp_func inpe,
uint32_t pcb_state,
uint32_t pcb_features,
uint32_t asoc_state, void *argp,
uint32_t argi,
end_func ef, struct sctp_inpcb *s_inp,
uint8_t chunk_output_off)
{ struct sctp_iterator *it = NULL;
if (af == NULL) { return (-1);
} if (SCTP_BASE_VAR(sctp_pcb_initialized) == 0) {
SCTP_PRINTF("%s: abort on initialize being %d\n", __func__,
SCTP_BASE_VAR(sctp_pcb_initialized)); return (-1);
}
SCTP_MALLOC(it, struct sctp_iterator *, sizeof(struct sctp_iterator),
SCTP_M_ITER); if (it == NULL) {
SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_PCB, ENOMEM); return (-1);
}
memset(it, 0, sizeof(*it));
it->function_assoc = af;
it->function_inp = inpf; if (inpf)
it->done_current_ep = 0; else
it->done_current_ep = 1;
it->function_atend = ef;
it->pointer = argp;
it->val = argi;
it->pcb_flags = pcb_state;
it->pcb_features = pcb_features;
it->asoc_state = asoc_state;
it->function_inp_end = inpe;
it->no_chunk_output = chunk_output_off; #ifdefined(__FreeBSD__) && !defined(__Userspace__)
it->vn = curvnet; #endif if (s_inp) { /* Assume lock is held here */
it->inp = s_inp;
SCTP_INP_INCR_REF(it->inp);
it->iterator_flags = SCTP_ITERATOR_DO_SINGLE_INP;
} else {
SCTP_INP_INFO_RLOCK();
it->inp = LIST_FIRST(&SCTP_BASE_INFO(listhead)); if (it->inp) {
SCTP_INP_INCR_REF(it->inp);
}
SCTP_INP_INFO_RUNLOCK();
it->iterator_flags = SCTP_ITERATOR_DO_ALL_INP;
}
SCTP_IPI_ITERATOR_WQ_LOCK(); if (SCTP_BASE_VAR(sctp_pcb_initialized) == 0) {
SCTP_IPI_ITERATOR_WQ_UNLOCK();
SCTP_PRINTF("%s: rollback on initialize being %d it=%p\n", __func__,
SCTP_BASE_VAR(sctp_pcb_initialized), it);
SCTP_FREE(it, SCTP_M_ITER); return (-1);
}
TAILQ_INSERT_TAIL(&sctp_it_ctl.iteratorhead, it, sctp_nxt_itr); if (sctp_it_ctl.iterator_running == 0) {
sctp_wakeup_iterator();
}
SCTP_IPI_ITERATOR_WQ_UNLOCK(); /* sa_ignore MEMLEAK {memory is put on the tailq for the iterator} */ return (0);
}
/* * Atomically add flags to the sctp_flags of an inp. * To be used when the write lock of the inp is not held.
*/ void
sctp_pcb_add_flags(struct sctp_inpcb *inp, uint32_t flags)
{
uint32_t old_flags, new_flags;
do {
old_flags = inp->sctp_flags;
new_flags = old_flags | flags;
} while (atomic_cmpset_int(&inp->sctp_flags, old_flags, new_flags) == 0);
}
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.180 Sekunden
(vorverarbeitet am 2026-04-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.