[SCTP]: Add RCU synchronization around sctp_localaddr_list
[powerpc.git] / net / sctp / protocol.c
index e98579b..7ee120e 100644 (file)
@@ -153,6 +153,9 @@ static void sctp_v4_copy_addrlist(struct list_head *addrlist,
                        addr->a.v4.sin_family = AF_INET;
                        addr->a.v4.sin_port = 0;
                        addr->a.v4.sin_addr.s_addr = ifa->ifa_local;
+                       addr->valid = 1;
+                       INIT_LIST_HEAD(&addr->list);
+                       INIT_RCU_HEAD(&addr->rcu);
                        list_add_tail(&addr->list, addrlist);
                }
        }
@@ -192,16 +195,24 @@ static void sctp_free_local_addr_list(void)
        }
 }
 
+void sctp_local_addr_free(struct rcu_head *head)
+{
+       struct sctp_sockaddr_entry *e = container_of(head,
+                               struct sctp_sockaddr_entry, rcu);
+       kfree(e);
+}
+
 /* Copy the local addresses which are valid for 'scope' into 'bp'.  */
 int sctp_copy_local_addr_list(struct sctp_bind_addr *bp, sctp_scope_t scope,
                              gfp_t gfp, int copy_flags)
 {
        struct sctp_sockaddr_entry *addr;
        int error = 0;
-       struct list_head *pos, *temp;
 
-       list_for_each_safe(pos, temp, &sctp_local_addr_list) {
-               addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+       rcu_read_lock();
+       list_for_each_entry_rcu(addr, &sctp_local_addr_list, list) {
+               if (!addr->valid)
+                       continue;
                if (sctp_in_scope(&addr->a, scope)) {
                        /* Now that the address is in scope, check to see if
                         * the address type is really supported by the local
@@ -221,6 +232,7 @@ int sctp_copy_local_addr_list(struct sctp_bind_addr *bp, sctp_scope_t scope,
        }
 
 end_copy:
+       rcu_read_unlock();
        return error;
 }
 
@@ -600,13 +612,18 @@ static void sctp_v4_seq_dump_addr(struct seq_file *seq, union sctp_addr *addr)
        seq_printf(seq, "%d.%d.%d.%d ", NIPQUAD(addr->v4.sin_addr));
 }
 
-/* Event handler for inet address addition/deletion events.  */
+/* 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
+ * time and thus corrupt the list.
+ * The reader side is protected with RCU.
+ */
 static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev,
                               void *ptr)
 {
        struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
-       struct sctp_sockaddr_entry *addr;
-       struct list_head *pos, *temp;
+       struct sctp_sockaddr_entry *addr = NULL;
+       struct sctp_sockaddr_entry *temp;
 
        switch (ev) {
        case NETDEV_UP:
@@ -615,19 +632,25 @@ static int sctp_inetaddr_event(struct notifier_block *this, unsigned long ev,
                        addr->a.v4.sin_family = AF_INET;
                        addr->a.v4.sin_port = 0;
                        addr->a.v4.sin_addr.s_addr = ifa->ifa_local;
-                       list_add_tail(&addr->list, &sctp_local_addr_list);
+                       addr->valid = 1;
+                       spin_lock_bh(&sctp_local_addr_lock);
+                       list_add_tail_rcu(&addr->list, &sctp_local_addr_list);
+                       spin_unlock_bh(&sctp_local_addr_lock);
                }
                break;
        case NETDEV_DOWN:
-               list_for_each_safe(pos, temp, &sctp_local_addr_list) {
-                       addr = list_entry(pos, struct sctp_sockaddr_entry, list);
+               spin_lock_bh(&sctp_local_addr_lock);
+               list_for_each_entry_safe(addr, temp,
+                                       &sctp_local_addr_list, list) {
                        if (addr->a.v4.sin_addr.s_addr == ifa->ifa_local) {
-                               list_del(pos);
-                               kfree(addr);
+                               addr->valid = 0;
+                               list_del_rcu(&addr->list);
                                break;
                        }
                }
-
+               spin_unlock_bh(&sctp_local_addr_lock);
+               if (addr && !addr->valid)
+                       call_rcu(&addr->rcu, sctp_local_addr_free);
                break;
        }
 
@@ -1160,6 +1183,7 @@ SCTP_STATIC __init int sctp_init(void)
 
        /* Initialize the local address list. */
        INIT_LIST_HEAD(&sctp_local_addr_list);
+       spin_lock_init(&sctp_local_addr_lock);
        sctp_get_local_addr_list();
 
        /* Register notifier for inet address additions/deletions. */
@@ -1227,6 +1251,9 @@ SCTP_STATIC __exit void sctp_exit(void)
        sctp_v6_del_protocol();
        inet_del_protocol(&sctp_protocol, IPPROTO_SCTP);
 
+       /* Unregister notifier for inet address additions/deletions. */
+       unregister_inetaddr_notifier(&sctp_inetaddr_notifier);
+
        /* Free the local address list.  */
        sctp_free_local_addr_list();
 
@@ -1240,9 +1267,6 @@ SCTP_STATIC __exit void sctp_exit(void)
        inet_unregister_protosw(&sctp_stream_protosw);
        inet_unregister_protosw(&sctp_seqpacket_protosw);
 
-       /* Unregister notifier for inet address additions/deletions. */
-       unregister_inetaddr_notifier(&sctp_inetaddr_notifier);
-
        sctp_sysctl_unregister();
        list_del(&sctp_ipv4_specific.list);