#include <linux/route.h>
#include <linux/inetdevice.h>
#include <linux/init.h>
+#include <linux/slab.h>
#ifdef CONFIG_SYSCTL
#include <linux/sysctl.h>
#endif
static void inet6_prefix_notify(int event, struct inet6_dev *idev,
struct prefix_info *pinfo);
-static int ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
- struct net_device *dev);
+static bool ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
+ struct net_device *dev);
static ATOMIC_NOTIFIER_HEAD(inet6addr_chain);
if (del_timer(&ifp->timer))
pr_notice("Timer is still running, when freeing ifa=%p\n", ifp);
- if (!ifp->dead) {
+ if (ifp->state != INET6_IFADDR_STATE_DEAD) {
pr_warning("Freeing alive inet6 address %p\n", ifp);
return;
}
break;
}
- list_add(&ifp->if_list, p);
+ list_add_tail(&ifp->if_list, p);
}
static u32 ipv6_addr_hash(const struct in6_addr *addr)
* We perform the hash function over the last 64 bits of the address
* This will include the IEEE address token on links that support it.
*/
- return jhash_2words(addr->s6_addr32[2], addr->s6_addr32[3], 0)
+ return jhash_2words((__force u32)addr->s6_addr32[2],
+ (__force u32)addr->s6_addr32[3], 0)
& (IN6_ADDR_HSIZE - 1);
}
ipv6_addr_copy(&ifa->addr, addr);
spin_lock_init(&ifa->lock);
+ spin_lock_init(&ifa->state_lock);
init_timer(&ifa->timer);
INIT_HLIST_NODE(&ifa->addr_lst);
ifa->timer.data = (unsigned long) ifa;
hash = ipv6_addr_hash(addr);
hlist_add_head_rcu(&ifa->addr_lst, &inet6_addr_lst[hash]);
- in6_ifa_hold(ifa);
spin_unlock(&addrconf_hash_lock);
write_lock(&idev->lock);
{
struct inet6_ifaddr *ifa, *ifn;
struct inet6_dev *idev = ifp->idev;
+ int state;
int hash;
int deleted = 0, onlink = 0;
unsigned long expires = jiffies;
hash = ipv6_addr_hash(&ifp->addr);
- ifp->dead = 1;
+ spin_lock_bh(&ifp->state_lock);
+ state = ifp->state;
+ ifp->state = INET6_IFADDR_STATE_DEAD;
+ spin_unlock_bh(&ifp->state_lock);
+
+ if (state == INET6_IFADDR_STATE_DEAD)
+ goto out;
spin_lock_bh(&addrconf_hash_lock);
hlist_del_init_rcu(&ifp->addr_lst);
- __in6_ifa_put(ifp);
spin_unlock_bh(&addrconf_hash_lock);
write_lock_bh(&idev->lock);
dst_release(&rt->u.dst);
}
+out:
in6_ifa_put(ifp);
}
int ipv6_chk_addr(struct net *net, struct in6_addr *addr,
struct net_device *dev, int strict)
{
- struct inet6_ifaddr *ifp = NULL;
+ struct inet6_ifaddr *ifp;
struct hlist_node *node;
unsigned int hash = ipv6_addr_hash(addr);
if (!net_eq(dev_net(ifp->idev->dev), net))
continue;
if (ipv6_addr_equal(&ifp->addr, addr) &&
- !(ifp->flags&IFA_F_TENTATIVE)) {
- if (dev == NULL || ifp->idev->dev == dev ||
- !(ifp->scope&(IFA_LINK|IFA_HOST) || strict))
- break;
+ !(ifp->flags&IFA_F_TENTATIVE) &&
+ (dev == NULL || ifp->idev->dev == dev ||
+ !(ifp->scope&(IFA_LINK|IFA_HOST) || strict))) {
+ rcu_read_unlock_bh();
+ return 1;
}
}
- rcu_read_unlock_bh();
- return ifp != NULL;
+ rcu_read_unlock_bh();
+ return 0;
}
EXPORT_SYMBOL(ipv6_chk_addr);
-static
-int ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
- struct net_device *dev)
+static bool ipv6_chk_same_addr(struct net *net, const struct in6_addr *addr,
+ struct net_device *dev)
{
+ unsigned int hash = ipv6_addr_hash(addr);
struct inet6_ifaddr *ifp;
struct hlist_node *node;
- unsigned int hash = ipv6_addr_hash(addr);
hlist_for_each_entry(ifp, node, &inet6_addr_lst[hash], addr_lst) {
if (!net_eq(dev_net(ifp->idev->dev), net))
continue;
if (ipv6_addr_equal(&ifp->addr, addr)) {
if (dev == NULL || ifp->idev->dev == dev)
- break;
+ return true;
}
}
- return ifp != NULL;
+ return false;
}
int ipv6_chk_prefix(struct in6_addr *addr, struct net_device *dev)
struct inet6_ifaddr *ipv6_get_ifaddr(struct net *net, const struct in6_addr *addr,
struct net_device *dev, int strict)
{
- struct inet6_ifaddr *ifp = NULL;
- struct hlist_node *node;
+ struct inet6_ifaddr *ifp, *result = NULL;
unsigned int hash = ipv6_addr_hash(addr);
+ struct hlist_node *node;
rcu_read_lock_bh();
- hlist_for_each_entry_rcu(ifp, node, &inet6_addr_lst[hash], addr_lst) {
+ hlist_for_each_entry_rcu_bh(ifp, node, &inet6_addr_lst[hash], addr_lst) {
if (!net_eq(dev_net(ifp->idev->dev), net))
continue;
if (ipv6_addr_equal(&ifp->addr, addr)) {
if (dev == NULL || ifp->idev->dev == dev ||
!(ifp->scope&(IFA_LINK|IFA_HOST) || strict)) {
+ result = ifp;
in6_ifa_hold(ifp);
break;
}
}
rcu_read_unlock_bh();
- return ifp;
+ return result;
}
/* Gets referenced address, destroys ifaddr */
ipv6_del_addr(ifp);
}
+static int addrconf_dad_end(struct inet6_ifaddr *ifp)
+{
+ int err = -ENOENT;
+
+ spin_lock(&ifp->state_lock);
+ if (ifp->state == INET6_IFADDR_STATE_DAD) {
+ ifp->state = INET6_IFADDR_STATE_POSTDAD;
+ err = 0;
+ }
+ spin_unlock(&ifp->state_lock);
+
+ return err;
+}
+
void addrconf_dad_failure(struct inet6_ifaddr *ifp)
{
struct inet6_dev *idev = ifp->idev;
+ if (addrconf_dad_end(ifp))
+ return;
+
if (net_ratelimit())
printk(KERN_INFO "%s: IPv6 duplicate address %pI6c detected!\n",
ifp->idev->dev->name, &ifp->addr);
struct inet6_dev *idev;
struct inet6_ifaddr *ifa;
LIST_HEAD(keep_list);
+ int state;
ASSERT_RTNL();
ifa = list_first_entry(&idev->tempaddr_list,
struct inet6_ifaddr, tmp_list);
list_del(&ifa->tmp_list);
- ifa->dead = 1;
write_unlock_bh(&idev->lock);
spin_lock_bh(&ifa->lock);
/* Flag it for later restoration when link comes up */
ifa->flags |= IFA_F_TENTATIVE;
+ ifa->state = INET6_IFADDR_STATE_DAD;
+
+ write_unlock_bh(&idev->lock);
+
in6_ifa_hold(ifa);
} else {
list_del(&ifa->if_list);
- ifa->dead = 1;
- }
- write_unlock_bh(&idev->lock);
- /* clear hash table */
- spin_lock_bh(&addrconf_hash_lock);
- hlist_del_init_rcu(&ifa->addr_lst);
- __in6_ifa_put(ifa);
- spin_unlock_bh(&addrconf_hash_lock);
+ /* clear hash table */
+ spin_lock_bh(&addrconf_hash_lock);
+ hlist_del_init_rcu(&ifa->addr_lst);
+ spin_unlock_bh(&addrconf_hash_lock);
+
+ write_unlock_bh(&idev->lock);
+ spin_lock_bh(&ifa->state_lock);
+ state = ifa->state;
+ ifa->state = INET6_IFADDR_STATE_DEAD;
+ spin_unlock_bh(&ifa->state_lock);
+
+ if (state == INET6_IFADDR_STATE_DEAD)
+ goto put_ifa;
+ }
__ipv6_ifa_notify(RTM_DELADDR, ifa);
- atomic_notifier_call_chain(&inet6addr_chain, NETDEV_DOWN, ifa);
+ if (ifa->state == INET6_IFADDR_STATE_DEAD)
+ atomic_notifier_call_chain(&inet6addr_chain,
+ NETDEV_DOWN, ifa);
+
+put_ifa:
in6_ifa_put(ifa);
write_lock_bh(&idev->lock);
net_srandom(ifp->addr.s6_addr32[3]);
read_lock_bh(&idev->lock);
- if (ifp->dead)
+ spin_lock(&ifp->lock);
+ if (ifp->state == INET6_IFADDR_STATE_DEAD)
goto out;
- spin_lock(&ifp->lock);
if (dev->flags&(IFF_NOARP|IFF_LOOPBACK) ||
idev->cnf.accept_dad < 1 ||
!(ifp->flags&IFA_F_TENTATIVE) ||
ip6_ins_rt(ifp->rt);
addrconf_dad_kick(ifp);
- spin_unlock(&ifp->lock);
out:
+ spin_unlock(&ifp->lock);
read_unlock_bh(&idev->lock);
}
struct inet6_dev *idev = ifp->idev;
struct in6_addr mcaddr;
+ if (!ifp->probes && addrconf_dad_end(ifp))
+ goto out;
+
read_lock(&idev->lock);
if (idev->dead || !(idev->if_flags & IF_READY)) {
read_unlock(&idev->lock);
}
spin_lock(&ifp->lock);
+ if (ifp->state == INET6_IFADDR_STATE_DEAD) {
+ spin_unlock(&ifp->lock);
+ read_unlock(&idev->lock);
+ goto out;
+ }
+
if (ifp->probes == 0) {
/*
* DAD was successful
read_lock_bh(&idev->lock);
list_for_each_entry(ifp, &idev->addr_list, if_list) {
spin_lock(&ifp->lock);
- if (!(ifp->flags & IFA_F_TENTATIVE)) {
- spin_unlock(&ifp->lock);
- continue;
- }
+ if (ifp->flags & IFA_F_TENTATIVE &&
+ ifp->state == INET6_IFADDR_STATE_DAD)
+ addrconf_dad_kick(ifp);
spin_unlock(&ifp->lock);
- addrconf_dad_kick(ifp);
}
read_unlock_bh(&idev->lock);
}
for (state->bucket = 0; state->bucket < IN6_ADDR_HSIZE; ++state->bucket) {
struct hlist_node *n;
- hlist_for_each_entry_rcu(ifa, n, &inet6_addr_lst[state->bucket],
+ hlist_for_each_entry_rcu_bh(ifa, n, &inet6_addr_lst[state->bucket],
addr_lst)
if (net_eq(dev_net(ifa->idev->dev), net))
return ifa;
struct net *net = seq_file_net(seq);
struct hlist_node *n = &ifa->addr_lst;
- hlist_for_each_entry_continue_rcu(ifa, n, addr_lst)
+ hlist_for_each_entry_continue_rcu_bh(ifa, n, addr_lst)
if (net_eq(dev_net(ifa->idev->dev), net))
return ifa;
while (++state->bucket < IN6_ADDR_HSIZE) {
- hlist_for_each_entry(ifa, n,
+ hlist_for_each_entry_rcu_bh(ifa, n,
&inet6_addr_lst[state->bucket], addr_lst) {
if (net_eq(dev_net(ifa->idev->dev), net))
return ifa;
}
static void *if6_seq_start(struct seq_file *seq, loff_t *pos)
- __acquires(rcu)
+ __acquires(rcu_bh)
{
rcu_read_lock_bh();
return if6_get_idx(seq, *pos);
}
static void if6_seq_stop(struct seq_file *seq, void *v)
- __releases(rcu)
+ __releases(rcu_bh)
{
rcu_read_unlock_bh();
}
unsigned int hash = ipv6_addr_hash(addr);
rcu_read_lock_bh();
- hlist_for_each_entry_rcu(ifp, n, &inet6_addr_lst[hash], addr_lst) {
+ hlist_for_each_entry_rcu_bh(ifp, n, &inet6_addr_lst[hash], addr_lst) {
if (!net_eq(dev_net(ifp->idev->dev), net))
continue;
if (ipv6_addr_equal(&ifp->addr, addr) &&
for (i = 0; i < IN6_ADDR_HSIZE; i++) {
restart:
- hlist_for_each_entry_rcu(ifp, node,
+ hlist_for_each_entry_rcu_bh(ifp, node,
&inet6_addr_lst[i], addr_lst) {
unsigned long age;
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;
idev = __in6_dev_get(dev);
addrconf_leave_anycast(ifp);
addrconf_leave_solict(ifp->idev, &ifp->addr);
dst_hold(&ifp->rt->u.dst);
- if (ip6_del_rt(ifp->rt))
+
+ if (ifp->state == INET6_IFADDR_STATE_DEAD &&
+ ip6_del_rt(ifp->rt))
dst_free(&ifp->rt->u.dst);
break;
}