[SCTP]: Change use_as_src into a full address state
[linux-2.6.git] / net / sctp / ipv6.c
index cd0af92..bd04aed 100644 (file)
 
 #include <asm/uaccess.h>
 
-/* Event handler for inet6 address addition/deletion events.  */
+/* Event handler for inet6 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_inet6addr_event(struct notifier_block *this, unsigned long ev,
                                void *ptr)
 {
        struct inet6_ifaddr *ifa = (struct inet6_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:
@@ -94,19 +99,26 @@ static int sctp_inet6addr_event(struct notifier_block *this, unsigned long ev,
                        memcpy(&addr->a.v6.sin6_addr, &ifa->addr,
                                 sizeof(struct in6_addr));
                        addr->a.v6.sin6_scope_id = ifa->idev->dev->ifindex;
-                       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);
-                       if (ipv6_addr_equal(&addr->a.v6.sin6_addr, &ifa->addr)) {
-                               list_del(pos);
-                               kfree(addr);
+               spin_lock_bh(&sctp_local_addr_lock);
+               list_for_each_entry_safe(addr, temp,
+                                       &sctp_local_addr_list, list) {
+                       if (ipv6_addr_equal(&addr->a.v6.sin6_addr,
+                                            &ifa->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;
        }
 
@@ -126,20 +138,20 @@ SCTP_STATIC void sctp_v6_err(struct sk_buff *skb, struct inet6_skb_parm *opt,
        struct sctp_association *asoc;
        struct sctp_transport *transport;
        struct ipv6_pinfo *np;
-       char *saveip, *savesctp;
+       sk_buff_data_t saveip, savesctp;
        int err;
 
        idev = in6_dev_get(skb->dev);
 
        /* Fix up skb to look at the embedded net header. */
-       saveip = skb->nh.raw;
-       savesctp  = skb->h.raw;
+       saveip   = skb->network_header;
+       savesctp = skb->transport_header;
        skb_reset_network_header(skb);
        skb_set_transport_header(skb, offset);
        sk = sctp_err_lookup(AF_INET6, skb, sctp_hdr(skb), &asoc, &transport);
        /* Put back, the original pointers. */
-       skb->nh.raw = saveip;
-       skb->h.raw = savesctp;
+       skb->network_header   = saveip;
+       skb->transport_header = savesctp;
        if (!sk) {
                ICMP6_INC_STATS_BH(idev, ICMP6_MIB_INERRORS);
                goto out;
@@ -290,9 +302,7 @@ static void sctp_v6_get_saddr(struct sctp_association *asoc,
                              union sctp_addr *saddr)
 {
        struct sctp_bind_addr *bp;
-       rwlock_t *addr_lock;
        struct sctp_sockaddr_entry *laddr;
-       struct list_head *pos;
        sctp_scope_t scope;
        union sctp_addr *baddr = NULL;
        __u8 matchlen = 0;
@@ -312,15 +322,15 @@ static void sctp_v6_get_saddr(struct sctp_association *asoc,
        scope = sctp_scope(daddr);
 
        bp = &asoc->base.bind_addr;
-       addr_lock = &asoc->base.addr_lock;
 
        /* Go through the bind address list and find the best source address
         * that matches the scope of the destination address.
         */
-       sctp_read_lock(addr_lock);
-       list_for_each(pos, &bp->address_list) {
-               laddr = list_entry(pos, struct sctp_sockaddr_entry, list);
-               if ((laddr->use_as_src) &&
+       rcu_read_lock();
+       list_for_each_entry_rcu(laddr, &bp->address_list, list) {
+               if (!laddr->valid)
+                       continue;
+               if ((laddr->state == SCTP_ADDR_SRC) &&
                    (laddr->a.sa.sa_family == AF_INET6) &&
                    (scope <= sctp_scope(&laddr->a))) {
                        bmatchlen = sctp_v6_addr_match_len(daddr, &laddr->a);
@@ -341,7 +351,7 @@ static void sctp_v6_get_saddr(struct sctp_association *asoc,
                       __FUNCTION__, asoc, NIP6(daddr->v6.sin6_addr));
        }
 
-       sctp_read_unlock(addr_lock);
+       rcu_read_unlock();
 }
 
 /* Make a copy of all potential local addresses. */
@@ -367,7 +377,9 @@ static void sctp_v6_copy_addrlist(struct list_head *addrlist,
                        addr->a.v6.sin6_port = 0;
                        addr->a.v6.sin6_addr = ifp->addr;
                        addr->a.v6.sin6_scope_id = dev->ifindex;
+                       addr->valid = 1;
                        INIT_LIST_HEAD(&addr->list);
+                       INIT_RCU_HEAD(&addr->rcu);
                        list_add_tail(&addr->list, addrlist);
                }
        }
@@ -481,7 +493,7 @@ static int sctp_v6_cmp_addr(const union sctp_addr *addr1,
        if (addr1->sa.sa_family != addr2->sa.sa_family) {
                if (addr1->sa.sa_family == AF_INET &&
                    addr2->sa.sa_family == AF_INET6 &&
-                   IPV6_ADDR_MAPPED == ipv6_addr_type(&addr2->v6.sin6_addr)) {
+                   ipv6_addr_v4mapped(&addr2->v6.sin6_addr)) {
                        if (addr2->v6.sin6_port == addr1->v4.sin_port &&
                            addr2->v6.sin6_addr.s6_addr32[3] ==
                            addr1->v4.sin_addr.s_addr)
@@ -489,7 +501,7 @@ static int sctp_v6_cmp_addr(const union sctp_addr *addr1,
                }
                if (addr2->sa.sa_family == AF_INET &&
                    addr1->sa.sa_family == AF_INET6 &&
-                   IPV6_ADDR_MAPPED == ipv6_addr_type(&addr1->v6.sin6_addr)) {
+                   ipv6_addr_v4mapped(&addr1->v6.sin6_addr)) {
                        if (addr1->v6.sin6_port == addr2->v4.sin_port &&
                            addr1->v6.sin6_addr.s6_addr32[3] ==
                            addr2->v4.sin_addr.s_addr)
@@ -604,7 +616,7 @@ static sctp_scope_t sctp_v6_scope(union sctp_addr *addr)
        default:
                retval = SCTP_SCOPE_GLOBAL;
                break;
-       };
+       }
 
        return retval;
 }
@@ -619,7 +631,7 @@ static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
        struct ipv6_pinfo *newnp, *np = inet6_sk(sk);
        struct sctp6_sock *newsctp6sk;
 
-       newsk = sk_alloc(PF_INET6, GFP_KERNEL, sk->sk_prot, 1);
+       newsk = sk_alloc(sk->sk_net, PF_INET6, GFP_KERNEL, sk->sk_prot);
        if (!newsk)
                goto out;
 
@@ -641,6 +653,8 @@ static struct sock *sctp_v6_create_accept_sk(struct sock *sk,
        newsctp6sk = (struct sctp6_sock *)newsk;
        inet_sk(newsk)->pinet6 = &newsctp6sk->inet6;
 
+       sctp_sk(newsk)->v4mapped = sctp_sk(sk)->v4mapped;
+
        newinet = inet_sk(newsk);
        newnp = inet6_sk(newsk);
 
@@ -841,9 +855,13 @@ static int sctp_inet6_bind_verify(struct sctp_sock *opt, union sctp_addr *addr)
                if (type & IPV6_ADDR_LINKLOCAL) {
                        if (!addr->v6.sin6_scope_id)
                                return 0;
-                       dev = dev_get_by_index(addr->v6.sin6_scope_id);
+                       dev = dev_get_by_index(&init_net, addr->v6.sin6_scope_id);
                        if (!dev)
                                return 0;
+                       if (!ipv6_chk_addr(&addr->v6.sin6_addr, dev, 0)) {
+                               dev_put(dev);
+                               return 0;
+                       }
                        dev_put(dev);
                }
                af = opt->pf->af;
@@ -868,7 +886,7 @@ static int sctp_inet6_send_verify(struct sctp_sock *opt, union sctp_addr *addr)
                if (type & IPV6_ADDR_LINKLOCAL) {
                        if (!addr->v6.sin6_scope_id)
                                return 0;
-                       dev = dev_get_by_index(addr->v6.sin6_scope_id);
+                       dev = dev_get_by_index(&init_net, addr->v6.sin6_scope_id);
                        if (!dev)
                                return 0;
                        dev_put(dev);
@@ -936,9 +954,9 @@ static struct inet_protosw sctpv6_stream_protosw = {
        .flags         = SCTP_PROTOSW_FLAG,
 };
 
-static int sctp6_rcv(struct sk_buff **pskb)
+static int sctp6_rcv(struct sk_buff *skb)
 {
-       return sctp_rcv(*pskb) ? -1 : 0;
+       return sctp_rcv(skb) ? -1 : 0;
 }
 
 static struct inet6_protocol sctpv6_protocol = {
@@ -992,45 +1010,52 @@ static struct sctp_pf sctp_pf_inet6_specific = {
        .af            = &sctp_ipv6_specific,
 };
 
-/* Initialize IPv6 support and register with inet6 stack.  */
+/* Initialize IPv6 support and register with socket layer.  */
 int sctp_v6_init(void)
 {
-       int rc = proto_register(&sctpv6_prot, 1);
+       int rc;
 
+       /* Register the SCTP specific PF_INET6 functions. */
+       sctp_register_pf(&sctp_pf_inet6_specific, PF_INET6);
+
+       /* Register the SCTP specific AF_INET6 functions. */
+       sctp_register_af(&sctp_ipv6_specific);
+
+       rc = proto_register(&sctpv6_prot, 1);
        if (rc)
-               goto out;
-       /* Register inet6 protocol. */
-       rc = -EAGAIN;
-       if (inet6_add_protocol(&sctpv6_protocol, IPPROTO_SCTP) < 0)
-               goto out_unregister_sctp_proto;
+               return rc;
 
        /* Add SCTPv6(UDP and TCP style) to inetsw6 linked list. */
        inet6_register_protosw(&sctpv6_seqpacket_protosw);
        inet6_register_protosw(&sctpv6_stream_protosw);
 
-       /* Register the SCTP specific PF_INET6 functions. */
-       sctp_register_pf(&sctp_pf_inet6_specific, PF_INET6);
-
-       /* Register the SCTP specific AF_INET6 functions. */
-       sctp_register_af(&sctp_ipv6_specific);
+       return 0;
+}
 
+/* Register with inet6 layer. */
+int sctp_v6_add_protocol(void)
+{
        /* Register notifier for inet6 address additions/deletions. */
        register_inet6addr_notifier(&sctp_inet6addr_notifier);
-       rc = 0;
-out:
-       return rc;
-out_unregister_sctp_proto:
-       proto_unregister(&sctpv6_prot);
-       goto out;
+
+       if (inet6_add_protocol(&sctpv6_protocol, IPPROTO_SCTP) < 0)
+               return -EAGAIN;
+
+       return 0;
 }
 
 /* IPv6 specific exit support. */
 void sctp_v6_exit(void)
 {
-       list_del(&sctp_ipv6_specific.list);
-       inet6_del_protocol(&sctpv6_protocol, IPPROTO_SCTP);
        inet6_unregister_protosw(&sctpv6_seqpacket_protosw);
        inet6_unregister_protosw(&sctpv6_stream_protosw);
-       unregister_inet6addr_notifier(&sctp_inet6addr_notifier);
        proto_unregister(&sctpv6_prot);
+       list_del(&sctp_ipv6_specific.list);
+}
+
+/* Unregister with inet6 layer. */
+void sctp_v6_del_protocol(void)
+{
+       inet6_del_protocol(&sctpv6_protocol, IPPROTO_SCTP);
+       unregister_inet6addr_notifier(&sctp_inet6addr_notifier);
 }