diff --git a/net/ipv6/anycast.c b/net/ipv6/anycast.c index 66c19320119a..952c1fd06150 100644 --- a/net/ipv6/anycast.c +++ b/net/ipv6/anycast.c @@ -203,6 +203,11 @@ void ipv6_sock_ac_close(struct sock *sk) rtnl_unlock(); } +static void aca_get(struct ifacaddr6 *aca) +{ + atomic_inc(&aca->aca_refcnt); +} + static void aca_put(struct ifacaddr6 *ac) { if (atomic_dec_and_test(&ac->aca_refcnt)) { @@ -212,6 +217,29 @@ static void aca_put(struct ifacaddr6 *ac) } } +static struct ifacaddr6 *aca_alloc(struct rt6_info *rt, + const struct in6_addr *addr) +{ + struct inet6_dev *idev = rt->rt6i_idev; + struct ifacaddr6 *aca; + + aca = kzalloc(sizeof(*aca), GFP_ATOMIC); + if (aca == NULL) + return NULL; + + aca->aca_addr = *addr; + in6_dev_hold(idev); + aca->aca_idev = idev; + aca->aca_rt = rt; + aca->aca_users = 1; + /* aca_tstamp should be updated upon changes */ + aca->aca_cstamp = aca->aca_tstamp = jiffies; + atomic_set(&aca->aca_refcnt, 1); + spin_lock_init(&aca->aca_lock); + + return aca; +} + /* * device anycast group inc (add if not found) */ @@ -223,7 +251,6 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr) ASSERT_RTNL(); - in6_dev_hold(idev); write_lock_bh(&idev->lock); if (idev->dead) { err = -ENODEV; @@ -238,35 +265,25 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr) } } - /* - * not found: create a new one. - */ - - aca = kzalloc(sizeof(struct ifacaddr6), GFP_ATOMIC); - + rt = addrconf_dst_alloc(idev, addr, true); + if (IS_ERR(rt)) { + err = PTR_ERR(rt); + goto out; + } + aca = aca_alloc(rt, addr); if (aca == NULL) { + ip6_rt_put(rt); err = -ENOMEM; goto out; } - rt = addrconf_dst_alloc(idev, addr, true); - if (IS_ERR(rt)) { - kfree(aca); - err = PTR_ERR(rt); - goto out; - } - - aca->aca_addr = *addr; - aca->aca_idev = idev; - aca->aca_rt = rt; - aca->aca_users = 1; - /* aca_tstamp should be updated upon changes */ - aca->aca_cstamp = aca->aca_tstamp = jiffies; - atomic_set(&aca->aca_refcnt, 2); - spin_lock_init(&aca->aca_lock); - aca->aca_next = idev->ac_list; idev->ac_list = aca; + + /* Hold this for addrconf_join_solict() below before we unlock, + * it is already exposed via idev->ac_list. + */ + aca_get(aca); write_unlock_bh(&idev->lock); ip6_ins_rt(rt); @@ -277,7 +294,6 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr) return 0; out: write_unlock_bh(&idev->lock); - in6_dev_put(idev); return err; }