sctp: Add Auto-ASCONF support (core).
[deliverable/linux.git] / net / sctp / protocol.c
index 67380a29e2e9a93c33a6dfd7d0b8f497c9789eba..013c6136c5468489da3fea330b9a5acfcfa501f2 100644 (file)
@@ -623,6 +623,142 @@ static void sctp_v4_ecn_capable(struct sock *sk)
        INET_ECN_xmit(sk);
 }
 
+void sctp_addr_wq_timeout_handler(unsigned long arg)
+{
+       struct sctp_sockaddr_entry *addrw, *temp;
+       struct sctp_sock *sp;
+
+       spin_lock_bh(&sctp_addr_wq_lock);
+
+       list_for_each_entry_safe(addrw, temp, &sctp_addr_waitq, list) {
+               SCTP_DEBUG_PRINTK_IPADDR("sctp_addrwq_timo_handler: the first ent in wq %p is ",
+                   " for cmd %d at entry %p\n", &sctp_addr_waitq, &addrw->a, addrw->state,
+                   addrw);
+
+               /* Now we send an ASCONF for each association */
+               /* Note. we currently don't handle link local IPv6 addressees */
+               if (addrw->a.sa.sa_family == AF_INET6) {
+                       struct in6_addr *in6;
+
+                       if (ipv6_addr_type(&addrw->a.v6.sin6_addr) &
+                           IPV6_ADDR_LINKLOCAL)
+                               goto free_next;
+
+                       in6 = (struct in6_addr *)&addrw->a.v6.sin6_addr;
+                       if (ipv6_chk_addr(&init_net, in6, NULL, 0) == 0 &&
+                           addrw->state == SCTP_ADDR_NEW) {
+                               unsigned long timeo_val;
+
+                               SCTP_DEBUG_PRINTK("sctp_timo_handler: this is on DAD, trying %d sec later\n",
+                                   SCTP_ADDRESS_TICK_DELAY);
+                               timeo_val = jiffies;
+                               timeo_val += msecs_to_jiffies(SCTP_ADDRESS_TICK_DELAY);
+                               mod_timer(&sctp_addr_wq_timer, timeo_val);
+                               break;
+                       }
+               }
+
+               list_for_each_entry(sp, &sctp_auto_asconf_splist, auto_asconf_list) {
+                       struct sock *sk;
+
+                       sk = sctp_opt2sk(sp);
+                       /* ignore bound-specific endpoints */
+                       if (!sctp_is_ep_boundall(sk))
+                               continue;
+                       sctp_bh_lock_sock(sk);
+                       if (sctp_asconf_mgmt(sp, addrw) < 0)
+                               SCTP_DEBUG_PRINTK("sctp_addrwq_timo_handler: sctp_asconf_mgmt failed\n");
+                       sctp_bh_unlock_sock(sk);
+               }
+free_next:
+               list_del(&addrw->list);
+               kfree(addrw);
+       }
+       spin_unlock_bh(&sctp_addr_wq_lock);
+}
+
+static void sctp_free_addr_wq(void)
+{
+       struct sctp_sockaddr_entry *addrw;
+       struct sctp_sockaddr_entry *temp;
+
+       spin_lock_bh(&sctp_addr_wq_lock);
+       del_timer(&sctp_addr_wq_timer);
+       list_for_each_entry_safe(addrw, temp, &sctp_addr_waitq, list) {
+               list_del(&addrw->list);
+               kfree(addrw);
+       }
+       spin_unlock_bh(&sctp_addr_wq_lock);
+}
+
+/* lookup the entry for the same address in the addr_waitq
+ * sctp_addr_wq MUST be locked
+ */
+static struct sctp_sockaddr_entry *sctp_addr_wq_lookup(struct sctp_sockaddr_entry *addr)
+{
+       struct sctp_sockaddr_entry *addrw;
+
+       list_for_each_entry(addrw, &sctp_addr_waitq, list) {
+               if (addrw->a.sa.sa_family != addr->a.sa.sa_family)
+                       continue;
+               if (addrw->a.sa.sa_family == AF_INET) {
+                       if (addrw->a.v4.sin_addr.s_addr ==
+                           addr->a.v4.sin_addr.s_addr)
+                               return addrw;
+               } else if (addrw->a.sa.sa_family == AF_INET6) {
+                       if (ipv6_addr_equal(&addrw->a.v6.sin6_addr,
+                           &addr->a.v6.sin6_addr))
+                               return addrw;
+               }
+       }
+       return NULL;
+}
+
+void sctp_addr_wq_mgmt(struct sctp_sockaddr_entry *addr, int cmd)
+{
+       struct sctp_sockaddr_entry *addrw;
+       unsigned long timeo_val;
+
+       /* first, we check if an opposite message already exist in the queue.
+        * If we found such message, it is removed.
+        * This operation is a bit stupid, but the DHCP client attaches the
+        * new address after a couple of addition and deletion of that address
+        */
+
+       spin_lock_bh(&sctp_addr_wq_lock);
+       /* Offsets existing events in addr_wq */
+       addrw = sctp_addr_wq_lookup(addr);
+       if (addrw) {
+               if (addrw->state != cmd) {
+                       SCTP_DEBUG_PRINTK_IPADDR("sctp_addr_wq_mgmt offsets existing entry for %d ",
+                           " in wq %p\n", addrw->state, &addrw->a,
+                           &sctp_addr_waitq);
+                       list_del(&addrw->list);
+                       kfree(addrw);
+               }
+               spin_unlock_bh(&sctp_addr_wq_lock);
+               return;
+       }
+
+       /* OK, we have to add the new address to the wait queue */
+       addrw = kmemdup(addr, sizeof(struct sctp_sockaddr_entry), GFP_ATOMIC);
+       if (addrw == NULL) {
+               spin_unlock_bh(&sctp_addr_wq_lock);
+               return;
+       }
+       addrw->state = cmd;
+       list_add_tail(&addrw->list, &sctp_addr_waitq);
+       SCTP_DEBUG_PRINTK_IPADDR("sctp_addr_wq_mgmt add new entry for cmd:%d ",
+           " in wq %p\n", addrw->state, &addrw->a, &sctp_addr_waitq);
+
+       if (!timer_pending(&sctp_addr_wq_timer)) {
+               timeo_val = jiffies;
+               timeo_val += msecs_to_jiffies(SCTP_ADDRESS_TICK_DELAY);
+               mod_timer(&sctp_addr_wq_timer, timeo_val);
+       }
+       spin_unlock_bh(&sctp_addr_wq_lock);
+}
+
 /* Event handler for inet address addition/deletion events.
  * The sctp_local_addr_list needs to be protocted by a spin lock since
  * multiple notifiers (say IPv4 and IPv6) may be running at the same
@@ -650,6 +786,7 @@ static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev,
                        addr->valid = 1;
                        spin_lock_bh(&sctp_local_addr_lock);
                        list_add_tail_rcu(&addr->list, &sctp_local_addr_list);
+                       sctp_addr_wq_mgmt(addr, SCTP_ADDR_NEW);
                        spin_unlock_bh(&sctp_local_addr_lock);
                }
                break;
@@ -660,6 +797,7 @@ static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev,
                        if (addr->a.sa.sa_family == AF_INET &&
                                        addr->a.v4.sin_addr.s_addr ==
                                        ifa->ifa_local) {
+                               sctp_addr_wq_mgmt(addr, SCTP_ADDR_DEL);
                                found = 1;
                                addr->valid = 0;
                                list_del_rcu(&addr->list);
@@ -1242,6 +1380,7 @@ SCTP_STATIC __init int sctp_init(void)
        /* Disable ADDIP by default. */
        sctp_addip_enable = 0;
        sctp_addip_noauth = 0;
+       sctp_default_auto_asconf = 0;
 
        /* Enable PR-SCTP by default. */
        sctp_prsctp_enable = 1;
@@ -1266,6 +1405,13 @@ SCTP_STATIC __init int sctp_init(void)
        spin_lock_init(&sctp_local_addr_lock);
        sctp_get_local_addr_list();
 
+       /* Initialize the address event list */
+       INIT_LIST_HEAD(&sctp_addr_waitq);
+       INIT_LIST_HEAD(&sctp_auto_asconf_splist);
+       spin_lock_init(&sctp_addr_wq_lock);
+       sctp_addr_wq_timer.expires = 0;
+       setup_timer(&sctp_addr_wq_timer, sctp_addr_wq_timeout_handler, 0);
+
        status = sctp_v4_protosw_init();
 
        if (status)
@@ -1337,6 +1483,7 @@ SCTP_STATIC __exit void sctp_exit(void)
        /* Unregister with inet6/inet layers. */
        sctp_v6_del_protocol();
        sctp_v4_del_protocol();
+       sctp_free_addr_wq();
 
        /* Free the control endpoint.  */
        inet_ctl_sock_destroy(sctp_ctl_sock);
This page took 0.027991 seconds and 5 git commands to generate.