summaryrefslogtreecommitdiffstats
path: root/net/ipv6/addrconf.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/ipv6/addrconf.c')
-rw-r--r--net/ipv6/addrconf.c107
1 files changed, 66 insertions, 41 deletions
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 88fd8c5877e..413054f02aa 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -53,6 +53,7 @@
#include <linux/route.h>
#include <linux/inetdevice.h>
#include <linux/init.h>
+#include <linux/slab.h>
#ifdef CONFIG_SYSCTL
#include <linux/sysctl.h>
#endif
@@ -1380,6 +1381,8 @@ static void addrconf_dad_stop(struct inet6_ifaddr *ifp, int dad_failed)
if (dad_failed)
ifp->flags |= IFA_F_DADFAILED;
spin_unlock_bh(&ifp->lock);
+ if (dad_failed)
+ ipv6_ifa_notify(0, ifp);
in6_ifa_put(ifp);
#ifdef CONFIG_IPV6_PRIVACY
} else if (ifp->flags&IFA_F_TEMPORARY) {
@@ -2615,7 +2618,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;
@@ -2649,11 +2652,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;
@@ -2689,31 +2692,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 */
@@ -2739,28 +2762,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);
/*
@@ -2768,10 +2792,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);
}
@@ -2850,9 +2875,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;
}
@@ -2864,7 +2889,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);
@@ -2874,7 +2899,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);
@@ -3586,7 +3611,7 @@ static int inet6_dump_addr(struct sk_buff *skb, struct netlink_callback *cb,
hlist_for_each_entry_rcu(dev, node, head, index_hlist) {
if (idx < s_idx)
goto cont;
- if (idx > s_idx)
+ if (h > s_h || idx > s_idx)
s_ip_idx = 0;
ip_idx = 0;
if ((idev = __in6_dev_get(dev)) == NULL)