sctp: Add ASCONF operation on the single-homed host
authorMichio Honda <micchie@sfc.wide.ad.jp>
Tue, 26 Apr 2011 11:19:36 +0000 (20:19 +0900)
committerDavid S. Miller <davem@davemloft.net>
Thu, 2 Jun 2011 09:04:53 +0000 (02:04 -0700)
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>
include/net/sctp/structs.h
net/sctp/associola.c
net/sctp/outqueue.c
net/sctp/protocol.c
net/sctp/sm_make_chunk.c
net/sctp/socket.c

index cd8c929e6b0d4a36aa027ae8cb773865a6925384..31d7ea2e1d2a2bdbd2c9e9bd823274482f74a564 100644 (file)
@@ -1913,6 +1913,8 @@ struct sctp_association {
         * after reaching 4294967295.
         */
        __u32 addip_serial;
+       union sctp_addr *asconf_addr_del_pending;
+       int src_out_of_asoc_ok;
 
        /* SCTP AUTH: list of the endpoint shared keys.  These
         * keys are provided out of band by the user applicaton
index 4a62888f2e43f43a7037f556257520a462d5ece2..dc16b90ddb6f28d66b623fe820a8b457696d5560 100644 (file)
@@ -280,6 +280,8 @@ static struct sctp_association *sctp_association_init(struct sctp_association *a
        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 @@ void sctp_association_free(struct sctp_association *asoc)
 
        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);
 
index 1c88c8911dc50095315bc02463f2b3e5e1535509..edc753297a49fb034fdaf8a1f44a846323fc5ef9 100644 (file)
@@ -754,6 +754,16 @@ static int sctp_outq_flush(struct sctp_outq *q, int rtx_timeout)
         */
 
        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 @@ static int sctp_outq_flush(struct sctp_outq *q, int rtx_timeout)
                }
        }
 
+       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:
index 013c6136c5468489da3fea330b9a5acfcfa501f2..af0a6b0fc9b6106ba56813bfe43b56a9f8edd630 100644 (file)
@@ -503,7 +503,9 @@ static void sctp_v4_get_dst(struct sctp_transport *t, union sctp_addr *saddr,
                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;
index 37406039820f080545a1dfd7e1f51be95b996e5c..3363d37882594e596e42d7030d39dbd476fa8733 100644 (file)
@@ -2768,6 +2768,7 @@ struct sctp_chunk *sctp_make_asconf_update_ip(struct sctp_association *asoc,
        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 @@ struct sctp_chunk *sctp_make_asconf_update_ip(struct sctp_association *asoc,
                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 @@ struct sctp_chunk *sctp_make_asconf_update_ip(struct sctp_association *asoc,
 
                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, &param);
+               sctp_addto_chunk(retval, addr_param_len, &addr_param);
+       }
        return retval;
 }
 
@@ -3224,6 +3243,11 @@ static void sctp_asconf_param_success(struct sctp_association *asoc,
        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 @@ int sctp_process_asconf_ack(struct sctp_association *asoc,
                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);
index cc06198dc4445005e754dd26e2a3a1684b9ea593..e7e1b142875c7ca8f3c501ed7510ca2fc534dd2d 100644 (file)
@@ -583,10 +583,6 @@ static int sctp_send_asconf_add_ip(struct sock             *sk,
                        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 @@ static int sctp_send_asconf_add_ip(struct sock             *sk,
                                                    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 @@ static int sctp_send_asconf_del_ip(struct sock              *sk,
        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 @@ static int sctp_send_asconf_del_ip(struct sock             *sk,
                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 @@ static int sctp_send_asconf_del_ip(struct sock              *sk,
                        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 @@ static int sctp_send_asconf_del_ip(struct sock              *sk,
                                             sctp_sk(asoc->base.sk));
                }
 
+               if (stored)
+                       /* We don't need to transmit ASCONF */
+                       continue;
                retval = sctp_send_asconf(asoc, chunk);
        }
 out: