Skip to content

Commit f3d9832

Browse files
dsaherndavem330
authored andcommitted
ipv6: addrconf: cleanup locking in ipv6_add_addr
ipv6_add_addr is called in process context with rtnl lock held (e.g., manual config of an address) or during softirq processing (e.g., autoconf and address from a router advertisement). Currently, ipv6_add_addr calls rcu_read_lock_bh shortly after entry and does not call unlock until exit, minus the call around the address validator notifier. Similarly, addrconf_hash_lock is taken after the validator notifier and held until exit. This forces the allocation of inet6_ifaddr to always be atomic. Refactor ipv6_add_addr as follows: 1. add an input boolean to discriminate the call path (process context or softirq). This new flag controls whether the alloc can be done with GFP_KERNEL or GFP_ATOMIC. 2. Move the rcu_read_lock_bh and unlock calls only around functions that do rcu updates. 3. Remove the in6_dev_hold and put added by 3ad7d24 ("Ipvlan should return an error when an address is already in use."). This was done presumably because rcu_read_unlock_bh needs to be called before calling the validator. Since rcu_read_lock is not needed before the validator runs revert the hold and put added by 3ad7d24 and only do the hold when setting ifp->idev. 4. move duplicate address check and insertion of new address in the global address hash into a helper. The helper is called after an ifa is allocated and filled in. This allows the ifa for manually configured addresses to be done with GFP_KERNEL and reduces the overall amount of time with rcu_read_lock held and hash table spinlock held. Signed-off-by: David Ahern <dsahern@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
1 parent 6b1f8ed commit f3d9832

File tree

1 file changed

+60
-44
lines changed

1 file changed

+60
-44
lines changed

net/ipv6/addrconf.c

+60-44
Original file line numberDiff line numberDiff line change
@@ -957,18 +957,43 @@ static u32 inet6_addr_hash(const struct in6_addr *addr)
957957
return hash_32(ipv6_addr_hash(addr), IN6_ADDR_HSIZE_SHIFT);
958958
}
959959

960+
static int ipv6_add_addr_hash(struct net_device *dev, struct inet6_ifaddr *ifa)
961+
{
962+
unsigned int hash;
963+
int err = 0;
964+
965+
spin_lock(&addrconf_hash_lock);
966+
967+
/* Ignore adding duplicate addresses on an interface */
968+
if (ipv6_chk_same_addr(dev_net(dev), &ifa->addr, dev)) {
969+
ADBG("ipv6_add_addr: already assigned\n");
970+
err = -EEXIST;
971+
goto out;
972+
}
973+
974+
/* Add to big hash table */
975+
hash = inet6_addr_hash(&ifa->addr);
976+
hlist_add_head_rcu(&ifa->addr_lst, &inet6_addr_lst[hash]);
977+
978+
out:
979+
spin_unlock(&addrconf_hash_lock);
980+
981+
return err;
982+
}
983+
960984
/* On success it returns ifp with increased reference count */
961985

962986
static struct inet6_ifaddr *
963987
ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
964988
const struct in6_addr *peer_addr, int pfxlen,
965-
int scope, u32 flags, u32 valid_lft, u32 prefered_lft)
989+
int scope, u32 flags, u32 valid_lft, u32 prefered_lft,
990+
bool can_block)
966991
{
992+
gfp_t gfp_flags = can_block ? GFP_KERNEL : GFP_ATOMIC;
967993
struct net *net = dev_net(idev->dev);
968994
struct inet6_ifaddr *ifa = NULL;
969-
struct rt6_info *rt;
995+
struct rt6_info *rt = NULL;
970996
struct in6_validator_info i6vi;
971-
unsigned int hash;
972997
int err = 0;
973998
int addr_type = ipv6_addr_type(addr);
974999

@@ -978,42 +1003,24 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
9781003
addr_type & IPV6_ADDR_LOOPBACK))
9791004
return ERR_PTR(-EADDRNOTAVAIL);
9801005

981-
rcu_read_lock_bh();
982-
983-
in6_dev_hold(idev);
984-
9851006
if (idev->dead) {
9861007
err = -ENODEV; /*XXX*/
987-
goto out2;
1008+
goto out;
9881009
}
9891010

9901011
if (idev->cnf.disable_ipv6) {
9911012
err = -EACCES;
992-
goto out2;
1013+
goto out;
9931014
}
9941015

9951016
i6vi.i6vi_addr = *addr;
9961017
i6vi.i6vi_dev = idev;
997-
rcu_read_unlock_bh();
998-
9991018
err = inet6addr_validator_notifier_call_chain(NETDEV_UP, &i6vi);
1000-
1001-
rcu_read_lock_bh();
10021019
err = notifier_to_errno(err);
1003-
if (err)
1004-
goto out2;
1005-
1006-
spin_lock(&addrconf_hash_lock);
1007-
1008-
/* Ignore adding duplicate addresses on an interface */
1009-
if (ipv6_chk_same_addr(dev_net(idev->dev), addr, idev->dev)) {
1010-
ADBG("ipv6_add_addr: already assigned\n");
1011-
err = -EEXIST;
1020+
if (err < 0)
10121021
goto out;
1013-
}
1014-
1015-
ifa = kzalloc(sizeof(struct inet6_ifaddr), GFP_ATOMIC);
10161022

1023+
ifa = kzalloc(sizeof(*ifa), gfp_flags);
10171024
if (!ifa) {
10181025
ADBG("ipv6_add_addr: malloc failed\n");
10191026
err = -ENOBUFS;
@@ -1023,6 +1030,7 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
10231030
rt = addrconf_dst_alloc(idev, addr, false);
10241031
if (IS_ERR(rt)) {
10251032
err = PTR_ERR(rt);
1033+
rt = NULL;
10261034
goto out;
10271035
}
10281036

@@ -1053,16 +1061,21 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
10531061
ifa->rt = rt;
10541062

10551063
ifa->idev = idev;
1064+
in6_dev_hold(idev);
1065+
10561066
/* For caller */
10571067
refcount_set(&ifa->refcnt, 1);
10581068

1059-
/* Add to big hash table */
1060-
hash = inet6_addr_hash(addr);
1069+
rcu_read_lock_bh();
10611070

1062-
hlist_add_head_rcu(&ifa->addr_lst, &inet6_addr_lst[hash]);
1063-
spin_unlock(&addrconf_hash_lock);
1071+
err = ipv6_add_addr_hash(idev->dev, ifa);
1072+
if (err < 0) {
1073+
rcu_read_unlock_bh();
1074+
goto out;
1075+
}
10641076

10651077
write_lock(&idev->lock);
1078+
10661079
/* Add to inet6_dev unicast addr list. */
10671080
ipv6_link_dev_addr(idev, ifa);
10681081

@@ -1073,21 +1086,23 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
10731086

10741087
in6_ifa_hold(ifa);
10751088
write_unlock(&idev->lock);
1076-
out2:
1089+
10771090
rcu_read_unlock_bh();
10781091

1079-
if (likely(err == 0))
1080-
inet6addr_notifier_call_chain(NETDEV_UP, ifa);
1081-
else {
1082-
kfree(ifa);
1083-
in6_dev_put(idev);
1092+
inet6addr_notifier_call_chain(NETDEV_UP, ifa);
1093+
out:
1094+
if (unlikely(err < 0)) {
1095+
if (rt)
1096+
ip6_rt_put(rt);
1097+
if (ifa) {
1098+
if (ifa->idev)
1099+
in6_dev_put(ifa->idev);
1100+
kfree(ifa);
1101+
}
10841102
ifa = ERR_PTR(err);
10851103
}
10861104

10871105
return ifa;
1088-
out:
1089-
spin_unlock(&addrconf_hash_lock);
1090-
goto out2;
10911106
}
10921107

10931108
enum cleanup_prefix_rt_t {
@@ -1334,7 +1349,7 @@ static int ipv6_create_tempaddr(struct inet6_ifaddr *ifp, struct inet6_ifaddr *i
13341349

13351350
ift = ipv6_add_addr(idev, &addr, NULL, tmp_plen,
13361351
ipv6_addr_scope(&addr), addr_flags,
1337-
tmp_valid_lft, tmp_prefered_lft);
1352+
tmp_valid_lft, tmp_prefered_lft, true);
13381353
if (IS_ERR(ift)) {
13391354
in6_ifa_put(ifp);
13401355
in6_dev_put(idev);
@@ -2018,7 +2033,7 @@ void addrconf_dad_failure(struct inet6_ifaddr *ifp)
20182033

20192034
ifp2 = ipv6_add_addr(idev, &new_addr, NULL, pfxlen,
20202035
scope, flags, valid_lft,
2021-
preferred_lft);
2036+
preferred_lft, false);
20222037
if (IS_ERR(ifp2))
20232038
goto lock_errdad;
20242039

@@ -2476,7 +2491,7 @@ int addrconf_prefix_rcv_add_addr(struct net *net, struct net_device *dev,
24762491
pinfo->prefix_len,
24772492
addr_type&IPV6_ADDR_SCOPE_MASK,
24782493
addr_flags, valid_lft,
2479-
prefered_lft);
2494+
prefered_lft, false);
24802495

24812496
if (IS_ERR_OR_NULL(ifp))
24822497
return -1;
@@ -2845,7 +2860,7 @@ static int inet6_addr_add(struct net *net, int ifindex,
28452860
}
28462861

28472862
ifp = ipv6_add_addr(idev, pfx, peer_pfx, plen, scope, ifa_flags,
2848-
valid_lft, prefered_lft);
2863+
valid_lft, prefered_lft, true);
28492864

28502865
if (!IS_ERR(ifp)) {
28512866
if (!(ifa_flags & IFA_F_NOPREFIXROUTE)) {
@@ -2960,7 +2975,8 @@ static void add_addr(struct inet6_dev *idev, const struct in6_addr *addr,
29602975

29612976
ifp = ipv6_add_addr(idev, addr, NULL, plen,
29622977
scope, IFA_F_PERMANENT,
2963-
INFINITY_LIFE_TIME, INFINITY_LIFE_TIME);
2978+
INFINITY_LIFE_TIME, INFINITY_LIFE_TIME,
2979+
true);
29642980
if (!IS_ERR(ifp)) {
29652981
spin_lock_bh(&ifp->lock);
29662982
ifp->flags &= ~IFA_F_TENTATIVE;
@@ -3060,7 +3076,7 @@ void addrconf_add_linklocal(struct inet6_dev *idev,
30603076
#endif
30613077

30623078
ifp = ipv6_add_addr(idev, addr, NULL, 64, IFA_LINK, addr_flags,
3063-
INFINITY_LIFE_TIME, INFINITY_LIFE_TIME);
3079+
INFINITY_LIFE_TIME, INFINITY_LIFE_TIME, true);
30643080
if (!IS_ERR(ifp)) {
30653081
addrconf_prefix_route(&ifp->addr, ifp->prefix_len, idev->dev, 0, 0);
30663082
addrconf_dad_start(ifp);

0 commit comments

Comments
 (0)