summaryrefslogtreecommitdiffstats
path: root/netwerk/sctp/src/netinet/sctp_asconf.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /netwerk/sctp/src/netinet/sctp_asconf.c
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'netwerk/sctp/src/netinet/sctp_asconf.c')
-rw-r--r--netwerk/sctp/src/netinet/sctp_asconf.c3534
1 files changed, 3534 insertions, 0 deletions
diff --git a/netwerk/sctp/src/netinet/sctp_asconf.c b/netwerk/sctp/src/netinet/sctp_asconf.c
new file mode 100644
index 0000000000..a4440e5e0c
--- /dev/null
+++ b/netwerk/sctp/src/netinet/sctp_asconf.c
@@ -0,0 +1,3534 @@
+/*-
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * Copyright (c) 2001-2007, 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.
+ */
+
+#if defined(_WIN32)
+// Needed for unified build so that rand_s is available to all unified
+// sources.
+#if !defined(_CRT_RAND_S) && !defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
+#define _CRT_RAND_S
+#endif
+#endif
+
+#include <netinet/sctp_os.h>
+#include <netinet/sctp_var.h>
+#include <netinet/sctp_sysctl.h>
+#include <netinet/sctp_pcb.h>
+#include <netinet/sctp_header.h>
+#include <netinet/sctputil.h>
+#include <netinet/sctp_output.h>
+#include <netinet/sctp_asconf.h>
+#include <netinet/sctp_timer.h>
+
+/*
+ * debug flags:
+ * SCTP_DEBUG_ASCONF1: protocol info, general info and errors
+ * SCTP_DEBUG_ASCONF2: detailed info
+ */
+
+/*
+ * RFC 5061
+ *
+ * An ASCONF parameter queue exists per asoc which holds the pending address
+ * operations. Lists are updated upon receipt of ASCONF-ACK.
+ *
+ * A restricted_addrs list exists per assoc to hold local addresses that are
+ * not (yet) usable by the assoc as a source address. These addresses are
+ * either pending an ASCONF operation (and exist on the ASCONF parameter
+ * queue), or they are permanently restricted (the peer has returned an
+ * ERROR indication to an ASCONF(ADD), or the peer does not support ASCONF).
+ *
+ * Deleted addresses are always immediately removed from the lists as they will
+ * (shortly) no longer exist in the kernel. We send ASCONFs as a courtesy,
+ * only if allowed.
+ */
+
+/*
+ * ASCONF parameter processing.
+ * response_required: set if a reply is required (eg. SUCCESS_REPORT).
+ * returns a mbuf to an "error" response parameter or NULL/"success" if ok.
+ * FIX: allocating this many mbufs on the fly is pretty inefficient...
+ */
+static struct mbuf *
+sctp_asconf_success_response(uint32_t id)
+{
+ struct mbuf *m_reply = NULL;
+ struct sctp_asconf_paramhdr *aph;
+
+ m_reply = sctp_get_mbuf_for_msg(sizeof(struct sctp_asconf_paramhdr),
+ 0, M_NOWAIT, 1, MT_DATA);
+ if (m_reply == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_success_response: couldn't get mbuf!\n");
+ return (NULL);
+ }
+ aph = mtod(m_reply, struct sctp_asconf_paramhdr *);
+ aph->correlation_id = id;
+ aph->ph.param_type = htons(SCTP_SUCCESS_REPORT);
+ aph->ph.param_length = sizeof(struct sctp_asconf_paramhdr);
+ SCTP_BUF_LEN(m_reply) = aph->ph.param_length;
+ aph->ph.param_length = htons(aph->ph.param_length);
+
+ return (m_reply);
+}
+
+static struct mbuf *
+sctp_asconf_error_response(uint32_t id, uint16_t cause, uint8_t *error_tlv,
+ uint16_t tlv_length)
+{
+ struct mbuf *m_reply = NULL;
+ struct sctp_asconf_paramhdr *aph;
+ struct sctp_error_cause *error;
+ uint32_t buf_len;
+ uint16_t i, param_length, cause_length, padding_length;
+ uint8_t *tlv;
+
+ if (error_tlv == NULL) {
+ tlv_length = 0;
+ }
+ cause_length = sizeof(struct sctp_error_cause) + tlv_length;
+ param_length = sizeof(struct sctp_asconf_paramhdr) + cause_length;
+ padding_length = tlv_length % 4;
+ if (padding_length != 0) {
+ padding_length = 4 - padding_length;
+ }
+ buf_len = param_length + padding_length;
+ if (buf_len > MLEN) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_error_response: tlv_length (%xh) too big\n",
+ tlv_length);
+ return (NULL);
+ }
+ m_reply = sctp_get_mbuf_for_msg(buf_len, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_reply == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_error_response: couldn't get mbuf!\n");
+ return (NULL);
+ }
+ aph = mtod(m_reply, struct sctp_asconf_paramhdr *);
+ aph->ph.param_type = htons(SCTP_ERROR_CAUSE_IND);
+ aph->ph.param_length = htons(param_length);
+ aph->correlation_id = id;
+ error = (struct sctp_error_cause *)(aph + 1);
+ error->code = htons(cause);
+ error->length = htons(cause_length);
+ if (error_tlv != NULL) {
+ tlv = (uint8_t *) (error + 1);
+ memcpy(tlv, error_tlv, tlv_length);
+ for (i = 0; i < padding_length; i++) {
+ tlv[tlv_length + i] = 0;
+ }
+ }
+ SCTP_BUF_LEN(m_reply) = buf_len;
+ return (m_reply);
+}
+
+static struct mbuf *
+sctp_process_asconf_add_ip(struct sockaddr *src, struct sctp_asconf_paramhdr *aph,
+ struct sctp_tcb *stcb, int send_hb, int response_required)
+{
+ struct sctp_nets *net;
+ struct mbuf *m_reply = NULL;
+ union sctp_sockstore store;
+ struct sctp_paramhdr *ph;
+ uint16_t param_type, aparam_length;
+#if defined(INET) || defined(INET6)
+ uint16_t param_length;
+#endif
+ struct sockaddr *sa;
+ int zero_address = 0;
+ int bad_address = 0;
+#ifdef INET
+ struct sockaddr_in *sin;
+ struct sctp_ipv4addr_param *v4addr;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+ struct sctp_ipv6addr_param *v6addr;
+#endif
+
+ aparam_length = ntohs(aph->ph.param_length);
+ if (aparam_length < sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_paramhdr)) {
+ return (NULL);
+ }
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ param_type = ntohs(ph->param_type);
+#if defined(INET) || defined(INET6)
+ param_length = ntohs(ph->param_length);
+ if (param_length + sizeof(struct sctp_asconf_paramhdr) != aparam_length) {
+ return (NULL);
+ }
+#endif
+ sa = &store.sa;
+ switch (param_type) {
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv4addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v4addr = (struct sctp_ipv4addr_param *)ph;
+ sin = &store.sin;
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin->sin_port = stcb->rport;
+ sin->sin_addr.s_addr = v4addr->addr;
+ if ((sin->sin_addr.s_addr == INADDR_BROADCAST) ||
+ IN_MULTICAST(ntohl(sin->sin_addr.s_addr))) {
+ bad_address = 1;
+ }
+ if (sin->sin_addr.s_addr == INADDR_ANY)
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_add_ip: adding ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv6addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v6addr = (struct sctp_ipv6addr_param *)ph;
+ sin6 = &store.sin6;
+ 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;
+ memcpy((caddr_t)&sin6->sin6_addr, v6addr->addr,
+ sizeof(struct in6_addr));
+ if (IN6_IS_ADDR_MULTICAST(&sin6->sin6_addr)) {
+ bad_address = 1;
+ }
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_add_ip: adding ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+ default:
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_INVALID_PARAM, (uint8_t *) aph,
+ aparam_length);
+ return (m_reply);
+ } /* end switch */
+
+ /* if 0.0.0.0/::0, add the source address instead */
+ if (zero_address && SCTP_BASE_SYSCTL(sctp_nat_friendly)) {
+ sa = src;
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_add_ip: using source addr ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, src);
+ }
+ net = NULL;
+ /* add the address */
+ if (bad_address) {
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_INVALID_PARAM, (uint8_t *) aph,
+ aparam_length);
+ } else if (sctp_add_remote_addr(stcb, sa, &net, stcb->asoc.port,
+ SCTP_DONOT_SETSCOPE,
+ SCTP_ADDR_DYNAMIC_ADDED) != 0) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_add_ip: error adding address\n");
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_RESOURCE_SHORTAGE, (uint8_t *) aph,
+ aparam_length);
+ } else {
+ if (response_required) {
+ m_reply =
+ sctp_asconf_success_response(aph->correlation_id);
+ }
+ if (net != NULL) {
+ /* notify upper layer */
+ sctp_ulp_notify(SCTP_NOTIFY_ASCONF_ADD_IP, stcb, 0, sa, SCTP_SO_NOT_LOCKED);
+ sctp_timer_start(SCTP_TIMER_TYPE_PATHMTURAISE, stcb->sctp_ep, stcb, net);
+ sctp_timer_start(SCTP_TIMER_TYPE_HEARTBEAT, stcb->sctp_ep,
+ stcb, net);
+ if (send_hb) {
+ sctp_send_hb(stcb, net, SCTP_SO_NOT_LOCKED);
+ }
+ }
+ }
+ return (m_reply);
+}
+
+static int
+sctp_asconf_del_remote_addrs_except(struct sctp_tcb *stcb, struct sockaddr *src)
+{
+ struct sctp_nets *src_net, *net, *nnet;
+
+ /* make sure the source address exists as a destination net */
+ src_net = sctp_findnet(stcb, src);
+ if (src_net == NULL) {
+ /* not found */
+ return (-1);
+ }
+
+ /* delete all destination addresses except the source */
+ TAILQ_FOREACH_SAFE(net, &stcb->asoc.nets, sctp_next, nnet) {
+ if (net != src_net) {
+ /* delete this address */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_del_remote_addrs_except: deleting ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1,
+ (struct sockaddr *)&net->ro._l_addr);
+ /* notify upper layer */
+ sctp_ulp_notify(SCTP_NOTIFY_ASCONF_DELETE_IP, stcb, 0,
+ (struct sockaddr *)&net->ro._l_addr, SCTP_SO_NOT_LOCKED);
+ sctp_remove_net(stcb, net);
+ }
+ }
+ return (0);
+}
+
+static struct mbuf *
+sctp_process_asconf_delete_ip(struct sockaddr *src,
+ struct sctp_asconf_paramhdr *aph,
+ struct sctp_tcb *stcb, int response_required)
+{
+ struct mbuf *m_reply = NULL;
+ union sctp_sockstore store;
+ struct sctp_paramhdr *ph;
+ uint16_t param_type, aparam_length;
+#if defined(INET) || defined(INET6)
+ uint16_t param_length;
+#endif
+ struct sockaddr *sa;
+ int zero_address = 0;
+ int result;
+#ifdef INET
+ struct sockaddr_in *sin;
+ struct sctp_ipv4addr_param *v4addr;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+ struct sctp_ipv6addr_param *v6addr;
+#endif
+
+ aparam_length = ntohs(aph->ph.param_length);
+ if (aparam_length < sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_paramhdr)) {
+ return (NULL);
+ }
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ param_type = ntohs(ph->param_type);
+#if defined(INET) || defined(INET6)
+ param_length = ntohs(ph->param_length);
+ if (param_length + sizeof(struct sctp_asconf_paramhdr) != aparam_length) {
+ return (NULL);
+ }
+#endif
+ sa = &store.sa;
+ switch (param_type) {
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv4addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v4addr = (struct sctp_ipv4addr_param *)ph;
+ sin = &store.sin;
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin->sin_port = stcb->rport;
+ sin->sin_addr.s_addr = v4addr->addr;
+ if (sin->sin_addr.s_addr == INADDR_ANY)
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_delete_ip: deleting ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv6addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v6addr = (struct sctp_ipv6addr_param *)ph;
+ sin6 = &store.sin6;
+ 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;
+ memcpy(&sin6->sin6_addr, v6addr->addr,
+ sizeof(struct in6_addr));
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_delete_ip: deleting ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+ default:
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_UNRESOLVABLE_ADDR, (uint8_t *) aph,
+ aparam_length);
+ return (m_reply);
+ }
+
+ /* make sure the source address is not being deleted */
+ if (sctp_cmpaddr(sa, src)) {
+ /* trying to delete the source address! */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_delete_ip: tried to delete source addr\n");
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_DELETING_SRC_ADDR, (uint8_t *) aph,
+ aparam_length);
+ return (m_reply);
+ }
+
+ /* if deleting 0.0.0.0/::0, delete all addresses except src addr */
+ if (zero_address && SCTP_BASE_SYSCTL(sctp_nat_friendly)) {
+ result = sctp_asconf_del_remote_addrs_except(stcb, src);
+
+ if (result) {
+ /* src address did not exist? */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_delete_ip: src addr does not exist?\n");
+ /* what error to reply with?? */
+ m_reply =
+ sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_REQUEST_REFUSED, (uint8_t *) aph,
+ aparam_length);
+ } else if (response_required) {
+ m_reply =
+ sctp_asconf_success_response(aph->correlation_id);
+ }
+ return (m_reply);
+ }
+
+ /* delete the address */
+ result = sctp_del_remote_addr(stcb, sa);
+ /*
+ * note if result == -2, the address doesn't exist in the asoc but
+ * since it's being deleted anyways, we just ack the delete -- but
+ * this probably means something has already gone awry
+ */
+ if (result == -1) {
+ /* only one address in the asoc */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_delete_ip: tried to delete last IP addr!\n");
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_DELETING_LAST_ADDR, (uint8_t *) aph,
+ aparam_length);
+ } else {
+ if (response_required) {
+ m_reply = sctp_asconf_success_response(aph->correlation_id);
+ }
+ /* notify upper layer */
+ sctp_ulp_notify(SCTP_NOTIFY_ASCONF_DELETE_IP, stcb, 0, sa, SCTP_SO_NOT_LOCKED);
+ }
+ return (m_reply);
+}
+
+static struct mbuf *
+sctp_process_asconf_set_primary(struct sockaddr *src,
+ struct sctp_asconf_paramhdr *aph,
+ struct sctp_tcb *stcb, int response_required)
+{
+ struct mbuf *m_reply = NULL;
+ union sctp_sockstore store;
+ struct sctp_paramhdr *ph;
+ uint16_t param_type, aparam_length;
+#if defined(INET) || defined(INET6)
+ uint16_t param_length;
+#endif
+ struct sockaddr *sa;
+ int zero_address = 0;
+#ifdef INET
+ struct sockaddr_in *sin;
+ struct sctp_ipv4addr_param *v4addr;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+ struct sctp_ipv6addr_param *v6addr;
+#endif
+
+ aparam_length = ntohs(aph->ph.param_length);
+ if (aparam_length < sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_paramhdr)) {
+ return (NULL);
+ }
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ param_type = ntohs(ph->param_type);
+#if defined(INET) || defined(INET6)
+ param_length = ntohs(ph->param_length);
+ if (param_length + sizeof(struct sctp_asconf_paramhdr) != aparam_length) {
+ return (NULL);
+ }
+#endif
+ sa = &store.sa;
+ switch (param_type) {
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv4addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v4addr = (struct sctp_ipv4addr_param *)ph;
+ sin = &store.sin;
+ memset(sin, 0, sizeof(*sin));
+ sin->sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ sin->sin_len = sizeof(struct sockaddr_in);
+#endif
+ sin->sin_addr.s_addr = v4addr->addr;
+ if (sin->sin_addr.s_addr == INADDR_ANY)
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_set_primary: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ if (param_length != sizeof(struct sctp_ipv6addr_param)) {
+ /* invalid param size */
+ return (NULL);
+ }
+ v6addr = (struct sctp_ipv6addr_param *)ph;
+ sin6 = &store.sin6;
+ memset(sin6, 0, sizeof(*sin6));
+ sin6->sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ sin6->sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ memcpy((caddr_t)&sin6->sin6_addr, v6addr->addr,
+ sizeof(struct in6_addr));
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr))
+ zero_address = 1;
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "process_asconf_set_primary: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ break;
+#endif
+ default:
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_UNRESOLVABLE_ADDR, (uint8_t *) aph,
+ aparam_length);
+ return (m_reply);
+ }
+
+ /* if 0.0.0.0/::0, use the source address instead */
+ if (zero_address && SCTP_BASE_SYSCTL(sctp_nat_friendly)) {
+ sa = src;
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_set_primary: using source addr ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, src);
+ }
+ /* set the primary address */
+ if (sctp_set_primary_addr(stcb, sa, NULL) == 0) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_set_primary: primary address set\n");
+ /* notify upper layer */
+ sctp_ulp_notify(SCTP_NOTIFY_ASCONF_SET_PRIMARY, stcb, 0, sa, SCTP_SO_NOT_LOCKED);
+ if ((stcb->asoc.primary_destination->dest_state & SCTP_ADDR_REACHABLE) &&
+ ((stcb->asoc.primary_destination->dest_state & SCTP_ADDR_PF) == 0) &&
+ (stcb->asoc.alternate != NULL)) {
+ sctp_free_remote_addr(stcb->asoc.alternate);
+ stcb->asoc.alternate = NULL;
+ }
+ if (response_required) {
+ m_reply = sctp_asconf_success_response(aph->correlation_id);
+ }
+ /* Mobility adaptation.
+ Ideally, when the reception of SET PRIMARY with DELETE IP
+ ADDRESS of the previous primary destination, unacknowledged
+ DATA are retransmitted immediately to the new primary
+ destination for seamless handover.
+ If the destination is UNCONFIRMED and marked to REQ_PRIM,
+ The retransmission occur when reception of the
+ HEARTBEAT-ACK. (See sctp_handle_heartbeat_ack in
+ sctp_input.c)
+ Also, when change of the primary destination, it is better
+ that all subsequent new DATA containing already queued DATA
+ are transmitted to the new primary destination. (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)) &&
+ sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_PRIM_DELETED) &&
+ (stcb->asoc.primary_destination->dest_state &
+ SCTP_ADDR_UNCONFIRMED) == 0) {
+ sctp_timer_stop(SCTP_TIMER_TYPE_PRIM_DELETED,
+ stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_ASCONF + SCTP_LOC_1);
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) {
+ sctp_assoc_immediate_retrans(stcb,
+ stcb->asoc.primary_destination);
+ }
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_BASE)) {
+ sctp_move_chunks_from_net(stcb,
+ stcb->asoc.deleted_primary);
+ }
+ sctp_delete_prim_timer(stcb->sctp_ep, stcb);
+ }
+ } else {
+ /* couldn't set the requested primary address! */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_asconf_set_primary: set primary failed!\n");
+ /* must have been an invalid address, so report */
+ m_reply = sctp_asconf_error_response(aph->correlation_id,
+ SCTP_CAUSE_UNRESOLVABLE_ADDR, (uint8_t *) aph,
+ aparam_length);
+ }
+
+ return (m_reply);
+}
+
+/*
+ * handles an ASCONF chunk.
+ * if all parameters are processed ok, send a plain (empty) ASCONF-ACK
+ */
+void
+sctp_handle_asconf(struct mbuf *m, unsigned int offset,
+ struct sockaddr *src,
+ struct sctp_asconf_chunk *cp, struct sctp_tcb *stcb,
+ int first)
+{
+ struct sctp_association *asoc;
+ uint32_t serial_num;
+ struct mbuf *n, *m_ack, *m_result, *m_tail;
+ struct sctp_asconf_ack_chunk *ack_cp;
+ struct sctp_asconf_paramhdr *aph;
+ struct sctp_ipv6addr_param *p_addr;
+ unsigned int asconf_limit, cnt;
+ int error = 0; /* did an error occur? */
+
+ /* asconf param buffer */
+ uint8_t aparam_buf[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_asconf_ack *ack, *ack_next;
+
+ /* verify minimum length */
+ if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_asconf_chunk)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf: chunk too small = %xh\n",
+ ntohs(cp->ch.chunk_length));
+ return;
+ }
+ asoc = &stcb->asoc;
+ serial_num = ntohl(cp->serial_number);
+
+ if (SCTP_TSN_GE(asoc->asconf_seq_in, serial_num)) {
+ /* got a duplicate ASCONF */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf: got duplicate serial number = %xh\n",
+ serial_num);
+ return;
+ } else if (serial_num != (asoc->asconf_seq_in + 1)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: incorrect serial number = %xh (expected next = %xh)\n",
+ serial_num, asoc->asconf_seq_in + 1);
+ return;
+ }
+
+ /* it's the expected "next" sequence number, so process it */
+ asoc->asconf_seq_in = serial_num; /* update sequence */
+ /* get length of all the param's in the ASCONF */
+ asconf_limit = offset + ntohs(cp->ch.chunk_length);
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf: asconf_limit=%u, sequence=%xh\n",
+ asconf_limit, serial_num);
+
+ if (first) {
+ /* delete old cache */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,"handle_asconf: Now processing first ASCONF. Try to delete old cache\n");
+
+ TAILQ_FOREACH_SAFE(ack, &asoc->asconf_ack_sent, next, ack_next) {
+ if (ack->serial_number == serial_num)
+ break;
+ SCTPDBG(SCTP_DEBUG_ASCONF1,"handle_asconf: delete old(%u) < first(%u)\n",
+ ack->serial_number, serial_num);
+ TAILQ_REMOVE(&asoc->asconf_ack_sent, ack, next);
+ if (ack->data != NULL) {
+ sctp_m_freem(ack->data);
+ }
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_asconf_ack), ack);
+ }
+ }
+
+ m_ack = sctp_get_mbuf_for_msg(sizeof(struct sctp_asconf_ack_chunk), 0,
+ M_NOWAIT, 1, MT_DATA);
+ if (m_ack == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf: couldn't get mbuf!\n");
+ return;
+ }
+ m_tail = m_ack; /* current reply chain's tail */
+
+ /* fill in ASCONF-ACK header */
+ ack_cp = mtod(m_ack, struct sctp_asconf_ack_chunk *);
+ ack_cp->ch.chunk_type = SCTP_ASCONF_ACK;
+ ack_cp->ch.chunk_flags = 0;
+ ack_cp->serial_number = htonl(serial_num);
+ /* set initial lengths (eg. just an ASCONF-ACK), ntohx at the end! */
+ SCTP_BUF_LEN(m_ack) = sizeof(struct sctp_asconf_ack_chunk);
+ ack_cp->ch.chunk_length = sizeof(struct sctp_asconf_ack_chunk);
+
+ /* skip the lookup address parameter */
+ offset += sizeof(struct sctp_asconf_chunk);
+ p_addr = (struct sctp_ipv6addr_param *)sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr), (uint8_t *)&aparam_buf);
+ if (p_addr == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf: couldn't get lookup addr!\n");
+ /* respond with a missing/invalid mandatory parameter error */
+ sctp_m_freem(m_ack);
+ return;
+ }
+ /* skip lookup addr */
+ offset += SCTP_SIZE32(ntohs(p_addr->ph.param_length));
+ /* get pointer to first asconf param in ASCONF */
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, sizeof(struct sctp_asconf_paramhdr), (uint8_t *)&aparam_buf);
+ if (aph == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "Empty ASCONF received?\n");
+ goto send_reply;
+ }
+ /* process through all parameters */
+ cnt = 0;
+ while (aph != NULL) {
+ unsigned int param_length, param_type;
+
+ param_type = ntohs(aph->ph.param_type);
+ param_length = ntohs(aph->ph.param_length);
+ if (offset + param_length > asconf_limit) {
+ /* parameter goes beyond end of chunk! */
+ sctp_m_freem(m_ack);
+ return;
+ }
+ m_result = NULL;
+
+ if (param_length > sizeof(aparam_buf)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: param length (%u) larger than buffer size!\n", param_length);
+ sctp_m_freem(m_ack);
+ return;
+ }
+ if (param_length < sizeof(struct sctp_asconf_paramhdr)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: param length (%u) too short\n", param_length);
+ sctp_m_freem(m_ack);
+ return;
+ }
+ /* get the entire parameter */
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, param_length, aparam_buf);
+ if (aph == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: couldn't get entire param\n");
+ sctp_m_freem(m_ack);
+ return;
+ }
+ switch (param_type) {
+ case SCTP_ADD_IP_ADDRESS:
+ m_result = sctp_process_asconf_add_ip(src, aph, stcb,
+ (cnt < SCTP_BASE_SYSCTL(sctp_hb_maxburst)), error);
+ cnt++;
+ break;
+ case SCTP_DEL_IP_ADDRESS:
+ m_result = sctp_process_asconf_delete_ip(src, aph, stcb,
+ error);
+ break;
+ case SCTP_ERROR_CAUSE_IND:
+ /* not valid in an ASCONF chunk */
+ break;
+ case SCTP_SET_PRIM_ADDR:
+ m_result = sctp_process_asconf_set_primary(src, aph,
+ stcb, error);
+ break;
+ case SCTP_NAT_VTAGS:
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: sees a NAT VTAG state parameter\n");
+ break;
+ case SCTP_SUCCESS_REPORT:
+ /* not valid in an ASCONF chunk */
+ break;
+ case SCTP_ULP_ADAPTATION:
+ /* FIX */
+ break;
+ default:
+ if ((param_type & 0x8000) == 0) {
+ /* Been told to STOP at this param */
+ asconf_limit = offset;
+ /*
+ * FIX FIX - We need to call
+ * sctp_arethere_unrecognized_parameters()
+ * to get a operr and send it for any
+ * param's with the 0x4000 bit set OR do it
+ * here ourselves... note we still must STOP
+ * if the 0x8000 bit is clear.
+ */
+ }
+ /* unknown/invalid param type */
+ break;
+ } /* switch */
+
+ /* add any (error) result to the reply mbuf chain */
+ if (m_result != NULL) {
+ SCTP_BUF_NEXT(m_tail) = m_result;
+ m_tail = m_result;
+ ack_cp->ch.chunk_length += SCTP_BUF_LEN(m_result);
+ /* set flag to force success reports */
+ error = 1;
+ }
+ offset += SCTP_SIZE32(param_length);
+ /* update remaining ASCONF message length to process */
+ if (offset >= asconf_limit) {
+ /* no more data in the mbuf chain */
+ break;
+ }
+ /* get pointer to next asconf param */
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_asconf_paramhdr),
+ (uint8_t *)&aparam_buf);
+ if (aph == NULL) {
+ /* can't get an asconf paramhdr */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: can't get asconf param hdr!\n");
+ /* FIX ME - add error here... */
+ }
+ }
+
+ send_reply:
+ ack_cp->ch.chunk_length = htons(ack_cp->ch.chunk_length);
+ /* save the ASCONF-ACK reply */
+ ack = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_asconf_ack),
+ struct sctp_asconf_ack);
+ if (ack == NULL) {
+ sctp_m_freem(m_ack);
+ return;
+ }
+ ack->serial_number = serial_num;
+ ack->last_sent_to = NULL;
+ ack->data = m_ack;
+ ack->len = 0;
+ for (n = m_ack; n != NULL; n = SCTP_BUF_NEXT(n)) {
+ ack->len += SCTP_BUF_LEN(n);
+ }
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_ack_sent, ack, next);
+
+ /* see if last_control_chunk_from is set properly (use IP src addr) */
+ if (stcb->asoc.last_control_chunk_from == NULL) {
+ /*
+ * this could happen if the source address was just newly
+ * added
+ */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: looking up net for IP source address\n");
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "Looking for IP source: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, src);
+ /* look up the from address */
+ stcb->asoc.last_control_chunk_from = sctp_findnet(stcb, src);
+#ifdef SCTP_DEBUG
+ if (stcb->asoc.last_control_chunk_from == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf: IP source address not found?!\n");
+ }
+#endif
+ }
+}
+
+/*
+ * does the address match? returns 0 if not, 1 if so
+ */
+static uint32_t
+sctp_asconf_addr_match(struct sctp_asconf_addr *aa, struct sockaddr *sa)
+{
+ switch (sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* XXX scopeid */
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+
+ if ((aa->ap.addrp.ph.param_type == SCTP_IPV6_ADDRESS) &&
+ (memcmp(&aa->ap.addrp.addr, &sin6->sin6_addr,
+ sizeof(struct in6_addr)) == 0)) {
+ return (1);
+ }
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+
+ if ((aa->ap.addrp.ph.param_type == SCTP_IPV4_ADDRESS) &&
+ (memcmp(&aa->ap.addrp.addr, &sin->sin_addr,
+ sizeof(struct in_addr)) == 0)) {
+ return (1);
+ }
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+ return (0);
+}
+
+/*
+ * does the address match? returns 0 if not, 1 if so
+ */
+static uint32_t
+sctp_addr_match(struct sctp_paramhdr *ph, struct sockaddr *sa)
+{
+#if defined(INET) || defined(INET6)
+ uint16_t param_type, param_length;
+
+ param_type = ntohs(ph->param_type);
+ param_length = ntohs(ph->param_length);
+#endif
+ switch (sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* XXX scopeid */
+ struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+ struct sctp_ipv6addr_param *v6addr;
+
+ v6addr = (struct sctp_ipv6addr_param *)ph;
+ if ((param_type == SCTP_IPV6_ADDRESS) &&
+ (param_length == sizeof(struct sctp_ipv6addr_param)) &&
+ (memcmp(&v6addr->addr, &sin6->sin6_addr,
+ sizeof(struct in6_addr)) == 0)) {
+ return (1);
+ }
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+ struct sctp_ipv4addr_param *v4addr;
+
+ v4addr = (struct sctp_ipv4addr_param *)ph;
+ if ((param_type == SCTP_IPV4_ADDRESS) &&
+ (param_length == sizeof(struct sctp_ipv4addr_param)) &&
+ (memcmp(&v4addr->addr, &sin->sin_addr,
+ sizeof(struct in_addr)) == 0)) {
+ return (1);
+ }
+ break;
+ }
+#endif
+ default:
+ break;
+ }
+ return (0);
+}
+/*
+ * Cleanup for non-responded/OP ERR'd ASCONF
+ */
+void
+sctp_asconf_cleanup(struct sctp_tcb *stcb)
+{
+ /*
+ * clear out any existing asconfs going out
+ */
+ sctp_timer_stop(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_ASCONF + SCTP_LOC_2);
+ stcb->asoc.asconf_seq_out_acked = stcb->asoc.asconf_seq_out;
+ /* remove the old ASCONF on our outbound queue */
+ sctp_toss_old_asconf(stcb);
+}
+
+/*
+ * cleanup any cached source addresses that may be topologically
+ * incorrect after a new address has been added to this interface.
+ */
+static void
+sctp_asconf_nets_cleanup(struct sctp_tcb *stcb, struct sctp_ifn *ifn)
+{
+ struct sctp_nets *net;
+
+ /*
+ * Ideally, we want to only clear cached routes and source addresses
+ * that are topologically incorrect. But since there is no easy way
+ * to know whether the newly added address on the ifn would cause a
+ * routing change (i.e. a new egress interface would be chosen)
+ * without doing a new routing lookup and source address selection,
+ * we will (for now) just flush any cached route using a different
+ * ifn (and cached source addrs) and let output re-choose them during
+ * the next send on that net.
+ */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /*
+ * clear any cached route (and cached source address) if the
+ * route's interface is NOT the same as the address change.
+ * If it's the same interface, just clear the cached source
+ * address.
+ */
+ if (SCTP_ROUTE_HAS_VALID_IFN(&net->ro) &&
+ ((ifn == NULL) ||
+ (SCTP_GET_IF_INDEX_FROM_ROUTE(&net->ro) != ifn->ifn_index))) {
+ /* clear any cached route */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(&net->ro);
+#else
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+#endif
+ }
+ /* clear any cached source address */
+ if (net->src_addr_selected) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+ }
+ }
+}
+
+void
+sctp_assoc_immediate_retrans(struct sctp_tcb *stcb, struct sctp_nets *dstnet)
+{
+ int error;
+
+ if (dstnet->dest_state & SCTP_ADDR_UNCONFIRMED) {
+ return;
+ }
+ if (stcb->asoc.deleted_primary == NULL) {
+ return;
+ }
+
+ if (!TAILQ_EMPTY(&stcb->asoc.sent_queue)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "assoc_immediate_retrans: Deleted primary is ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, &stcb->asoc.deleted_primary->ro._l_addr.sa);
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "Current Primary is ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, &stcb->asoc.primary_destination->ro._l_addr.sa);
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb,
+ stcb->asoc.deleted_primary,
+ SCTP_FROM_SCTP_ASCONF + SCTP_LOC_3);
+ stcb->asoc.num_send_timers_up--;
+ if (stcb->asoc.num_send_timers_up < 0) {
+ stcb->asoc.num_send_timers_up = 0;
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+ error = sctp_t3rxt_timer(stcb->sctp_ep, stcb,
+ stcb->asoc.deleted_primary);
+ if (error) {
+ SCTP_INP_DECR_REF(stcb->sctp_ep);
+ return;
+ }
+ SCTP_TCB_LOCK_ASSERT(stcb);
+#ifdef SCTP_AUDITING_ENABLED
+ sctp_auditing(4, stcb->sctp_ep, stcb, stcb->asoc.deleted_primary);
+#endif
+ sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_T3, SCTP_SO_NOT_LOCKED);
+ if ((stcb->asoc.num_send_timers_up == 0) &&
+ (stcb->asoc.sent_queue_cnt > 0)) {
+ struct sctp_tmit_chunk *chk;
+
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if (chk->whoTo != NULL) {
+ break;
+ }
+ }
+ if (chk != NULL) {
+ sctp_timer_start(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, chk->whoTo);
+ }
+ }
+ }
+ return;
+}
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Userspace__)
+static int
+sctp_asconf_queue_mgmt(struct sctp_tcb *, struct sctp_ifa *, uint16_t);
+
+void
+sctp_net_immediate_retrans(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ struct sctp_tmit_chunk *chk;
+
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "net_immediate_retrans: RTO is %d\n", net->RTO);
+ sctp_timer_stop(SCTP_TIMER_TYPE_SEND, stcb->sctp_ep, stcb, net,
+ SCTP_FROM_SCTP_ASCONF + SCTP_LOC_4);
+ stcb->asoc.cc_functions.sctp_set_initial_cc_param(stcb, net);
+ net->error_count = 0;
+ TAILQ_FOREACH(chk, &stcb->asoc.sent_queue, sctp_next) {
+ if (chk->whoTo == net) {
+ if (chk->sent < SCTP_DATAGRAM_RESEND) {
+ chk->sent = SCTP_DATAGRAM_RESEND;
+ sctp_ucount_incr(stcb->asoc.sent_queue_retran_cnt);
+ sctp_flight_size_decrease(chk);
+ sctp_total_flight_decrease(stcb, chk);
+ net->marked_retrans++;
+ stcb->asoc.marked_retrans++;
+ }
+ }
+ }
+ if (net->marked_retrans) {
+ sctp_chunk_output(stcb->sctp_ep, stcb, SCTP_OUTPUT_FROM_T3, SCTP_SO_NOT_LOCKED);
+ }
+}
+
+static void
+sctp_path_check_and_react(struct sctp_tcb *stcb, struct sctp_ifa *newifa)
+{
+ struct sctp_nets *net;
+ int addrnum, changed;
+
+ /* If number of local valid addresses is 1, the valid address is
+ probably newly added address.
+ Several valid addresses in this association. A source address
+ may not be changed. Additionally, they can be configured on a
+ same interface as "alias" addresses. (by micchie)
+ */
+ addrnum = sctp_local_addr_count(stcb);
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "p_check_react(): %d local addresses\n",
+ addrnum);
+ if (addrnum == 1) {
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /* clear any cached route and source address */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(&net->ro);
+#else
+ if (net->ro.ro_rt) {
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+ }
+#endif
+ if (net->src_addr_selected) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+ }
+ /* Retransmit unacknowledged DATA chunks immediately */
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) {
+ sctp_net_immediate_retrans(stcb, net);
+ }
+ /* also, SET PRIMARY is maybe already sent */
+ }
+ return;
+ }
+
+ /* Multiple local addresses exist in the association. */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /* clear any cached route and source address */
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(&net->ro);
+#else
+ if (net->ro.ro_rt) {
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+ }
+#endif
+ if (net->src_addr_selected) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+ }
+ /* Check if the nexthop is corresponding to the new address.
+ If the new address is corresponding to the current nexthop,
+ the path will be changed.
+ If the new address is NOT corresponding to the current
+ nexthop, the path will not be changed.
+ */
+ SCTP_RTALLOC((sctp_route_t *)&net->ro,
+ stcb->sctp_ep->def_vrf_id,
+ stcb->sctp_ep->fibnum);
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (net->ro.ro_nh == NULL)
+#else
+ if (net->ro.ro_rt == NULL)
+#endif
+ continue;
+
+ changed = 0;
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (sctp_v4src_match_nexthop(newifa, (sctp_route_t *)&net->ro)) {
+ changed = 1;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (sctp_v6src_match_nexthop(
+ &newifa->address.sin6, (sctp_route_t *)&net->ro)) {
+ changed = 1;
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ /* if the newly added address does not relate routing
+ information, we skip.
+ */
+ if (changed == 0)
+ continue;
+ /* Retransmit unacknowledged DATA chunks immediately */
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) {
+ sctp_net_immediate_retrans(stcb, net);
+ }
+ /* Send SET PRIMARY for this new address */
+ if (net == stcb->asoc.primary_destination) {
+ (void)sctp_asconf_queue_mgmt(stcb, newifa,
+ SCTP_SET_PRIM_ADDR);
+ }
+ }
+}
+#endif
+
+/*
+ * process an ADD/DELETE IP ack from peer.
+ * addr: corresponding sctp_ifa to the address being added/deleted.
+ * type: SCTP_ADD_IP_ADDRESS or SCTP_DEL_IP_ADDRESS.
+ * flag: 1=success, 0=failure.
+ */
+static void
+sctp_asconf_addr_mgmt_ack(struct sctp_tcb *stcb, struct sctp_ifa *addr, uint32_t flag)
+{
+ /*
+ * do the necessary asoc list work- if we get a failure indication,
+ * leave the address on the assoc's restricted list. If we get a
+ * success indication, remove the address from the restricted list.
+ */
+ /*
+ * Note: this will only occur for ADD_IP_ADDRESS, since
+ * DEL_IP_ADDRESS is never actually added to the list...
+ */
+ if (flag) {
+ /* success case, so remove from the restricted list */
+ sctp_del_local_addr_restricted(stcb, addr);
+
+#if defined(__FreeBSD__) || defined(__APPLE__) || defined(__Userspace__)
+ if (sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_BASE) ||
+ sctp_is_mobility_feature_on(stcb->sctp_ep,
+ SCTP_MOBILITY_FASTHANDOFF)) {
+ sctp_path_check_and_react(stcb, addr);
+ return;
+ }
+#endif
+ /* clear any cached/topologically incorrect source addresses */
+ sctp_asconf_nets_cleanup(stcb, addr->ifn_p);
+ }
+ /* else, leave it on the list */
+}
+
+/*
+ * add an asconf add/delete/set primary IP address parameter to the queue.
+ * type = SCTP_ADD_IP_ADDRESS, SCTP_DEL_IP_ADDRESS, SCTP_SET_PRIM_ADDR.
+ * returns 0 if queued, -1 if not queued/removed.
+ * NOTE: if adding, but a delete for the same address is already scheduled
+ * (and not yet sent out), simply remove it from queue. Same for deleting
+ * an address already scheduled for add. If a duplicate operation is found,
+ * ignore the new one.
+ */
+static int
+sctp_asconf_queue_mgmt(struct sctp_tcb *stcb, struct sctp_ifa *ifa,
+ uint16_t type)
+{
+ struct sctp_asconf_addr *aa, *aa_next;
+
+ /* make sure the request isn't already in the queue */
+ TAILQ_FOREACH_SAFE(aa, &stcb->asoc.asconf_queue, next, aa_next) {
+ /* address match? */
+ if (sctp_asconf_addr_match(aa, &ifa->address.sa) == 0)
+ continue;
+ /* is the request already in queue but not sent?
+ * pass the request already sent in order to resolve the following case:
+ * 1. arrival of ADD, then sent
+ * 2. arrival of DEL. we can't remove the ADD request already sent
+ * 3. arrival of ADD
+ */
+ if (aa->ap.aph.ph.param_type == type && aa->sent == 0) {
+ return (-1);
+ }
+ /* is the negative request already in queue, and not sent */
+ if ((aa->sent == 0) && (type == SCTP_ADD_IP_ADDRESS) &&
+ (aa->ap.aph.ph.param_type == SCTP_DEL_IP_ADDRESS)) {
+ /* add requested, delete already queued */
+ TAILQ_REMOVE(&stcb->asoc.asconf_queue, aa, next);
+ /* remove the ifa from the restricted list */
+ sctp_del_local_addr_restricted(stcb, ifa);
+ /* free the asconf param */
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ SCTPDBG(SCTP_DEBUG_ASCONF2, "asconf_queue_mgmt: add removes queued entry\n");
+ return (-1);
+ }
+ if ((aa->sent == 0) && (type == SCTP_DEL_IP_ADDRESS) &&
+ (aa->ap.aph.ph.param_type == SCTP_ADD_IP_ADDRESS)) {
+ /* delete requested, add already queued */
+ TAILQ_REMOVE(&stcb->asoc.asconf_queue, aa, next);
+ /* remove the aa->ifa from the restricted list */
+ sctp_del_local_addr_restricted(stcb, aa->ifa);
+ /* free the asconf param */
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ SCTPDBG(SCTP_DEBUG_ASCONF2, "asconf_queue_mgmt: delete removes queued entry\n");
+ return (-1);
+ }
+ } /* for each aa */
+
+ /* adding new request to the queue */
+ SCTP_MALLOC(aa, struct sctp_asconf_addr *, sizeof(*aa),
+ SCTP_M_ASC_ADDR);
+ if (aa == NULL) {
+ /* didn't get memory */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "asconf_queue_mgmt: failed to get memory!\n");
+ return (-1);
+ }
+ aa->special_del = 0;
+ /* fill in asconf address parameter fields */
+ /* top level elements are "networked" during send */
+ aa->ap.aph.ph.param_type = type;
+ aa->ifa = ifa;
+ atomic_add_int(&ifa->refcount, 1);
+ /* correlation_id filled in during send routine later... */
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = &ifa->address.sin6;
+ aa->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS;
+ aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv6addr_param));
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_paramhdr) +
+ sizeof(struct sctp_ipv6addr_param);
+ memcpy(&aa->ap.addrp.addr, &sin6->sin6_addr,
+ sizeof(struct in6_addr));
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ sin = &ifa->address.sin;
+ aa->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS;
+ aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv4addr_param));
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_paramhdr) +
+ sizeof(struct sctp_ipv4addr_param);
+ memcpy(&aa->ap.addrp.addr, &sin->sin_addr,
+ sizeof(struct in_addr));
+ break;
+ }
+#endif
+ default:
+ /* invalid family! */
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ sctp_free_ifa(ifa);
+ return (-1);
+ }
+ aa->sent = 0; /* clear sent flag */
+
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next);
+#ifdef SCTP_DEBUG
+ if (SCTP_BASE_SYSCTL(sctp_debug_on) & SCTP_DEBUG_ASCONF2) {
+ if (type == SCTP_ADD_IP_ADDRESS) {
+ SCTP_PRINTF("asconf_queue_mgmt: inserted asconf ADD_IP_ADDRESS: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF2, &ifa->address.sa);
+ } else if (type == SCTP_DEL_IP_ADDRESS) {
+ SCTP_PRINTF("asconf_queue_mgmt: appended asconf DEL_IP_ADDRESS: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF2, &ifa->address.sa);
+ } else {
+ SCTP_PRINTF("asconf_queue_mgmt: appended asconf SET_PRIM_ADDR: ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF2, &ifa->address.sa);
+ }
+ }
+#endif
+
+ return (0);
+}
+
+/*
+ * add an asconf operation for the given ifa and type.
+ * type = SCTP_ADD_IP_ADDRESS, SCTP_DEL_IP_ADDRESS, SCTP_SET_PRIM_ADDR.
+ * returns 0 if completed, -1 if not completed, 1 if immediate send is
+ * advisable.
+ */
+static int
+sctp_asconf_queue_add(struct sctp_tcb *stcb, struct sctp_ifa *ifa,
+ uint16_t type)
+{
+ uint32_t status;
+ int pending_delete_queued = 0;
+ int last;
+
+ /* see if peer supports ASCONF */
+ if (stcb->asoc.asconf_supported == 0) {
+ return (-1);
+ }
+
+ /*
+ * if this is deleting the last address from the assoc, mark it as
+ * pending.
+ */
+ if ((type == SCTP_DEL_IP_ADDRESS) && !stcb->asoc.asconf_del_pending) {
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ last = (sctp_local_addr_count(stcb) == 0);
+ } else {
+ last = (sctp_local_addr_count(stcb) == 1);
+ }
+ if (last) {
+ /* set the pending delete info only */
+ stcb->asoc.asconf_del_pending = 1;
+ stcb->asoc.asconf_addr_del_pending = ifa;
+ atomic_add_int(&ifa->refcount, 1);
+ SCTPDBG(SCTP_DEBUG_ASCONF2,
+ "asconf_queue_add: mark delete last address pending\n");
+ return (-1);
+ }
+ }
+
+ /* queue an asconf parameter */
+ status = sctp_asconf_queue_mgmt(stcb, ifa, type);
+
+ /*
+ * if this is an add, and there is a delete also pending (i.e. the
+ * last local address is being changed), queue the pending delete too.
+ */
+ if ((type == SCTP_ADD_IP_ADDRESS) && stcb->asoc.asconf_del_pending && (status == 0)) {
+ /* queue in the pending delete */
+ if (sctp_asconf_queue_mgmt(stcb,
+ stcb->asoc.asconf_addr_del_pending,
+ SCTP_DEL_IP_ADDRESS) == 0) {
+ SCTPDBG(SCTP_DEBUG_ASCONF2, "asconf_queue_add: queuing pending delete\n");
+ pending_delete_queued = 1;
+ /* clear out the pending delete info */
+ stcb->asoc.asconf_del_pending = 0;
+ sctp_free_ifa(stcb->asoc.asconf_addr_del_pending);
+ stcb->asoc.asconf_addr_del_pending = NULL;
+ }
+ }
+
+ if (pending_delete_queued) {
+ struct sctp_nets *net;
+ /*
+ * since we know that the only/last address is now being
+ * changed in this case, reset the cwnd/rto on all nets to
+ * start as a new address and path. Also clear the error
+ * counts to give the assoc the best chance to complete the
+ * address change.
+ */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ stcb->asoc.cc_functions.sctp_set_initial_cc_param(stcb,
+ net);
+ net->RTO = 0;
+ net->error_count = 0;
+ }
+ stcb->asoc.overall_error_count = 0;
+ if (SCTP_BASE_SYSCTL(sctp_logging_level) & SCTP_THRESHOLD_LOGGING) {
+ sctp_misc_ints(SCTP_THRESHOLD_CLEAR,
+ stcb->asoc.overall_error_count,
+ 0,
+ SCTP_FROM_SCTP_ASCONF,
+ __LINE__);
+ }
+
+ /* queue in an advisory set primary too */
+ (void)sctp_asconf_queue_mgmt(stcb, ifa, SCTP_SET_PRIM_ADDR);
+ /* let caller know we should send this out immediately */
+ status = 1;
+ }
+ return (status);
+}
+
+/*-
+ * add an asconf delete IP address parameter to the queue by sockaddr and
+ * possibly with no sctp_ifa available. This is only called by the routine
+ * that checks the addresses in an INIT-ACK against the current address list.
+ * returns 0 if completed, non-zero if not completed.
+ * NOTE: if an add is already scheduled (and not yet sent out), simply
+ * remove it from queue. If a duplicate operation is found, ignore the
+ * new one.
+ */
+static int
+sctp_asconf_queue_sa_delete(struct sctp_tcb *stcb, struct sockaddr *sa)
+{
+ struct sctp_ifa *ifa;
+ struct sctp_asconf_addr *aa, *aa_next;
+
+ if (stcb == NULL) {
+ return (-1);
+ }
+ /* see if peer supports ASCONF */
+ if (stcb->asoc.asconf_supported == 0) {
+ return (-1);
+ }
+ /* make sure the request isn't already in the queue */
+ TAILQ_FOREACH_SAFE(aa, &stcb->asoc.asconf_queue, next, aa_next) {
+ /* address match? */
+ if (sctp_asconf_addr_match(aa, sa) == 0)
+ continue;
+ /* is the request already in queue (sent or not) */
+ if (aa->ap.aph.ph.param_type == SCTP_DEL_IP_ADDRESS) {
+ return (-1);
+ }
+ /* is the negative request already in queue, and not sent */
+ if (aa->sent == 1)
+ continue;
+ if (aa->ap.aph.ph.param_type == SCTP_ADD_IP_ADDRESS) {
+ /* add already queued, so remove existing entry */
+ TAILQ_REMOVE(&stcb->asoc.asconf_queue, aa, next);
+ sctp_del_local_addr_restricted(stcb, aa->ifa);
+ /* free the entry */
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ return (-1);
+ }
+ } /* for each aa */
+
+ /* find any existing ifa-- NOTE ifa CAN be allowed to be NULL */
+ ifa = sctp_find_ifa_by_addr(sa, stcb->asoc.vrf_id, SCTP_ADDR_NOT_LOCKED);
+
+ /* adding new request to the queue */
+ SCTP_MALLOC(aa, struct sctp_asconf_addr *, sizeof(*aa),
+ SCTP_M_ASC_ADDR);
+ if (aa == NULL) {
+ /* didn't get memory */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_asconf_queue_sa_delete: failed to get memory!\n");
+ return (-1);
+ }
+ aa->special_del = 0;
+ /* fill in asconf address parameter fields */
+ /* top level elements are "networked" during send */
+ aa->ap.aph.ph.param_type = SCTP_DEL_IP_ADDRESS;
+ aa->ifa = ifa;
+ if (ifa)
+ atomic_add_int(&ifa->refcount, 1);
+ /* correlation_id filled in during send routine later... */
+ switch (sa->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* IPv6 address */
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)sa;
+ aa->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS;
+ aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv6addr_param));
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_ipv6addr_param);
+ memcpy(&aa->ap.addrp.addr, &sin6->sin6_addr,
+ sizeof(struct in6_addr));
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ /* IPv4 address */
+ struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+
+ aa->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS;
+ aa->ap.addrp.ph.param_length = (sizeof(struct sctp_ipv4addr_param));
+ aa->ap.aph.ph.param_length = sizeof(struct sctp_asconf_paramhdr) + sizeof(struct sctp_ipv4addr_param);
+ memcpy(&aa->ap.addrp.addr, &sin->sin_addr,
+ sizeof(struct in_addr));
+ break;
+ }
+#endif
+ default:
+ /* invalid family! */
+ SCTP_FREE(aa, SCTP_M_ASC_ADDR);
+ if (ifa)
+ sctp_free_ifa(ifa);
+ return (-1);
+ }
+ aa->sent = 0; /* clear sent flag */
+
+ /* delete goes to the back of the queue */
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa, next);
+
+ /* sa_ignore MEMLEAK {memory is put on the tailq} */
+ return (0);
+}
+
+/*
+ * find a specific asconf param on our "sent" queue
+ */
+static struct sctp_asconf_addr *
+sctp_asconf_find_param(struct sctp_tcb *stcb, uint32_t correlation_id)
+{
+ struct sctp_asconf_addr *aa;
+
+ TAILQ_FOREACH(aa, &stcb->asoc.asconf_queue, next) {
+ if (aa->ap.aph.correlation_id == correlation_id &&
+ aa->sent == 1) {
+ /* found it */
+ return (aa);
+ }
+ }
+ /* didn't find it */
+ return (NULL);
+}
+
+/*
+ * process an SCTP_ERROR_CAUSE_IND for a ASCONF-ACK parameter and do
+ * notifications based on the error response
+ */
+static void
+sctp_asconf_process_error(struct sctp_tcb *stcb SCTP_UNUSED,
+ struct sctp_asconf_paramhdr *aph)
+{
+ struct sctp_error_cause *eh;
+ struct sctp_paramhdr *ph;
+ uint16_t param_type;
+ uint16_t error_code;
+
+ eh = (struct sctp_error_cause *)(aph + 1);
+ ph = (struct sctp_paramhdr *)(eh + 1);
+ /* validate lengths */
+ if (htons(eh->length) + sizeof(struct sctp_error_cause) >
+ htons(aph->ph.param_length)) {
+ /* invalid error cause length */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_process_error: cause element too long\n");
+ return;
+ }
+ if (htons(ph->param_length) + sizeof(struct sctp_paramhdr) >
+ htons(eh->length)) {
+ /* invalid included TLV length */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "asconf_process_error: included TLV too long\n");
+ return;
+ }
+ /* which error code ? */
+ error_code = ntohs(eh->code);
+ param_type = ntohs(aph->ph.param_type);
+ /* FIX: this should go back up the REMOTE_ERROR ULP notify */
+ switch (error_code) {
+ case SCTP_CAUSE_RESOURCE_SHORTAGE:
+ /* we allow ourselves to "try again" for this error */
+ break;
+ default:
+ /* peer can't handle it... */
+ switch (param_type) {
+ case SCTP_ADD_IP_ADDRESS:
+ case SCTP_DEL_IP_ADDRESS:
+ case SCTP_SET_PRIM_ADDR:
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+/*
+ * process an asconf queue param.
+ * aparam: parameter to process, will be removed from the queue.
+ * flag: 1=success case, 0=failure case
+ */
+static void
+sctp_asconf_process_param_ack(struct sctp_tcb *stcb,
+ struct sctp_asconf_addr *aparam, uint32_t flag)
+{
+ uint16_t param_type;
+
+ /* process this param */
+ param_type = aparam->ap.aph.ph.param_type;
+ switch (param_type) {
+ case SCTP_ADD_IP_ADDRESS:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_param_ack: added IP address\n");
+ sctp_asconf_addr_mgmt_ack(stcb, aparam->ifa, flag);
+ break;
+ case SCTP_DEL_IP_ADDRESS:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_param_ack: deleted IP address\n");
+ /* nothing really to do... lists already updated */
+ break;
+ case SCTP_SET_PRIM_ADDR:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "process_param_ack: set primary IP address\n");
+ /* nothing to do... peer may start using this addr */
+ break;
+ default:
+ /* should NEVER happen */
+ break;
+ }
+
+ /* remove the param and free it */
+ TAILQ_REMOVE(&stcb->asoc.asconf_queue, aparam, next);
+ if (aparam->ifa)
+ sctp_free_ifa(aparam->ifa);
+ SCTP_FREE(aparam, SCTP_M_ASC_ADDR);
+}
+
+/*
+ * cleanup from a bad asconf ack parameter
+ */
+static void
+sctp_asconf_ack_clear(struct sctp_tcb *stcb SCTP_UNUSED)
+{
+ /* assume peer doesn't really know how to do asconfs */
+ /* XXX we could free the pending queue here */
+
+}
+
+void
+sctp_handle_asconf_ack(struct mbuf *m, int offset,
+ struct sctp_asconf_ack_chunk *cp, struct sctp_tcb *stcb,
+ struct sctp_nets *net, int *abort_no_unlock)
+{
+ struct sctp_association *asoc;
+ uint32_t serial_num;
+ uint16_t ack_length;
+ struct sctp_asconf_paramhdr *aph;
+ struct sctp_asconf_addr *aa, *aa_next;
+ uint32_t last_error_id = 0; /* last error correlation id */
+ uint32_t id;
+ struct sctp_asconf_addr *ap;
+
+ /* asconf param buffer */
+ uint8_t aparam_buf[SCTP_PARAM_BUFFER_SIZE];
+
+ /* verify minimum length */
+ if (ntohs(cp->ch.chunk_length) < sizeof(struct sctp_asconf_ack_chunk)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "handle_asconf_ack: chunk too small = %xh\n",
+ ntohs(cp->ch.chunk_length));
+ return;
+ }
+ asoc = &stcb->asoc;
+ serial_num = ntohl(cp->serial_number);
+
+ /*
+ * NOTE: we may want to handle this differently- currently, we will
+ * abort when we get an ack for the expected serial number + 1 (eg.
+ * we didn't send it), process an ack normally if it is the expected
+ * serial number, and re-send the previous ack for *ALL* other
+ * serial numbers
+ */
+
+ /*
+ * if the serial number is the next expected, but I didn't send it,
+ * abort the asoc, since someone probably just hijacked us...
+ */
+ if (serial_num == (asoc->asconf_seq_out + 1)) {
+ struct mbuf *op_err;
+ char msg[SCTP_DIAG_INFO_LEN];
+
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf_ack: got unexpected next serial number! Aborting asoc!\n");
+ SCTP_SNPRINTF(msg, sizeof(msg), "Never sent serial number %8.8x", serial_num);
+ op_err = sctp_generate_cause(SCTP_CAUSE_PROTOCOL_VIOLATION, msg);
+ sctp_abort_an_association(stcb->sctp_ep, stcb, op_err, false, SCTP_SO_NOT_LOCKED);
+ *abort_no_unlock = 1;
+ return;
+ }
+ if (serial_num != asoc->asconf_seq_out_acked + 1) {
+ /* got a duplicate/unexpected ASCONF-ACK */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "handle_asconf_ack: got duplicate/unexpected serial number = %xh (expected = %xh)\n",
+ serial_num, asoc->asconf_seq_out_acked + 1);
+ return;
+ }
+
+ if (serial_num == asoc->asconf_seq_out - 1) {
+ /* stop our timer */
+ sctp_timer_stop(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep, stcb, NULL,
+ SCTP_FROM_SCTP_ASCONF + SCTP_LOC_5);
+ }
+
+ /* process the ASCONF-ACK contents */
+ ack_length = ntohs(cp->ch.chunk_length) -
+ sizeof(struct sctp_asconf_ack_chunk);
+ offset += sizeof(struct sctp_asconf_ack_chunk);
+ /* process through all parameters */
+ while (ack_length >= sizeof(struct sctp_asconf_paramhdr)) {
+ unsigned int param_length, param_type;
+
+ /* get pointer to next asconf parameter */
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_asconf_paramhdr), aparam_buf);
+ if (aph == NULL) {
+ /* can't get an asconf paramhdr */
+ sctp_asconf_ack_clear(stcb);
+ return;
+ }
+ param_type = ntohs(aph->ph.param_type);
+ param_length = ntohs(aph->ph.param_length);
+ if (param_length > ack_length) {
+ sctp_asconf_ack_clear(stcb);
+ return;
+ }
+ if (param_length < sizeof(struct sctp_asconf_paramhdr)) {
+ sctp_asconf_ack_clear(stcb);
+ return;
+ }
+ /* get the complete parameter... */
+ if (param_length > sizeof(aparam_buf)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "param length (%u) larger than buffer size!\n", param_length);
+ sctp_asconf_ack_clear(stcb);
+ return;
+ }
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(m, offset, param_length, aparam_buf);
+ if (aph == NULL) {
+ sctp_asconf_ack_clear(stcb);
+ return;
+ }
+ /* correlation_id is transparent to peer, no ntohl needed */
+ id = aph->correlation_id;
+
+ switch (param_type) {
+ case SCTP_ERROR_CAUSE_IND:
+ last_error_id = id;
+ /* find the corresponding asconf param in our queue */
+ ap = sctp_asconf_find_param(stcb, id);
+ if (ap == NULL) {
+ /* hmm... can't find this in our queue! */
+ break;
+ }
+ /* process the parameter, failed flag */
+ sctp_asconf_process_param_ack(stcb, ap, 0);
+ /* process the error response */
+ sctp_asconf_process_error(stcb, aph);
+ break;
+ case SCTP_SUCCESS_REPORT:
+ /* find the corresponding asconf param in our queue */
+ ap = sctp_asconf_find_param(stcb, id);
+ if (ap == NULL) {
+ /* hmm... can't find this in our queue! */
+ break;
+ }
+ /* process the parameter, success flag */
+ sctp_asconf_process_param_ack(stcb, ap, 1);
+ break;
+ default:
+ break;
+ } /* switch */
+
+ /* update remaining ASCONF-ACK message length to process */
+ if (ack_length > SCTP_SIZE32(param_length)) {
+ ack_length -= SCTP_SIZE32(param_length);
+ } else {
+ break;
+ }
+ offset += SCTP_SIZE32(param_length);
+ } /* while */
+
+ /*
+ * if there are any "sent" params still on the queue, these are
+ * implicitly "success", or "failed" (if we got an error back) ...
+ * so process these appropriately
+ *
+ * we assume that the correlation_id's are monotonically increasing
+ * beginning from 1 and that we don't have *that* many outstanding
+ * at any given time
+ */
+ if (last_error_id == 0)
+ last_error_id--; /* set to "max" value */
+ TAILQ_FOREACH_SAFE(aa, &stcb->asoc.asconf_queue, next, aa_next) {
+ if (aa->sent == 1) {
+ /*
+ * implicitly successful or failed if correlation_id
+ * < last_error_id, then success else, failure
+ */
+ if (aa->ap.aph.correlation_id < last_error_id)
+ sctp_asconf_process_param_ack(stcb, aa, 1);
+ else
+ sctp_asconf_process_param_ack(stcb, aa, 0);
+ } else {
+ /*
+ * since we always process in order (FIFO queue) if
+ * we reach one that hasn't been sent, the rest
+ * should not have been sent either. so, we're
+ * done...
+ */
+ break;
+ }
+ }
+
+ /* update the next sequence number to use */
+ asoc->asconf_seq_out_acked++;
+ /* remove the old ASCONF on our outbound queue */
+ sctp_toss_old_asconf(stcb);
+ if (!TAILQ_EMPTY(&stcb->asoc.asconf_queue)) {
+#ifdef SCTP_TIMER_BASED_ASCONF
+ /* we have more params, so restart our timer */
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, stcb->sctp_ep,
+ stcb, net);
+#else
+ /* we have more params, so send out more */
+ sctp_send_asconf(stcb, net, SCTP_ADDR_NOT_LOCKED);
+#endif
+ }
+}
+
+#ifdef INET6
+static uint32_t
+sctp_is_scopeid_in_nets(struct sctp_tcb *stcb, struct sockaddr *sa)
+{
+ struct sockaddr_in6 *sin6, *net6;
+ struct sctp_nets *net;
+
+ if (sa->sa_family != AF_INET6) {
+ /* wrong family */
+ return (0);
+ }
+ sin6 = (struct sockaddr_in6 *)sa;
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr) == 0) {
+ /* not link local address */
+ return (0);
+ }
+ /* hunt through our destination nets list for this scope_id */
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ if (((struct sockaddr *)(&net->ro._l_addr))->sa_family !=
+ AF_INET6)
+ continue;
+ net6 = (struct sockaddr_in6 *)&net->ro._l_addr;
+ if (IN6_IS_ADDR_LINKLOCAL(&net6->sin6_addr) == 0)
+ continue;
+ if (sctp_is_same_scope(sin6, net6)) {
+ /* found one */
+ return (1);
+ }
+ }
+ /* didn't find one */
+ return (0);
+}
+#endif
+
+/*
+ * address management functions
+ */
+static void
+sctp_addr_mgmt_assoc(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ struct sctp_ifa *ifa, uint16_t type, int addr_locked)
+{
+ int status;
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0 ||
+ sctp_is_feature_off(inp, SCTP_PCB_FLAGS_DO_ASCONF)) {
+ /* subset bound, no ASCONF allowed case, so ignore */
+ return;
+ }
+ /*
+ * note: we know this is not the subset bound, no ASCONF case eg.
+ * this is boundall or subset bound w/ASCONF allowed
+ */
+
+ /* first, make sure that the address is IPv4 or IPv6 and not jailed */
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &ifa->address.sin6.sin6_addr) != 0) {
+ return;
+ }
+#endif
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &ifa->address.sin.sin_addr) != 0) {
+ return;
+ }
+#endif
+ break;
+#endif
+ default:
+ return;
+ }
+#ifdef INET6
+ /* make sure we're "allowed" to add this type of addr */
+ if (ifa->address.sa.sa_family == AF_INET6) {
+ /* invalid if we're not a v6 endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0)
+ return;
+ /* is the v6 addr really valid ? */
+ if (ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) {
+ return;
+ }
+ }
+#endif
+ /* put this address on the "pending/do not use yet" list */
+ sctp_add_local_addr_restricted(stcb, ifa);
+ /*
+ * check address scope if address is out of scope, don't queue
+ * anything... note: this would leave the address on both inp and
+ * asoc lists
+ */
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = &ifa->address.sin6;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ /* we skip unspecified addresses */
+ return;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ if (stcb->asoc.scope.local_scope == 0) {
+ return;
+ }
+ /* is it the right link local scope? */
+ if (sctp_is_scopeid_in_nets(stcb, &ifa->address.sa) == 0) {
+ return;
+ }
+ }
+ if (stcb->asoc.scope.site_scope == 0 &&
+ IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr)) {
+ return;
+ }
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ struct sockaddr_in *sin;
+
+ /* invalid if we are a v6 only endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp))
+ return;
+
+ sin = &ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ /* we skip unspecified addresses */
+ return;
+ }
+ if (stcb->asoc.scope.ipv4_local_scope == 0 &&
+ IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) {
+ return;
+ }
+ break;
+ }
+#endif
+ default:
+ /* else, not AF_INET or AF_INET6, so skip */
+ return;
+ }
+
+ /* queue an asconf for this address add/delete */
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_DO_ASCONF)) {
+ /* does the peer do asconf? */
+ if (stcb->asoc.asconf_supported) {
+ /* queue an asconf for this addr */
+ status = sctp_asconf_queue_add(stcb, ifa, type);
+
+ /*
+ * if queued ok, and in the open state, send out the
+ * ASCONF. If in the non-open state, these will be
+ * sent when the state goes open.
+ */
+ if (status == 0 &&
+ ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED))) {
+#ifdef SCTP_TIMER_BASED_ASCONF
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF, inp,
+ stcb, stcb->asoc.primary_destination);
+#else
+ sctp_send_asconf(stcb, NULL, addr_locked);
+#endif
+ }
+ }
+ }
+}
+
+int
+sctp_asconf_iterator_ep(struct sctp_inpcb *inp, void *ptr, uint32_t val SCTP_UNUSED)
+{
+ struct sctp_asconf_iterator *asc;
+ struct sctp_ifa *ifa;
+ struct sctp_laddr *l;
+ int cnt_invalid = 0;
+
+ asc = (struct sctp_asconf_iterator *)ptr;
+ LIST_FOREACH(l, &asc->list_of_work, sctp_nxt_addr) {
+ ifa = l->ifa;
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ /* invalid if we're not a v6 endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) {
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return (1);
+ }
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ /* invalid if we are a v6 only endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp)) {
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return (1);
+ }
+ break;
+ }
+#endif
+ default:
+ /* invalid address family */
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return (1);
+ }
+ }
+ return (0);
+}
+
+static int
+sctp_asconf_iterator_ep_end(struct sctp_inpcb *inp, void *ptr, uint32_t val SCTP_UNUSED)
+{
+ struct sctp_ifa *ifa;
+ struct sctp_asconf_iterator *asc;
+ struct sctp_laddr *laddr, *nladdr, *l;
+
+ /* Only for specific case not bound all */
+ asc = (struct sctp_asconf_iterator *)ptr;
+ LIST_FOREACH(l, &asc->list_of_work, sctp_nxt_addr) {
+ ifa = l->ifa;
+ if (l->action == SCTP_ADD_IP_ADDRESS) {
+ LIST_FOREACH(laddr, &inp->sctp_addr_list,
+ sctp_nxt_addr) {
+ if (laddr->ifa == ifa) {
+ laddr->action = 0;
+ break;
+ }
+ }
+ } else if (l->action == SCTP_DEL_IP_ADDRESS) {
+ LIST_FOREACH_SAFE(laddr, &inp->sctp_addr_list, sctp_nxt_addr, nladdr) {
+ /* remove only after all guys are done */
+ if (laddr->ifa == ifa) {
+ sctp_del_local_addr_ep(inp, ifa);
+ }
+ }
+ }
+ }
+ return (0);
+}
+
+void
+sctp_asconf_iterator_stcb(struct sctp_inpcb *inp, struct sctp_tcb *stcb,
+ void *ptr, uint32_t val SCTP_UNUSED)
+{
+ struct sctp_asconf_iterator *asc;
+ struct sctp_ifa *ifa;
+ struct sctp_laddr *l;
+ int cnt_invalid = 0;
+ int type, status;
+ int num_queued = 0;
+
+ asc = (struct sctp_asconf_iterator *)ptr;
+ LIST_FOREACH(l, &asc->list_of_work, sctp_nxt_addr) {
+ ifa = l->ifa;
+ type = l->action;
+
+ /* address's vrf_id must be the vrf_id of the assoc */
+ if (ifa->vrf_id != stcb->asoc.vrf_id) {
+ continue;
+ }
+
+ /* Same checks again for assoc */
+ switch (ifa->address.sa.sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ {
+ /* invalid if we're not a v6 endpoint */
+ struct sockaddr_in6 *sin6;
+
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) == 0) {
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return;
+ else
+ continue;
+ }
+ sin6 = &ifa->address.sin6;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ /* we skip unspecified addresses */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(inp->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) {
+ if (stcb->asoc.scope.local_scope == 0) {
+ continue;
+ }
+ /* is it the right link local scope? */
+ if (sctp_is_scopeid_in_nets(stcb, &ifa->address.sa) == 0) {
+ continue;
+ }
+ }
+ break;
+ }
+#endif
+#ifdef INET
+ case AF_INET:
+ {
+ /* invalid if we are a v6 only endpoint */
+ struct sockaddr_in *sin;
+
+ /* invalid if we are a v6 only endpoint */
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp))
+ continue;
+
+ sin = &ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ /* we skip unspecified addresses */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(inp->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if (stcb->asoc.scope.ipv4_local_scope == 0 &&
+ IN4_ISPRIVATE_ADDRESS(&sin->sin_addr)) {
+ continue;
+ }
+ if ((inp->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) &&
+ SCTP_IPV6_V6ONLY(inp)) {
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return;
+ else
+ continue;
+ }
+ break;
+ }
+#endif
+ default:
+ /* invalid address family */
+ cnt_invalid++;
+ if (asc->cnt == cnt_invalid)
+ return;
+ else
+ continue;
+ }
+
+ if (type == SCTP_ADD_IP_ADDRESS) {
+ /* prevent this address from being used as a source */
+ sctp_add_local_addr_restricted(stcb, ifa);
+ } else if (type == SCTP_DEL_IP_ADDRESS) {
+ struct sctp_nets *net;
+ TAILQ_FOREACH(net, &stcb->asoc.nets, sctp_next) {
+ /* delete this address if cached */
+ if (net->ro._s_addr == ifa) {
+ sctp_free_ifa(net->ro._s_addr);
+ net->ro._s_addr = NULL;
+ net->src_addr_selected = 0;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ RO_NHFREE(&net->ro);
+#else
+ if (net->ro.ro_rt) {
+ RTFREE(net->ro.ro_rt);
+ net->ro.ro_rt = NULL;
+ }
+#endif
+ /*
+ * Now we deleted our src address,
+ * should we not also now reset the
+ * cwnd/rto to start as if its a new
+ * address?
+ */
+ stcb->asoc.cc_functions.sctp_set_initial_cc_param(stcb, net);
+ net->RTO = 0;
+ }
+ }
+ } else if (type == SCTP_SET_PRIM_ADDR) {
+ if ((stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) == 0) {
+ /* must validate the ifa is in the ep */
+ if (sctp_is_addr_in_ep(stcb->sctp_ep, ifa) == 0) {
+ continue;
+ }
+ } else {
+ /* Need to check scopes for this guy */
+ if (sctp_is_address_in_scope(ifa, &stcb->asoc.scope, 0) == 0) {
+ continue;
+ }
+ }
+ }
+ /* queue an asconf for this address add/delete */
+ if (sctp_is_feature_on(inp, SCTP_PCB_FLAGS_DO_ASCONF) &&
+ stcb->asoc.asconf_supported == 1) {
+ /* queue an asconf for this addr */
+ status = sctp_asconf_queue_add(stcb, ifa, type);
+ /*
+ * if queued ok, and in the open state, update the
+ * count of queued params. If in the non-open state,
+ * these get sent when the assoc goes open.
+ */
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+ if (status >= 0) {
+ num_queued++;
+ }
+ }
+ }
+ }
+ /*
+ * If we have queued params in the open state, send out an ASCONF.
+ */
+ if (num_queued > 0) {
+ sctp_send_asconf(stcb, NULL, SCTP_ADDR_NOT_LOCKED);
+ }
+}
+
+void
+sctp_asconf_iterator_end(void *ptr, uint32_t val SCTP_UNUSED)
+{
+ struct sctp_asconf_iterator *asc;
+ struct sctp_ifa *ifa;
+ struct sctp_laddr *l, *nl;
+
+ asc = (struct sctp_asconf_iterator *)ptr;
+ LIST_FOREACH_SAFE(l, &asc->list_of_work, sctp_nxt_addr, nl) {
+ ifa = l->ifa;
+ if (l->action == SCTP_ADD_IP_ADDRESS) {
+ /* Clear the defer use flag */
+ ifa->localifa_flags &= ~SCTP_ADDR_DEFER_USE;
+ }
+ sctp_free_ifa(ifa);
+ SCTP_ZONE_FREE(SCTP_BASE_INFO(ipi_zone_laddr), l);
+ SCTP_DECR_LADDR_COUNT();
+ }
+ SCTP_FREE(asc, SCTP_M_ASC_IT);
+}
+
+/*
+ * sa is the sockaddr to ask the peer to set primary to.
+ * returns: 0 = completed, -1 = error
+ */
+int32_t
+sctp_set_primary_ip_address_sa(struct sctp_tcb *stcb, struct sockaddr *sa)
+{
+ uint32_t vrf_id;
+ struct sctp_ifa *ifa;
+
+ /* find the ifa for the desired set primary */
+ vrf_id = stcb->asoc.vrf_id;
+ ifa = sctp_find_ifa_by_addr(sa, vrf_id, SCTP_ADDR_NOT_LOCKED);
+ if (ifa == NULL) {
+ /* Invalid address */
+ return (-1);
+ }
+
+ /* queue an ASCONF:SET_PRIM_ADDR to be sent */
+ if (!sctp_asconf_queue_add(stcb, ifa, SCTP_SET_PRIM_ADDR)) {
+ /* set primary queuing succeeded */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "set_primary_ip_address_sa: queued on tcb=%p, ",
+ (void *)stcb);
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ if ((SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) ||
+ (SCTP_GET_STATE(stcb) == SCTP_STATE_SHUTDOWN_RECEIVED)) {
+#ifdef SCTP_TIMER_BASED_ASCONF
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF,
+ stcb->sctp_ep, stcb,
+ stcb->asoc.primary_destination);
+#else
+ sctp_send_asconf(stcb, NULL, SCTP_ADDR_NOT_LOCKED);
+#endif
+ }
+ } else {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "set_primary_ip_address_sa: failed to add to queue on tcb=%p, ",
+ (void *)stcb);
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF1, sa);
+ return (-1);
+ }
+ return (0);
+}
+
+int
+sctp_is_addr_pending(struct sctp_tcb *stcb, struct sctp_ifa *sctp_ifa)
+{
+ struct sctp_tmit_chunk *chk, *nchk;
+ unsigned int offset, asconf_limit;
+ struct sctp_asconf_chunk *acp;
+ struct sctp_asconf_paramhdr *aph;
+ uint8_t aparam_buf[SCTP_PARAM_BUFFER_SIZE];
+ struct sctp_paramhdr *ph;
+ int add_cnt, del_cnt;
+ uint16_t last_param_type;
+
+ add_cnt = del_cnt = 0;
+ last_param_type = 0;
+ TAILQ_FOREACH_SAFE(chk, &stcb->asoc.asconf_send_queue, sctp_next, nchk) {
+ if (chk->data == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: No mbuf data?\n");
+ continue;
+ }
+ offset = 0;
+ acp = mtod(chk->data, struct sctp_asconf_chunk *);
+ offset += sizeof(struct sctp_asconf_chunk);
+ asconf_limit = ntohs(acp->ch.chunk_length);
+ ph = (struct sctp_paramhdr *)sctp_m_getptr(chk->data, offset, sizeof(struct sctp_paramhdr), aparam_buf);
+ if (ph == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: couldn't get lookup addr!\n");
+ continue;
+ }
+ offset += ntohs(ph->param_length);
+
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(chk->data, offset, sizeof(struct sctp_asconf_paramhdr), aparam_buf);
+ if (aph == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: Empty ASCONF will be sent?\n");
+ continue;
+ }
+ while (aph != NULL) {
+ unsigned int param_length, param_type;
+
+ param_type = ntohs(aph->ph.param_type);
+ param_length = ntohs(aph->ph.param_length);
+ if (offset + param_length > asconf_limit) {
+ /* parameter goes beyond end of chunk! */
+ break;
+ }
+ if (param_length > sizeof(aparam_buf)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: param length (%u) larger than buffer size!\n", param_length);
+ break;
+ }
+ if (param_length <= sizeof(struct sctp_paramhdr)) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: param length(%u) too short\n", param_length);
+ break;
+ }
+
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(chk->data, offset, param_length, aparam_buf);
+ if (aph == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "is_addr_pending: couldn't get entire param\n");
+ break;
+ }
+
+ ph = (struct sctp_paramhdr *)(aph + 1);
+ if (sctp_addr_match(ph, &sctp_ifa->address.sa) != 0) {
+ switch (param_type) {
+ case SCTP_ADD_IP_ADDRESS:
+ add_cnt++;
+ break;
+ case SCTP_DEL_IP_ADDRESS:
+ del_cnt++;
+ break;
+ default:
+ break;
+ }
+ last_param_type = param_type;
+ }
+
+ offset += SCTP_SIZE32(param_length);
+ if (offset >= asconf_limit) {
+ /* no more data in the mbuf chain */
+ break;
+ }
+ /* get pointer to next asconf param */
+ aph = (struct sctp_asconf_paramhdr *)sctp_m_getptr(chk->data, offset, sizeof(struct sctp_asconf_paramhdr), aparam_buf);
+ }
+ }
+
+ /* we want to find the sequences which consist of ADD -> DEL -> ADD or DEL -> ADD */
+ if (add_cnt > del_cnt ||
+ (add_cnt == del_cnt && last_param_type == SCTP_ADD_IP_ADDRESS)) {
+ return (1);
+ }
+ return (0);
+}
+
+static struct sockaddr *
+sctp_find_valid_localaddr(struct sctp_tcb *stcb, int addr_locked)
+{
+ struct sctp_vrf *vrf = NULL;
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa;
+
+ if (addr_locked == SCTP_ADDR_NOT_LOCKED)
+ SCTP_IPI_ADDR_RLOCK();
+ vrf = sctp_find_vrf(stcb->asoc.vrf_id);
+ if (vrf == NULL) {
+ if (addr_locked == SCTP_ADDR_NOT_LOCKED)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (NULL);
+ }
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ if (stcb->asoc.scope.loopback_scope == 0 &&
+ SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ /* Skip if loopback_scope not set */
+ continue;
+ }
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ switch (sctp_ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ if (stcb->asoc.scope.ipv4_addr_legal) {
+ struct sockaddr_in *sin;
+
+ sin = &sctp_ifa->address.sin;
+ if (sin->sin_addr.s_addr == 0) {
+ /* skip unspecified addresses */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if (stcb->asoc.scope.ipv4_local_scope == 0 &&
+ IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))
+ continue;
+
+ if (sctp_is_addr_restricted(stcb, sctp_ifa) &&
+ (!sctp_is_addr_pending(stcb, sctp_ifa)))
+ continue;
+ /* found a valid local v4 address to use */
+ if (addr_locked == SCTP_ADDR_NOT_LOCKED)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (&sctp_ifa->address.sa);
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ if (stcb->asoc.scope.ipv6_addr_legal) {
+ struct sockaddr_in6 *sin6;
+
+ if (sctp_ifa->localifa_flags & SCTP_ADDR_IFA_UNUSEABLE) {
+ continue;
+ }
+
+ sin6 = &sctp_ifa->address.sin6;
+ if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) {
+ /* we skip unspecified addresses */
+ continue;
+ }
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if (stcb->asoc.scope.local_scope == 0 &&
+ IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+ continue;
+ if (stcb->asoc.scope.site_scope == 0 &&
+ IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))
+ continue;
+
+ if (sctp_is_addr_restricted(stcb, sctp_ifa) &&
+ (!sctp_is_addr_pending(stcb, sctp_ifa)))
+ continue;
+ /* found a valid local v6 address to use */
+ if (addr_locked == SCTP_ADDR_NOT_LOCKED)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (&sctp_ifa->address.sa);
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ }
+ }
+ /* no valid addresses found */
+ if (addr_locked == SCTP_ADDR_NOT_LOCKED)
+ SCTP_IPI_ADDR_RUNLOCK();
+ return (NULL);
+}
+
+static struct sockaddr *
+sctp_find_valid_localaddr_ep(struct sctp_tcb *stcb)
+{
+ struct sctp_laddr *laddr;
+
+ LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ continue;
+ }
+ /* is the address restricted ? */
+ if (sctp_is_addr_restricted(stcb, laddr->ifa) &&
+ (!sctp_is_addr_pending(stcb, laddr->ifa)))
+ continue;
+
+ /* found a valid local address to use */
+ return (&laddr->ifa->address.sa);
+ }
+ /* no valid addresses found */
+ return (NULL);
+}
+
+/*
+ * builds an ASCONF chunk from queued ASCONF params.
+ * returns NULL on error (no mbuf, no ASCONF params queued, etc).
+ */
+struct mbuf *
+sctp_compose_asconf(struct sctp_tcb *stcb, int *retlen, int addr_locked)
+{
+ struct mbuf *m_asconf, *m_asconf_chk;
+ struct sctp_asconf_addr *aa;
+ struct sctp_asconf_chunk *acp;
+ struct sctp_asconf_paramhdr *aph;
+ struct sctp_asconf_addr_param *aap;
+ uint32_t p_length, overhead;
+ uint32_t correlation_id = 1; /* 0 is reserved... */
+ caddr_t ptr, lookup_ptr;
+ uint8_t lookup_used = 0;
+
+ /* are there any asconf params to send? */
+ TAILQ_FOREACH(aa, &stcb->asoc.asconf_queue, next) {
+ if (aa->sent == 0)
+ break;
+ }
+ if (aa == NULL)
+ return (NULL);
+
+ /* Consider IP header and SCTP common header. */
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUND_V6) {
+ overhead = SCTP_MIN_OVERHEAD;
+ } else {
+ overhead = SCTP_MIN_V4_OVERHEAD;
+ }
+ /* Consider ASONF chunk. */
+ overhead += sizeof(struct sctp_asconf_chunk);
+ /* Consider AUTH chunk. */
+ overhead += sctp_get_auth_chunk_len(stcb->asoc.peer_hmac_id);
+ if (stcb->asoc.smallest_mtu <= overhead) {
+ /* MTU too small. */
+ return (NULL);
+ }
+ /*
+ * get a chunk header mbuf and a cluster for the asconf params since
+ * it's simpler to fill in the asconf chunk header lookup address on
+ * the fly
+ */
+ m_asconf_chk = sctp_get_mbuf_for_msg(sizeof(struct sctp_asconf_chunk), 0, M_NOWAIT, 1, MT_DATA);
+ if (m_asconf_chk == NULL) {
+ /* no mbuf's */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_compose_asconf: couldn't get chunk mbuf!\n");
+ return (NULL);
+ }
+ m_asconf = sctp_get_mbuf_for_msg(MCLBYTES, 0, M_NOWAIT, 1, MT_DATA);
+ if (m_asconf == NULL) {
+ /* no mbuf's */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_compose_asconf: couldn't get mbuf!\n");
+ sctp_m_freem(m_asconf_chk);
+ return (NULL);
+ }
+ SCTP_BUF_LEN(m_asconf_chk) = sizeof(struct sctp_asconf_chunk);
+ SCTP_BUF_LEN(m_asconf) = 0;
+ acp = mtod(m_asconf_chk, struct sctp_asconf_chunk *);
+ memset(acp, 0, sizeof(struct sctp_asconf_chunk));
+ /* save pointers to lookup address and asconf params */
+ lookup_ptr = (caddr_t)(acp + 1); /* after the header */
+ ptr = mtod(m_asconf, caddr_t); /* beginning of cluster */
+
+ /* fill in chunk header info */
+ acp->ch.chunk_type = SCTP_ASCONF;
+ acp->ch.chunk_flags = 0;
+ acp->serial_number = htonl(stcb->asoc.asconf_seq_out);
+ stcb->asoc.asconf_seq_out++;
+
+ /* add parameters... up to smallest MTU allowed */
+ TAILQ_FOREACH(aa, &stcb->asoc.asconf_queue, next) {
+ if (aa->sent)
+ continue;
+ /* get the parameter length */
+ p_length = SCTP_SIZE32(aa->ap.aph.ph.param_length);
+ /* will it fit in current chunk? */
+ if ((SCTP_BUF_LEN(m_asconf) + p_length > stcb->asoc.smallest_mtu - overhead) ||
+ (SCTP_BUF_LEN(m_asconf) + p_length > MCLBYTES)) {
+ /* won't fit, so we're done with this chunk */
+ break;
+ }
+ /* assign (and store) a correlation id */
+ aa->ap.aph.correlation_id = correlation_id++;
+
+ /*
+ * fill in address if we're doing a delete this is a simple
+ * way for us to fill in the correlation address, which
+ * should only be used by the peer if we're deleting our
+ * source address and adding a new address (e.g. renumbering
+ * case)
+ */
+ if (lookup_used == 0 &&
+ (aa->special_del == 0) &&
+ aa->ap.aph.ph.param_type == SCTP_DEL_IP_ADDRESS) {
+ struct sctp_ipv6addr_param *lookup;
+ uint16_t p_size, addr_size;
+
+ lookup = (struct sctp_ipv6addr_param *)lookup_ptr;
+ lookup->ph.param_type =
+ htons(aa->ap.addrp.ph.param_type);
+ if (aa->ap.addrp.ph.param_type == SCTP_IPV6_ADDRESS) {
+ /* copy IPv6 address */
+ p_size = sizeof(struct sctp_ipv6addr_param);
+ addr_size = sizeof(struct in6_addr);
+ } else {
+ /* copy IPv4 address */
+ p_size = sizeof(struct sctp_ipv4addr_param);
+ addr_size = sizeof(struct in_addr);
+ }
+ lookup->ph.param_length = htons(SCTP_SIZE32(p_size));
+ memcpy(lookup->addr, &aa->ap.addrp.addr, addr_size);
+ SCTP_BUF_LEN(m_asconf_chk) += SCTP_SIZE32(p_size);
+ lookup_used = 1;
+ }
+ /* copy into current space */
+ memcpy(ptr, &aa->ap, p_length);
+
+ /* network elements and update lengths */
+ aph = (struct sctp_asconf_paramhdr *)ptr;
+ aap = (struct sctp_asconf_addr_param *)ptr;
+ /* correlation_id is transparent to peer, no htonl needed */
+ aph->ph.param_type = htons(aph->ph.param_type);
+ aph->ph.param_length = htons(aph->ph.param_length);
+ aap->addrp.ph.param_type = htons(aap->addrp.ph.param_type);
+ aap->addrp.ph.param_length = htons(aap->addrp.ph.param_length);
+
+ SCTP_BUF_LEN(m_asconf) += SCTP_SIZE32(p_length);
+ ptr += SCTP_SIZE32(p_length);
+
+ /*
+ * these params are removed off the pending list upon
+ * getting an ASCONF-ACK back from the peer, just set flag
+ */
+ aa->sent = 1;
+ }
+ /* check to see if the lookup addr has been populated yet */
+ if (lookup_used == 0) {
+ /* NOTE: if the address param is optional, can skip this... */
+ /* add any valid (existing) address... */
+ struct sctp_ipv6addr_param *lookup;
+ uint16_t p_size, addr_size;
+ struct sockaddr *found_addr;
+ caddr_t addr_ptr;
+
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL)
+ found_addr = sctp_find_valid_localaddr(stcb,
+ addr_locked);
+ else
+ found_addr = sctp_find_valid_localaddr_ep(stcb);
+
+ lookup = (struct sctp_ipv6addr_param *)lookup_ptr;
+ if (found_addr != NULL) {
+ switch (found_addr->sa_family) {
+#ifdef INET6
+ case AF_INET6:
+ /* copy IPv6 address */
+ lookup->ph.param_type =
+ htons(SCTP_IPV6_ADDRESS);
+ p_size = sizeof(struct sctp_ipv6addr_param);
+ addr_size = sizeof(struct in6_addr);
+ addr_ptr = (caddr_t)&((struct sockaddr_in6 *)
+ found_addr)->sin6_addr;
+ break;
+#endif
+#ifdef INET
+ case AF_INET:
+ /* copy IPv4 address */
+ lookup->ph.param_type =
+ htons(SCTP_IPV4_ADDRESS);
+ p_size = sizeof(struct sctp_ipv4addr_param);
+ addr_size = sizeof(struct in_addr);
+ addr_ptr = (caddr_t)&((struct sockaddr_in *)
+ found_addr)->sin_addr;
+ break;
+#endif
+ default:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_compose_asconf: no usable lookup addr (family = %d)!\n",
+ found_addr->sa_family);
+ sctp_m_freem(m_asconf_chk);
+ sctp_m_freem(m_asconf);
+ return (NULL);
+ }
+ lookup->ph.param_length = htons(SCTP_SIZE32(p_size));
+ memcpy(lookup->addr, addr_ptr, addr_size);
+ SCTP_BUF_LEN(m_asconf_chk) += SCTP_SIZE32(p_size);
+ } else {
+ /* uh oh... don't have any address?? */
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_compose_asconf: no lookup addr!\n");
+ sctp_m_freem(m_asconf_chk);
+ sctp_m_freem(m_asconf);
+ return (NULL);
+ }
+ }
+ /* chain it all together */
+ SCTP_BUF_NEXT(m_asconf_chk) = m_asconf;
+ *retlen = SCTP_BUF_LEN(m_asconf_chk) + SCTP_BUF_LEN(m_asconf);
+ acp->ch.chunk_length = htons(*retlen);
+
+ return (m_asconf_chk);
+}
+
+/*
+ * section to handle address changes before an association is up eg. changes
+ * during INIT/INIT-ACK/COOKIE-ECHO handshake
+ */
+
+/*
+ * processes the (local) addresses in the INIT-ACK chunk
+ */
+static void
+sctp_process_initack_addresses(struct sctp_tcb *stcb, struct mbuf *m,
+ unsigned int offset, unsigned int length)
+{
+ struct sctp_paramhdr tmp_param, *ph;
+ uint16_t plen, ptype;
+ struct sctp_ifa *sctp_ifa;
+ union sctp_sockstore store;
+#ifdef INET6
+ struct sctp_ipv6addr_param addr6_store;
+#endif
+#ifdef INET
+ struct sctp_ipv4addr_param addr4_store;
+#endif
+
+ SCTPDBG(SCTP_DEBUG_ASCONF2, "processing init-ack addresses\n");
+ if (stcb == NULL) /* Un-needed check for SA */
+ return;
+
+ /* convert to upper bound */
+ length += offset;
+
+ if ((offset + sizeof(struct sctp_paramhdr)) > length) {
+ return;
+ }
+ /* go through the addresses in the init-ack */
+ ph = (struct sctp_paramhdr *)
+ sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr),
+ (uint8_t *)&tmp_param);
+ while (ph != NULL) {
+ ptype = ntohs(ph->param_type);
+ plen = ntohs(ph->param_length);
+ switch (ptype) {
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ {
+ struct sctp_ipv6addr_param *a6p;
+
+ /* get the entire IPv6 address param */
+ a6p = (struct sctp_ipv6addr_param *)
+ sctp_m_getptr(m, offset,
+ sizeof(struct sctp_ipv6addr_param),
+ (uint8_t *)&addr6_store);
+ if (plen != sizeof(struct sctp_ipv6addr_param) ||
+ a6p == NULL) {
+ return;
+ }
+ memset(&store, 0, sizeof(union sctp_sockstore));
+ store.sin6.sin6_family = AF_INET6;
+#ifdef HAVE_SIN6_LEN
+ store.sin6.sin6_len = sizeof(struct sockaddr_in6);
+#endif
+ store.sin6.sin6_port = stcb->rport;
+ memcpy(&store.sin6.sin6_addr, a6p->addr, sizeof(struct in6_addr));
+ break;
+ }
+#endif
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ {
+ struct sctp_ipv4addr_param *a4p;
+
+ /* get the entire IPv4 address param */
+ a4p = (struct sctp_ipv4addr_param *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_ipv4addr_param),
+ (uint8_t *)&addr4_store);
+ if (plen != sizeof(struct sctp_ipv4addr_param) ||
+ a4p == NULL) {
+ return;
+ }
+ memset(&store, 0, sizeof(union sctp_sockstore));
+ store.sin.sin_family = AF_INET;
+#ifdef HAVE_SIN_LEN
+ store.sin.sin_len = sizeof(struct sockaddr_in);
+#endif
+ store.sin.sin_port = stcb->rport;
+ store.sin.sin_addr.s_addr = a4p->addr;
+ break;
+ }
+#endif
+ default:
+ goto next_addr;
+ }
+
+ /* see if this address really (still) exists */
+ sctp_ifa = sctp_find_ifa_by_addr(&store.sa, stcb->asoc.vrf_id,
+ SCTP_ADDR_NOT_LOCKED);
+ if (sctp_ifa == NULL) {
+ /* address doesn't exist anymore */
+ int status;
+
+ /* are ASCONFs allowed ? */
+ if ((sctp_is_feature_on(stcb->sctp_ep,
+ SCTP_PCB_FLAGS_DO_ASCONF)) &&
+ stcb->asoc.asconf_supported) {
+ /* queue an ASCONF DEL_IP_ADDRESS */
+ status = sctp_asconf_queue_sa_delete(stcb, &store.sa);
+ /*
+ * if queued ok, and in correct state, send
+ * out the ASCONF.
+ */
+ if (status == 0 &&
+ SCTP_GET_STATE(stcb) == SCTP_STATE_OPEN) {
+#ifdef SCTP_TIMER_BASED_ASCONF
+ sctp_timer_start(SCTP_TIMER_TYPE_ASCONF,
+ stcb->sctp_ep, stcb,
+ stcb->asoc.primary_destination);
+#else
+ sctp_send_asconf(stcb, NULL, SCTP_ADDR_NOT_LOCKED);
+#endif
+ }
+ }
+ }
+
+next_addr:
+ /*
+ * Sanity check: Make sure the length isn't 0, otherwise
+ * we'll be stuck in this loop for a long time...
+ */
+ if (SCTP_SIZE32(plen) == 0) {
+ SCTP_PRINTF("process_initack_addrs: bad len (%d) type=%xh\n",
+ plen, ptype);
+ return;
+ }
+ /* get next parameter */
+ offset += SCTP_SIZE32(plen);
+ if ((offset + sizeof(struct sctp_paramhdr)) > length)
+ return;
+ ph = (struct sctp_paramhdr *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_paramhdr), (uint8_t *)&tmp_param);
+ } /* while */
+}
+
+/* FIX ME: need to verify return result for v6 address type if v6 disabled */
+/*
+ * checks to see if a specific address is in the initack address list returns
+ * 1 if found, 0 if not
+ */
+static uint32_t
+sctp_addr_in_initack(struct mbuf *m, uint32_t offset, uint32_t length, struct sockaddr *sa)
+{
+ struct sctp_paramhdr tmp_param, *ph;
+ uint16_t plen, ptype;
+#ifdef INET
+ struct sockaddr_in *sin;
+ struct sctp_ipv4addr_param *a4p;
+ struct sctp_ipv6addr_param addr4_store;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+ struct sctp_ipv6addr_param *a6p;
+ struct sctp_ipv6addr_param addr6_store;
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ struct sockaddr_in6 sin6_tmp;
+#endif
+#endif
+
+ switch (sa->sa_family) {
+#ifdef INET
+ case AF_INET:
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ break;
+#endif
+ default:
+ return (0);
+ }
+
+ SCTPDBG(SCTP_DEBUG_ASCONF2, "find_initack_addr: starting search for ");
+ SCTPDBG_ADDR(SCTP_DEBUG_ASCONF2, sa);
+ /* convert to upper bound */
+ length += offset;
+
+ if ((offset + sizeof(struct sctp_paramhdr)) > length) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "find_initack_addr: invalid offset?\n");
+ return (0);
+ }
+ /* go through the addresses in the init-ack */
+ ph = (struct sctp_paramhdr *)sctp_m_getptr(m, offset,
+ sizeof(struct sctp_paramhdr), (uint8_t *) & tmp_param);
+ while (ph != NULL) {
+ ptype = ntohs(ph->param_type);
+ plen = ntohs(ph->param_length);
+ switch (ptype) {
+#ifdef INET6
+ case SCTP_IPV6_ADDRESS:
+ if (sa->sa_family == AF_INET6) {
+ /* get the entire IPv6 address param */
+ if (plen != sizeof(struct sctp_ipv6addr_param)) {
+ break;
+ }
+ /* get the entire IPv6 address param */
+ a6p = (struct sctp_ipv6addr_param *)
+ sctp_m_getptr(m, offset,
+ sizeof(struct sctp_ipv6addr_param),
+ (uint8_t *)&addr6_store);
+ if (a6p == NULL) {
+ return (0);
+ }
+ sin6 = (struct sockaddr_in6 *)sa;
+#ifdef SCTP_EMBEDDED_V6_SCOPE
+ if (IN6_IS_SCOPE_LINKLOCAL(&sin6->sin6_addr)) {
+ /* create a copy and clear scope */
+ memcpy(&sin6_tmp, sin6,
+ sizeof(struct sockaddr_in6));
+ sin6 = &sin6_tmp;
+ in6_clearscope(&sin6->sin6_addr);
+ }
+#endif /* SCTP_EMBEDDED_V6_SCOPE */
+ if (memcmp(&sin6->sin6_addr, a6p->addr,
+ sizeof(struct in6_addr)) == 0) {
+ /* found it */
+ return (1);
+ }
+ }
+ break;
+#endif /* INET6 */
+#ifdef INET
+ case SCTP_IPV4_ADDRESS:
+ if (sa->sa_family == AF_INET) {
+ if (plen != sizeof(struct sctp_ipv4addr_param)) {
+ break;
+ }
+ /* get the entire IPv4 address param */
+ a4p = (struct sctp_ipv4addr_param *)
+ sctp_m_getptr(m, offset,
+ sizeof(struct sctp_ipv4addr_param),
+ (uint8_t *)&addr4_store);
+ if (a4p == NULL) {
+ return (0);
+ }
+ sin = (struct sockaddr_in *)sa;
+ if (sin->sin_addr.s_addr == a4p->addr) {
+ /* found it */
+ return (1);
+ }
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ /* get next parameter */
+ offset += SCTP_SIZE32(plen);
+ if (offset + sizeof(struct sctp_paramhdr) > length) {
+ return (0);
+ }
+ ph = (struct sctp_paramhdr *)
+ sctp_m_getptr(m, offset, sizeof(struct sctp_paramhdr),
+ (uint8_t *) & tmp_param);
+ } /* while */
+ /* not found! */
+ return (0);
+}
+
+/*
+ * makes sure that the current endpoint local addr list is consistent with
+ * the new association (eg. subset bound, asconf allowed) adds addresses as
+ * necessary
+ */
+static void
+sctp_check_address_list_ep(struct sctp_tcb *stcb, struct mbuf *m, int offset,
+ int length, struct sockaddr *init_addr)
+{
+ struct sctp_laddr *laddr;
+
+ /* go through the endpoint list */
+ LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list, sctp_nxt_addr) {
+ /* be paranoid and validate the laddr */
+ if (laddr->ifa == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "check_addr_list_ep: laddr->ifa is NULL");
+ continue;
+ }
+ /* do i have it implicitly? */
+ if (sctp_cmpaddr(&laddr->ifa->address.sa, init_addr)) {
+ continue;
+ }
+ /* check to see if in the init-ack */
+ if (!sctp_addr_in_initack(m, offset, length, &laddr->ifa->address.sa)) {
+ /* try to add it */
+ sctp_addr_mgmt_assoc(stcb->sctp_ep, stcb, laddr->ifa,
+ SCTP_ADD_IP_ADDRESS, SCTP_ADDR_NOT_LOCKED);
+ }
+ }
+}
+
+/*
+ * makes sure that the current kernel address list is consistent with the new
+ * association (with all addrs bound) adds addresses as necessary
+ */
+static void
+sctp_check_address_list_all(struct sctp_tcb *stcb, struct mbuf *m, int offset,
+ int length, struct sockaddr *init_addr,
+ uint16_t local_scope, uint16_t site_scope,
+ uint16_t ipv4_scope, uint16_t loopback_scope)
+{
+ struct sctp_vrf *vrf = NULL;
+ struct sctp_ifn *sctp_ifn;
+ struct sctp_ifa *sctp_ifa;
+ uint32_t vrf_id;
+#ifdef INET
+ struct sockaddr_in *sin;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *sin6;
+#endif
+
+ if (stcb) {
+ vrf_id = stcb->asoc.vrf_id;
+ } else {
+ return;
+ }
+ SCTP_IPI_ADDR_RLOCK();
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ SCTP_IPI_ADDR_RUNLOCK();
+ return;
+ }
+ /* go through all our known interfaces */
+ LIST_FOREACH(sctp_ifn, &vrf->ifnlist, next_ifn) {
+ if (loopback_scope == 0 && SCTP_IFN_IS_IFT_LOOP(sctp_ifn)) {
+ /* skip loopback interface */
+ continue;
+ }
+ /* go through each interface address */
+ LIST_FOREACH(sctp_ifa, &sctp_ifn->ifalist, next_ifa) {
+ /* do i have it implicitly? */
+ if (sctp_cmpaddr(&sctp_ifa->address.sa, init_addr)) {
+ continue;
+ }
+ switch (sctp_ifa->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ sin = &sctp_ifa->address.sin;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if ((ipv4_scope == 0) &&
+ (IN4_ISPRIVATE_ADDRESS(&sin->sin_addr))) {
+ /* private address not in scope */
+ continue;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ sin6 = &sctp_ifa->address.sin6;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &sin6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if ((local_scope == 0) &&
+ (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))) {
+ continue;
+ }
+ if ((site_scope == 0) &&
+ (IN6_IS_ADDR_SITELOCAL(&sin6->sin6_addr))) {
+ continue;
+ }
+ break;
+#endif
+ default:
+ break;
+ }
+ /* check to see if in the init-ack */
+ if (!sctp_addr_in_initack(m, offset, length, &sctp_ifa->address.sa)) {
+ /* try to add it */
+ sctp_addr_mgmt_assoc(stcb->sctp_ep, stcb,
+ sctp_ifa, SCTP_ADD_IP_ADDRESS,
+ SCTP_ADDR_LOCKED);
+ }
+ } /* end foreach ifa */
+ } /* end foreach ifn */
+ SCTP_IPI_ADDR_RUNLOCK();
+}
+
+/*
+ * validates an init-ack chunk (from a cookie-echo) with current addresses
+ * adds addresses from the init-ack into our local address list, if needed
+ * queues asconf adds/deletes addresses as needed and makes appropriate list
+ * changes for source address selection m, offset: points to the start of the
+ * address list in an init-ack chunk length: total length of the address
+ * params only init_addr: address where my INIT-ACK was sent from
+ */
+void
+sctp_check_address_list(struct sctp_tcb *stcb, struct mbuf *m, int offset,
+ int length, struct sockaddr *init_addr,
+ uint16_t local_scope, uint16_t site_scope,
+ uint16_t ipv4_scope, uint16_t loopback_scope)
+{
+ /* process the local addresses in the initack */
+ sctp_process_initack_addresses(stcb, m, offset, length);
+
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ /* bound all case */
+ sctp_check_address_list_all(stcb, m, offset, length, init_addr,
+ local_scope, site_scope, ipv4_scope, loopback_scope);
+ } else {
+ /* subset bound case */
+ if (sctp_is_feature_on(stcb->sctp_ep,
+ SCTP_PCB_FLAGS_DO_ASCONF)) {
+ /* asconf's allowed */
+ sctp_check_address_list_ep(stcb, m, offset, length,
+ init_addr);
+ }
+ /* else, no asconfs allowed, so what we sent is what we get */
+ }
+}
+
+/*
+ * sctp_bindx() support
+ */
+uint32_t
+sctp_addr_mgmt_ep_sa(struct sctp_inpcb *inp, struct sockaddr *sa,
+ uint32_t type, uint32_t vrf_id)
+{
+ struct sctp_ifa *ifa;
+ struct sctp_laddr *laddr, *nladdr;
+
+#ifdef HAVE_SA_LEN
+ if (sa->sa_len == 0) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_ASCONF, EINVAL);
+ return (EINVAL);
+ }
+#endif
+ if (type == SCTP_ADD_IP_ADDRESS) {
+ /* For an add the address MUST be on the system */
+ ifa = sctp_find_ifa_by_addr(sa, vrf_id, SCTP_ADDR_NOT_LOCKED);
+ } else if (type == SCTP_DEL_IP_ADDRESS) {
+ /* For a delete we need to find it in the inp */
+ ifa = sctp_find_ifa_in_ep(inp, sa, SCTP_ADDR_NOT_LOCKED);
+ } else {
+ ifa = NULL;
+ }
+ if (ifa != NULL) {
+ if (type == SCTP_ADD_IP_ADDRESS) {
+ sctp_add_local_addr_ep(inp, ifa, type);
+ } else if (type == SCTP_DEL_IP_ADDRESS) {
+ if (inp->laddr_count < 2) {
+ /* can't delete the last local address */
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_ASCONF, EINVAL);
+ return (EINVAL);
+ }
+ LIST_FOREACH(laddr, &inp->sctp_addr_list,
+ sctp_nxt_addr) {
+ if (ifa == laddr->ifa) {
+ /* Mark in the delete */
+ laddr->action = type;
+ }
+ }
+ }
+ if (LIST_EMPTY(&inp->sctp_asoc_list)) {
+ /*
+ * There is no need to start the iterator if
+ * the inp has no associations.
+ */
+ if (type == SCTP_DEL_IP_ADDRESS) {
+ LIST_FOREACH_SAFE(laddr, &inp->sctp_addr_list, sctp_nxt_addr, nladdr) {
+ if (laddr->ifa == ifa) {
+ sctp_del_local_addr_ep(inp, ifa);
+ }
+ }
+ }
+ } else {
+ struct sctp_asconf_iterator *asc;
+ struct sctp_laddr *wi;
+ int ret;
+
+ SCTP_MALLOC(asc, struct sctp_asconf_iterator *,
+ sizeof(struct sctp_asconf_iterator),
+ SCTP_M_ASC_IT);
+ if (asc == NULL) {
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_ASCONF, ENOMEM);
+ return (ENOMEM);
+ }
+ wi = SCTP_ZONE_GET(SCTP_BASE_INFO(ipi_zone_laddr), struct sctp_laddr);
+ if (wi == NULL) {
+ SCTP_FREE(asc, SCTP_M_ASC_IT);
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_ASCONF, ENOMEM);
+ return (ENOMEM);
+ }
+ LIST_INIT(&asc->list_of_work);
+ asc->cnt = 1;
+ SCTP_INCR_LADDR_COUNT();
+ wi->ifa = ifa;
+ wi->action = type;
+ atomic_add_int(&ifa->refcount, 1);
+ LIST_INSERT_HEAD(&asc->list_of_work, wi, sctp_nxt_addr);
+ ret = sctp_initiate_iterator(sctp_asconf_iterator_ep,
+ sctp_asconf_iterator_stcb,
+ sctp_asconf_iterator_ep_end,
+ SCTP_PCB_ANY_FLAGS,
+ SCTP_PCB_ANY_FEATURES,
+ SCTP_ASOC_ANY_STATE,
+ (void *)asc, 0,
+ sctp_asconf_iterator_end, inp, 0);
+ if (ret) {
+ SCTP_PRINTF("Failed to initiate iterator for addr_mgmt_ep_sa\n");
+ SCTP_LTRACE_ERR_RET(inp, NULL, NULL, SCTP_FROM_SCTP_ASCONF, EFAULT);
+ sctp_asconf_iterator_end(asc, 0);
+ return (EFAULT);
+ }
+ }
+ return (0);
+ } else {
+ /* invalid address! */
+ SCTP_LTRACE_ERR_RET(NULL, NULL, NULL, SCTP_FROM_SCTP_ASCONF, EADDRNOTAVAIL);
+ return (EADDRNOTAVAIL);
+ }
+}
+
+void
+sctp_asconf_send_nat_state_update(struct sctp_tcb *stcb, struct sctp_nets *net)
+{
+ struct sctp_asconf_addr *aa_vtag, *aa_add, *aa_del;
+ struct sctp_ifa *sctp_ifap;
+ struct sctp_asconf_tag_param *vtag;
+#ifdef INET
+ struct sockaddr_in *to;
+#endif
+#ifdef INET6
+ struct sockaddr_in6 *to6;
+#endif
+
+ if (net == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "sctp_asconf_send_nat_state_update: Missing net\n");
+ return;
+ }
+ if (stcb == NULL) {
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "sctp_asconf_send_nat_state_update: Missing stcb\n");
+ return;
+ }
+ /* Need to have in the ASCONF:
+ * - VTAG(my_vtag/peer_vtag)
+ * - ADD(wildcard)
+ * - DEL(wildcard)
+ * - ADD(Any global addresses)
+ */
+ SCTP_MALLOC(aa_vtag, struct sctp_asconf_addr *, sizeof(struct sctp_asconf_addr), SCTP_M_ASC_ADDR);
+ SCTP_MALLOC(aa_add, struct sctp_asconf_addr *, sizeof(struct sctp_asconf_addr), SCTP_M_ASC_ADDR);
+ SCTP_MALLOC(aa_del, struct sctp_asconf_addr *, sizeof(struct sctp_asconf_addr), SCTP_M_ASC_ADDR);
+
+ if ((aa_vtag == NULL) || (aa_add == NULL) || (aa_del == NULL)) {
+ /* Didn't get memory */
+ SCTPDBG(SCTP_DEBUG_ASCONF1, "sctp_asconf_send_nat_state_update: failed to get memory!\n");
+out:
+ if (aa_vtag != NULL) {
+ SCTP_FREE(aa_vtag, SCTP_M_ASC_ADDR);
+ }
+ if (aa_add != NULL) {
+ SCTP_FREE(aa_add, SCTP_M_ASC_ADDR);
+ }
+ if (aa_del != NULL) {
+ SCTP_FREE(aa_del, SCTP_M_ASC_ADDR);
+ }
+ return;
+ }
+ memset(aa_vtag, 0, sizeof(struct sctp_asconf_addr));
+ aa_vtag->special_del = 0;
+ /* Fill in ASCONF address parameter fields. */
+ /* Top level elements are "networked" during send. */
+ aa_vtag->ifa = NULL;
+ aa_vtag->sent = 0; /* clear sent flag */
+ vtag = (struct sctp_asconf_tag_param *)&aa_vtag->ap.aph;
+ vtag->aph.ph.param_type = SCTP_NAT_VTAGS;
+ vtag->aph.ph.param_length = sizeof(struct sctp_asconf_tag_param);
+ vtag->local_vtag = htonl(stcb->asoc.my_vtag);
+ vtag->remote_vtag = htonl(stcb->asoc.peer_vtag);
+
+ memset(aa_add, 0, sizeof(struct sctp_asconf_addr));
+ memset(aa_del, 0, sizeof(struct sctp_asconf_addr));
+ switch (net->ro._l_addr.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ aa_add->ap.aph.ph.param_type = SCTP_ADD_IP_ADDRESS;
+ aa_add->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addrv4_param);
+ aa_add->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS;
+ aa_add->ap.addrp.ph.param_length = sizeof (struct sctp_ipv4addr_param);
+ /* No need to fill the address, we are using 0.0.0.0 */
+ aa_del->ap.aph.ph.param_type = SCTP_DEL_IP_ADDRESS;
+ aa_del->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addrv4_param);
+ aa_del->ap.addrp.ph.param_type = SCTP_IPV4_ADDRESS;
+ aa_del->ap.addrp.ph.param_length = sizeof (struct sctp_ipv4addr_param);
+ /* No need to fill the address, we are using 0.0.0.0 */
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ aa_add->ap.aph.ph.param_type = SCTP_ADD_IP_ADDRESS;
+ aa_add->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addr_param);
+ aa_add->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS;
+ aa_add->ap.addrp.ph.param_length = sizeof (struct sctp_ipv6addr_param);
+ /* No need to fill the address, we are using ::0 */
+ aa_del->ap.aph.ph.param_type = SCTP_DEL_IP_ADDRESS;
+ aa_del->ap.aph.ph.param_length = sizeof(struct sctp_asconf_addr_param);
+ aa_del->ap.addrp.ph.param_type = SCTP_IPV6_ADDRESS;
+ aa_del->ap.addrp.ph.param_length = sizeof (struct sctp_ipv6addr_param);
+ /* No need to fill the address, we are using ::0 */
+ break;
+#endif
+ default:
+ SCTPDBG(SCTP_DEBUG_ASCONF1,
+ "sctp_asconf_send_nat_state_update: unknown address family %d\n",
+ net->ro._l_addr.sa.sa_family);
+ goto out;
+ }
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa_vtag, next);
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa_add, next);
+ TAILQ_INSERT_TAIL(&stcb->asoc.asconf_queue, aa_del, next);
+
+ /* Now we must hunt the addresses and add all global addresses */
+ if (stcb->sctp_ep->sctp_flags & SCTP_PCB_FLAGS_BOUNDALL) {
+ struct sctp_vrf *vrf = NULL;
+ struct sctp_ifn *sctp_ifnp;
+ uint32_t vrf_id;
+
+ vrf_id = stcb->sctp_ep->def_vrf_id;
+ vrf = sctp_find_vrf(vrf_id);
+ if (vrf == NULL) {
+ goto skip_rest;
+ }
+
+ SCTP_IPI_ADDR_RLOCK();
+ LIST_FOREACH(sctp_ifnp, &vrf->ifnlist, next_ifn) {
+ LIST_FOREACH(sctp_ifap, &sctp_ifnp->ifalist, next_ifa) {
+ switch (sctp_ifap->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ to = &sctp_ifap->address.sin;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip4(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &to->sin_addr) != 0) {
+ continue;
+ }
+#endif
+ if (IN4_ISPRIVATE_ADDRESS(&to->sin_addr)) {
+ continue;
+ }
+ if (IN4_ISLOOPBACK_ADDRESS(&to->sin_addr)) {
+ continue;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ to6 = &sctp_ifap->address.sin6;
+#if defined(__FreeBSD__) && !defined(__Userspace__)
+ if (prison_check_ip6(stcb->sctp_ep->ip_inp.inp.inp_cred,
+ &to6->sin6_addr) != 0) {
+ continue;
+ }
+#endif
+ if (IN6_IS_ADDR_LOOPBACK(&to6->sin6_addr)) {
+ continue;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&to6->sin6_addr)) {
+ continue;
+ }
+ break;
+#endif
+ default:
+ continue;
+ }
+ sctp_asconf_queue_mgmt(stcb, sctp_ifap, SCTP_ADD_IP_ADDRESS);
+ }
+ }
+ SCTP_IPI_ADDR_RUNLOCK();
+ } else {
+ struct sctp_laddr *laddr;
+
+ LIST_FOREACH(laddr, &stcb->sctp_ep->sctp_addr_list, sctp_nxt_addr) {
+ if (laddr->ifa == NULL) {
+ continue;
+ }
+ if (laddr->ifa->localifa_flags & SCTP_BEING_DELETED)
+ /* Address being deleted by the system, dont
+ * list.
+ */
+ continue;
+ if (laddr->action == SCTP_DEL_IP_ADDRESS) {
+ /* Address being deleted on this ep
+ * don't list.
+ */
+ continue;
+ }
+ sctp_ifap = laddr->ifa;
+ switch (sctp_ifap->address.sa.sa_family) {
+#ifdef INET
+ case AF_INET:
+ to = &sctp_ifap->address.sin;
+ if (IN4_ISPRIVATE_ADDRESS(&to->sin_addr)) {
+ continue;
+ }
+ if (IN4_ISLOOPBACK_ADDRESS(&to->sin_addr)) {
+ continue;
+ }
+ break;
+#endif
+#ifdef INET6
+ case AF_INET6:
+ to6 = &sctp_ifap->address.sin6;
+ if (IN6_IS_ADDR_LOOPBACK(&to6->sin6_addr)) {
+ continue;
+ }
+ if (IN6_IS_ADDR_LINKLOCAL(&to6->sin6_addr)) {
+ continue;
+ }
+ break;
+#endif
+ default:
+ continue;
+ }
+ sctp_asconf_queue_mgmt(stcb, sctp_ifap, SCTP_ADD_IP_ADDRESS);
+ }
+ }
+ skip_rest:
+ /* Now we must send the asconf into the queue */
+ sctp_send_asconf(stcb, net, SCTP_ADDR_NOT_LOCKED);
+}