sctp: Add ASCONF operation on the single-homed host
In this case, the SCTP association transmits an ASCONF packet
including addition of the new IP address and deletion of the old
address. This patch implements this functionality.
In this case, the ASCONF chunk is added to the beginning of the
queue, because the other chunks cannot be transmitted in this state.
Signed-off-by: Michio Honda <micchie@sfc.wide.ad.jp>
Signed-off-by: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Acked-by: Wei Yongjun <yjwei@cn.fujitsu.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
diff --git a/net/sctp/associola.c b/net/sctp/associola.c
index 4a62888..dc16b90 100644
--- a/net/sctp/associola.c
+++ b/net/sctp/associola.c
@@ -280,6 +280,8 @@
asoc->peer.asconf_capable = 0;
if (sctp_addip_noauth)
asoc->peer.asconf_capable = 1;
+ asoc->asconf_addr_del_pending = NULL;
+ asoc->src_out_of_asoc_ok = 0;
/* Create an input queue. */
sctp_inq_init(&asoc->base.inqueue);
@@ -446,6 +448,10 @@
sctp_asconf_queue_teardown(asoc);
+ /* Free pending address space being deleted */
+ if (asoc->asconf_addr_del_pending != NULL)
+ kfree(asoc->asconf_addr_del_pending);
+
/* AUTH - Free the endpoint shared keys */
sctp_auth_destroy_keys(&asoc->endpoint_shared_keys);
diff --git a/net/sctp/outqueue.c b/net/sctp/outqueue.c
index 1c88c89..edc7532 100644
--- a/net/sctp/outqueue.c
+++ b/net/sctp/outqueue.c
@@ -754,6 +754,16 @@
*/
list_for_each_entry_safe(chunk, tmp, &q->control_chunk_list, list) {
+ /* RFC 5061, 5.3
+ * F1) This means that until such time as the ASCONF
+ * containing the add is acknowledged, the sender MUST
+ * NOT use the new IP address as a source for ANY SCTP
+ * packet except on carrying an ASCONF Chunk.
+ */
+ if (asoc->src_out_of_asoc_ok &&
+ chunk->chunk_hdr->type != SCTP_CID_ASCONF)
+ continue;
+
list_del_init(&chunk->list);
/* Pick the right transport to use. */
@@ -881,6 +891,9 @@
}
}
+ if (q->asoc->src_out_of_asoc_ok)
+ goto sctp_flush_out;
+
/* Is it OK to send data chunks? */
switch (asoc->state) {
case SCTP_STATE_COOKIE_ECHOED:
diff --git a/net/sctp/protocol.c b/net/sctp/protocol.c
index 013c613..af0a6b0 100644
--- a/net/sctp/protocol.c
+++ b/net/sctp/protocol.c
@@ -503,7 +503,9 @@
sctp_v4_dst_saddr(&dst_saddr, fl4, htons(bp->port));
rcu_read_lock();
list_for_each_entry_rcu(laddr, &bp->address_list, list) {
- if (!laddr->valid || (laddr->state != SCTP_ADDR_SRC))
+ if (!laddr->valid || (laddr->state == SCTP_ADDR_DEL) ||
+ (laddr->state != SCTP_ADDR_SRC &&
+ !asoc->src_out_of_asoc_ok))
continue;
if (sctp_v4_cmp_addr(&dst_saddr, &laddr->a))
goto out_unlock;
diff --git a/net/sctp/sm_make_chunk.c b/net/sctp/sm_make_chunk.c
index 3740603..3363d37 100644
--- a/net/sctp/sm_make_chunk.c
+++ b/net/sctp/sm_make_chunk.c
@@ -2768,6 +2768,7 @@
int addr_param_len = 0;
int totallen = 0;
int i;
+ int del_pickup = 0;
/* Get total length of all the address parameters. */
addr_buf = addrs;
@@ -2780,6 +2781,13 @@
totallen += addr_param_len;
addr_buf += af->sockaddr_len;
+ if (asoc->asconf_addr_del_pending && !del_pickup) {
+ /* reuse the parameter length from the same scope one */
+ totallen += paramlen;
+ totallen += addr_param_len;
+ del_pickup = 1;
+ SCTP_DEBUG_PRINTK("mkasconf_update_ip: picked same-scope del_pending addr, totallen for all addresses is %d\n", totallen);
+ }
}
/* Create an asconf chunk with the required length. */
@@ -2802,6 +2810,17 @@
addr_buf += af->sockaddr_len;
}
+ if (flags == SCTP_PARAM_ADD_IP && del_pickup) {
+ addr = asoc->asconf_addr_del_pending;
+ af = sctp_get_af_specific(addr->v4.sin_family);
+ addr_param_len = af->to_addr_param(addr, &addr_param);
+ param.param_hdr.type = SCTP_PARAM_DEL_IP;
+ param.param_hdr.length = htons(paramlen + addr_param_len);
+ param.crr_id = i;
+
+ sctp_addto_chunk(retval, paramlen, ¶m);
+ sctp_addto_chunk(retval, addr_param_len, &addr_param);
+ }
return retval;
}
@@ -3224,6 +3243,11 @@
case SCTP_PARAM_DEL_IP:
local_bh_disable();
sctp_del_bind_addr(bp, &addr);
+ if (asoc->asconf_addr_del_pending != NULL &&
+ sctp_cmp_addr_exact(asoc->asconf_addr_del_pending, &addr)) {
+ kfree(asoc->asconf_addr_del_pending);
+ asoc->asconf_addr_del_pending = NULL;
+ }
local_bh_enable();
list_for_each_entry(transport, &asoc->peer.transport_addr_list,
transports) {
@@ -3381,6 +3405,9 @@
asconf_len -= length;
}
+ if (no_err && asoc->src_out_of_asoc_ok)
+ asoc->src_out_of_asoc_ok = 0;
+
/* Free the cached last sent asconf chunk. */
list_del_init(&asconf->transmitted_list);
sctp_chunk_free(asconf);
diff --git a/net/sctp/socket.c b/net/sctp/socket.c
index cc06198..e7e1b14 100644
--- a/net/sctp/socket.c
+++ b/net/sctp/socket.c
@@ -583,10 +583,6 @@
goto out;
}
- retval = sctp_send_asconf(asoc, chunk);
- if (retval)
- goto out;
-
/* Add the new addresses to the bind address list with
* use_as_src set to 0.
*/
@@ -599,6 +595,23 @@
SCTP_ADDR_NEW, GFP_ATOMIC);
addr_buf += af->sockaddr_len;
}
+ if (asoc->src_out_of_asoc_ok) {
+ struct sctp_transport *trans;
+
+ list_for_each_entry(trans,
+ &asoc->peer.transport_addr_list, transports) {
+ /* Clear the source and route cache */
+ dst_release(trans->dst);
+ trans->cwnd = min(4*asoc->pathmtu, max_t(__u32,
+ 2*asoc->pathmtu, 4380));
+ trans->ssthresh = asoc->peer.i.a_rwnd;
+ trans->rto = asoc->rto_initial;
+ trans->rtt = trans->srtt = trans->rttvar = 0;
+ sctp_transport_route(trans, NULL,
+ sctp_sk(asoc->base.sk));
+ }
+ }
+ retval = sctp_send_asconf(asoc, chunk);
}
out:
@@ -715,7 +728,9 @@
struct sctp_sockaddr_entry *saddr;
int i;
int retval = 0;
+ int stored = 0;
+ chunk = NULL;
if (!sctp_addip_enable)
return retval;
@@ -766,8 +781,33 @@
bp = &asoc->base.bind_addr;
laddr = sctp_find_unmatch_addr(bp, (union sctp_addr *)addrs,
addrcnt, sp);
- if (!laddr)
- continue;
+ if ((laddr == NULL) && (addrcnt == 1)) {
+ if (asoc->asconf_addr_del_pending)
+ continue;
+ asoc->asconf_addr_del_pending =
+ kzalloc(sizeof(union sctp_addr), GFP_ATOMIC);
+ asoc->asconf_addr_del_pending->sa.sa_family =
+ addrs->sa_family;
+ asoc->asconf_addr_del_pending->v4.sin_port =
+ htons(bp->port);
+ if (addrs->sa_family == AF_INET) {
+ struct sockaddr_in *sin;
+
+ sin = (struct sockaddr_in *)addrs;
+ asoc->asconf_addr_del_pending->v4.sin_addr.s_addr = sin->sin_addr.s_addr;
+ } else if (addrs->sa_family == AF_INET6) {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = (struct sockaddr_in6 *)addrs;
+ ipv6_addr_copy(&asoc->asconf_addr_del_pending->v6.sin6_addr, &sin6->sin6_addr);
+ }
+ SCTP_DEBUG_PRINTK_IPADDR("send_asconf_del_ip: keep the last address asoc: %p ",
+ " at %p\n", asoc, asoc->asconf_addr_del_pending,
+ asoc->asconf_addr_del_pending);
+ asoc->src_out_of_asoc_ok = 1;
+ stored = 1;
+ goto skip_mkasconf;
+ }
/* We do not need RCU protection throughout this loop
* because this is done under a socket lock from the
@@ -780,6 +820,7 @@
goto out;
}
+skip_mkasconf:
/* Reset use_as_src flag for the addresses in the bind address
* list that are to be deleted.
*/
@@ -805,6 +846,9 @@
sctp_sk(asoc->base.sk));
}
+ if (stored)
+ /* We don't need to transmit ASCONF */
+ continue;
retval = sctp_send_asconf(asoc, chunk);
}
out: