IPv6: fix race between cleanup and add/delete address
[pandora-kernel.git] / net / ipv6 / addrconf.c
index b0d4a4b..6cf3ee1 100644 (file)
@@ -502,8 +502,11 @@ static int addrconf_fixup_forwarding(struct ctl_table *table, int *p, int old)
        if (p == &net->ipv6.devconf_dflt->forwarding)
                return 0;
 
-       if (!rtnl_trylock())
+       if (!rtnl_trylock()) {
+               /* Restore the original values before restarting */
+               *p = old;
                return restart_syscall();
+       }
 
        if (p == &net->ipv6.devconf_all->forwarding) {
                __s32 newf = net->ipv6.devconf_all->forwarding;
@@ -989,8 +992,7 @@ struct ipv6_saddr_dst {
 
 static inline int ipv6_saddr_preferred(int type)
 {
-       if (type & (IPV6_ADDR_MAPPED|IPV6_ADDR_COMPATv4|
-                   IPV6_ADDR_LOOPBACK|IPV6_ADDR_RESERVED))
+       if (type & (IPV6_ADDR_MAPPED|IPV6_ADDR_COMPATv4|IPV6_ADDR_LOOPBACK))
                return 1;
        return 0;
 }
@@ -2613,7 +2615,7 @@ static void addrconf_bonding_change(struct net_device *dev, unsigned long event)
 static int addrconf_ifdown(struct net_device *dev, int how)
 {
        struct inet6_dev *idev;
-       struct inet6_ifaddr *ifa, **bifa;
+       struct inet6_ifaddr *ifa, *keep_list, **bifa;
        struct net *net = dev_net(dev);
        int i;
 
@@ -2647,11 +2649,11 @@ static int addrconf_ifdown(struct net_device *dev, int how)
                write_lock_bh(&addrconf_hash_lock);
                while ((ifa = *bifa) != NULL) {
                        if (ifa->idev == idev &&
-                           (how || !(ifa->flags&IFA_F_PERMANENT))) {
+                           (how || !(ifa->flags&IFA_F_PERMANENT) ||
+                            ipv6_addr_type(&ifa->addr) & IPV6_ADDR_LINKLOCAL)) {
                                *bifa = ifa->lst_next;
                                ifa->lst_next = NULL;
-                               addrconf_del_timer(ifa);
-                               in6_ifa_put(ifa);
+                               __in6_ifa_put(ifa);
                                continue;
                        }
                        bifa = &ifa->lst_next;
@@ -2687,31 +2689,51 @@ static int addrconf_ifdown(struct net_device *dev, int how)
                write_lock_bh(&idev->lock);
        }
 #endif
-       bifa = &idev->addr_list;
-       while ((ifa = *bifa) != NULL) {
-               if (how == 0 && (ifa->flags&IFA_F_PERMANENT)) {
-                       /* Retain permanent address on admin down */
+       keep_list = NULL;
+       bifa = &keep_list;
+       while ((ifa = idev->addr_list) != NULL) {
+               idev->addr_list = ifa->if_next;
+               ifa->if_next = NULL;
+
+               addrconf_del_timer(ifa);
+
+               /* If just doing link down, and address is permanent
+                  and not link-local, then retain it. */
+               if (how == 0 &&
+                   (ifa->flags&IFA_F_PERMANENT) &&
+                   !(ipv6_addr_type(&ifa->addr) & IPV6_ADDR_LINKLOCAL)) {
+
+                       /* Move to holding list */
+                       *bifa = ifa;
                        bifa = &ifa->if_next;
 
-                       /* Restart DAD if needed when link comes back up */
-                       if ( !((dev->flags&(IFF_NOARP|IFF_LOOPBACK)) ||
-                              idev->cnf.accept_dad <= 0 ||
-                              (ifa->flags & IFA_F_NODAD)))
-                               ifa->flags |= IFA_F_TENTATIVE;
-               } else {
-                       *bifa = ifa->if_next;
-                       ifa->if_next = NULL;
+                       /* If not doing DAD on this address, just keep it. */
+                       if ((dev->flags&(IFF_NOARP|IFF_LOOPBACK)) ||
+                           idev->cnf.accept_dad <= 0 ||
+                           (ifa->flags & IFA_F_NODAD))
+                               continue;
 
+                       /* If it was tentative already, no need to notify */
+                       if (ifa->flags & IFA_F_TENTATIVE)
+                               continue;
+
+                       /* Flag it for later restoration when link comes up */
+                       ifa->flags |= IFA_F_TENTATIVE;
+                       in6_ifa_hold(ifa);
+               } else {
                        ifa->dead = 1;
-                       write_unlock_bh(&idev->lock);
+               }
+               write_unlock_bh(&idev->lock);
 
-                       __ipv6_ifa_notify(RTM_DELADDR, ifa);
-                       atomic_notifier_call_chain(&inet6addr_chain, NETDEV_DOWN, ifa);
-                       in6_ifa_put(ifa);
+               __ipv6_ifa_notify(RTM_DELADDR, ifa);
+               atomic_notifier_call_chain(&inet6addr_chain, NETDEV_DOWN, ifa);
+               in6_ifa_put(ifa);
 
-                       write_lock_bh(&idev->lock);
-               }
+               write_lock_bh(&idev->lock);
        }
+
+       idev->addr_list = keep_list;
+
        write_unlock_bh(&idev->lock);
 
        /* Step 5: Discard multicast list */
@@ -2737,28 +2759,29 @@ static int addrconf_ifdown(struct net_device *dev, int how)
 static void addrconf_rs_timer(unsigned long data)
 {
        struct inet6_ifaddr *ifp = (struct inet6_ifaddr *) data;
+       struct inet6_dev *idev = ifp->idev;
 
-       if (ifp->idev->cnf.forwarding)
+       read_lock(&idev->lock);
+       if (idev->dead || !(idev->if_flags & IF_READY))
                goto out;
 
-       if (ifp->idev->if_flags & IF_RA_RCVD) {
-               /*
-                *      Announcement received after solicitation
-                *      was sent
-                */
+       if (idev->cnf.forwarding)
+               goto out;
+
+       /* Announcement received after solicitation was sent */
+       if (idev->if_flags & IF_RA_RCVD)
                goto out;
-       }
 
        spin_lock(&ifp->lock);
-       if (ifp->probes++ < ifp->idev->cnf.rtr_solicits) {
+       if (ifp->probes++ < idev->cnf.rtr_solicits) {
                /* The wait after the last probe can be shorter */
                addrconf_mod_timer(ifp, AC_RS,
-                                  (ifp->probes == ifp->idev->cnf.rtr_solicits) ?
-                                  ifp->idev->cnf.rtr_solicit_delay :
-                                  ifp->idev->cnf.rtr_solicit_interval);
+                                  (ifp->probes == idev->cnf.rtr_solicits) ?
+                                  idev->cnf.rtr_solicit_delay :
+                                  idev->cnf.rtr_solicit_interval);
                spin_unlock(&ifp->lock);
 
-               ndisc_send_rs(ifp->idev->dev, &ifp->addr, &in6addr_linklocal_allrouters);
+               ndisc_send_rs(idev->dev, &ifp->addr, &in6addr_linklocal_allrouters);
        } else {
                spin_unlock(&ifp->lock);
                /*
@@ -2766,10 +2789,11 @@ static void addrconf_rs_timer(unsigned long data)
                 * assumption any longer.
                 */
                printk(KERN_DEBUG "%s: no IPv6 routers present\n",
-                      ifp->idev->dev->name);
+                      idev->dev->name);
        }
 
 out:
+       read_unlock(&idev->lock);
        in6_ifa_put(ifp);
 }
 
@@ -2848,9 +2872,9 @@ static void addrconf_dad_timer(unsigned long data)
        struct inet6_dev *idev = ifp->idev;
        struct in6_addr mcaddr;
 
-       read_lock_bh(&idev->lock);
-       if (idev->dead) {
-               read_unlock_bh(&idev->lock);
+       read_lock(&idev->lock);
+       if (idev->dead || !(idev->if_flags & IF_READY)) {
+               read_unlock(&idev->lock);
                goto out;
        }
 
@@ -2862,7 +2886,7 @@ static void addrconf_dad_timer(unsigned long data)
 
                ifp->flags &= ~(IFA_F_TENTATIVE|IFA_F_OPTIMISTIC|IFA_F_DADFAILED);
                spin_unlock(&ifp->lock);
-               read_unlock_bh(&idev->lock);
+               read_unlock(&idev->lock);
 
                addrconf_dad_completed(ifp);
 
@@ -2872,7 +2896,7 @@ static void addrconf_dad_timer(unsigned long data)
        ifp->probes--;
        addrconf_mod_timer(ifp, AC_DAD, ifp->idev->nd_parms->retrans_time);
        spin_unlock(&ifp->lock);
-       read_unlock_bh(&idev->lock);
+       read_unlock(&idev->lock);
 
        /* send a neighbour solicitation for our addr */
        addrconf_addr_solict_mult(&ifp->addr, &mcaddr);
@@ -4042,12 +4066,15 @@ int addrconf_sysctl_forward(ctl_table *ctl, int write,
 {
        int *valp = ctl->data;
        int val = *valp;
+       loff_t pos = *ppos;
        int ret;
 
        ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
 
        if (write)
                ret = addrconf_fixup_forwarding(ctl, valp, val);
+       if (ret)
+               *ppos = pos;
        return ret;
 }
 
@@ -4089,8 +4116,11 @@ static int addrconf_disable_ipv6(struct ctl_table *table, int *p, int old)
        if (p == &net->ipv6.devconf_dflt->disable_ipv6)
                return 0;
 
-       if (!rtnl_trylock())
+       if (!rtnl_trylock()) {
+               /* Restore the original values before restarting */
+               *p = old;
                return restart_syscall();
+       }
 
        if (p == &net->ipv6.devconf_all->disable_ipv6) {
                __s32 newf = net->ipv6.devconf_all->disable_ipv6;
@@ -4109,12 +4139,15 @@ int addrconf_sysctl_disable(ctl_table *ctl, int write,
 {
        int *valp = ctl->data;
        int val = *valp;
+       loff_t pos = *ppos;
        int ret;
 
        ret = proc_dointvec(ctl, write, buffer, lenp, ppos);
 
        if (write)
                ret = addrconf_disable_ipv6(ctl, valp, val);
+       if (ret)
+               *ppos = pos;
        return ret;
 }