diff options
author | David Ahern <dsahern@gmail.com> | 2017-10-18 12:56:52 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2017-10-20 08:15:07 -0400 |
commit | f3d9832e56c48e4ca50bab0457e21bcaade4536d (patch) | |
tree | 925d3bc392d8c01e5b4f01f2043f6dd0a01d09fa | |
parent | 6b1f8edabad562dc0b2a6d59fa49061eddd91290 (diff) |
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 3ad7d2468f79f ("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 3ad7d2468f79f 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>
-rw-r--r-- | net/ipv6/addrconf.c | 104 |
1 files changed, 60 insertions, 44 deletions
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c index 4603aa488f4f..a8d202b1b919 100644 --- a/net/ipv6/addrconf.c +++ b/net/ipv6/addrconf.c | |||
@@ -957,18 +957,43 @@ static u32 inet6_addr_hash(const struct in6_addr *addr) | |||
957 | return hash_32(ipv6_addr_hash(addr), IN6_ADDR_HSIZE_SHIFT); | 957 | return hash_32(ipv6_addr_hash(addr), IN6_ADDR_HSIZE_SHIFT); |
958 | } | 958 | } |
959 | 959 | ||
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 | |||
960 | /* On success it returns ifp with increased reference count */ | 984 | /* On success it returns ifp with increased reference count */ |
961 | 985 | ||
962 | static struct inet6_ifaddr * | 986 | static struct inet6_ifaddr * |
963 | ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, | 987 | ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, |
964 | const struct in6_addr *peer_addr, int pfxlen, | 988 | 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) | ||
966 | { | 991 | { |
992 | gfp_t gfp_flags = can_block ? GFP_KERNEL : GFP_ATOMIC; | ||
967 | struct net *net = dev_net(idev->dev); | 993 | struct net *net = dev_net(idev->dev); |
968 | struct inet6_ifaddr *ifa = NULL; | 994 | struct inet6_ifaddr *ifa = NULL; |
969 | struct rt6_info *rt; | 995 | struct rt6_info *rt = NULL; |
970 | struct in6_validator_info i6vi; | 996 | struct in6_validator_info i6vi; |
971 | unsigned int hash; | ||
972 | int err = 0; | 997 | int err = 0; |
973 | int addr_type = ipv6_addr_type(addr); | 998 | int addr_type = ipv6_addr_type(addr); |
974 | 999 | ||
@@ -978,42 +1003,24 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, | |||
978 | addr_type & IPV6_ADDR_LOOPBACK)) | 1003 | addr_type & IPV6_ADDR_LOOPBACK)) |
979 | return ERR_PTR(-EADDRNOTAVAIL); | 1004 | return ERR_PTR(-EADDRNOTAVAIL); |
980 | 1005 | ||
981 | rcu_read_lock_bh(); | ||
982 | |||
983 | in6_dev_hold(idev); | ||
984 | |||
985 | if (idev->dead) { | 1006 | if (idev->dead) { |
986 | err = -ENODEV; /*XXX*/ | 1007 | err = -ENODEV; /*XXX*/ |
987 | goto out2; | 1008 | goto out; |
988 | } | 1009 | } |
989 | 1010 | ||
990 | if (idev->cnf.disable_ipv6) { | 1011 | if (idev->cnf.disable_ipv6) { |
991 | err = -EACCES; | 1012 | err = -EACCES; |
992 | goto out2; | 1013 | goto out; |
993 | } | 1014 | } |
994 | 1015 | ||
995 | i6vi.i6vi_addr = *addr; | 1016 | i6vi.i6vi_addr = *addr; |
996 | i6vi.i6vi_dev = idev; | 1017 | i6vi.i6vi_dev = idev; |
997 | rcu_read_unlock_bh(); | ||
998 | |||
999 | err = inet6addr_validator_notifier_call_chain(NETDEV_UP, &i6vi); | 1018 | err = inet6addr_validator_notifier_call_chain(NETDEV_UP, &i6vi); |
1000 | |||
1001 | rcu_read_lock_bh(); | ||
1002 | err = notifier_to_errno(err); | 1019 | err = notifier_to_errno(err); |
1003 | if (err) | 1020 | if (err < 0) |
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; | ||
1012 | goto out; | 1021 | goto out; |
1013 | } | ||
1014 | |||
1015 | ifa = kzalloc(sizeof(struct inet6_ifaddr), GFP_ATOMIC); | ||
1016 | 1022 | ||
1023 | ifa = kzalloc(sizeof(*ifa), gfp_flags); | ||
1017 | if (!ifa) { | 1024 | if (!ifa) { |
1018 | ADBG("ipv6_add_addr: malloc failed\n"); | 1025 | ADBG("ipv6_add_addr: malloc failed\n"); |
1019 | err = -ENOBUFS; | 1026 | err = -ENOBUFS; |
@@ -1023,6 +1030,7 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, | |||
1023 | rt = addrconf_dst_alloc(idev, addr, false); | 1030 | rt = addrconf_dst_alloc(idev, addr, false); |
1024 | if (IS_ERR(rt)) { | 1031 | if (IS_ERR(rt)) { |
1025 | err = PTR_ERR(rt); | 1032 | err = PTR_ERR(rt); |
1033 | rt = NULL; | ||
1026 | goto out; | 1034 | goto out; |
1027 | } | 1035 | } |
1028 | 1036 | ||
@@ -1053,16 +1061,21 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, | |||
1053 | ifa->rt = rt; | 1061 | ifa->rt = rt; |
1054 | 1062 | ||
1055 | ifa->idev = idev; | 1063 | ifa->idev = idev; |
1064 | in6_dev_hold(idev); | ||
1065 | |||
1056 | /* For caller */ | 1066 | /* For caller */ |
1057 | refcount_set(&ifa->refcnt, 1); | 1067 | refcount_set(&ifa->refcnt, 1); |
1058 | 1068 | ||
1059 | /* Add to big hash table */ | 1069 | rcu_read_lock_bh(); |
1060 | hash = inet6_addr_hash(addr); | ||
1061 | 1070 | ||
1062 | hlist_add_head_rcu(&ifa->addr_lst, &inet6_addr_lst[hash]); | 1071 | err = ipv6_add_addr_hash(idev->dev, ifa); |
1063 | spin_unlock(&addrconf_hash_lock); | 1072 | if (err < 0) { |
1073 | rcu_read_unlock_bh(); | ||
1074 | goto out; | ||
1075 | } | ||
1064 | 1076 | ||
1065 | write_lock(&idev->lock); | 1077 | write_lock(&idev->lock); |
1078 | |||
1066 | /* Add to inet6_dev unicast addr list. */ | 1079 | /* Add to inet6_dev unicast addr list. */ |
1067 | ipv6_link_dev_addr(idev, ifa); | 1080 | ipv6_link_dev_addr(idev, ifa); |
1068 | 1081 | ||
@@ -1073,21 +1086,23 @@ ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, | |||
1073 | 1086 | ||
1074 | in6_ifa_hold(ifa); | 1087 | in6_ifa_hold(ifa); |
1075 | write_unlock(&idev->lock); | 1088 | write_unlock(&idev->lock); |
1076 | out2: | 1089 | |
1077 | rcu_read_unlock_bh(); | 1090 | rcu_read_unlock_bh(); |
1078 | 1091 | ||
1079 | if (likely(err == 0)) | 1092 | inet6addr_notifier_call_chain(NETDEV_UP, ifa); |
1080 | inet6addr_notifier_call_chain(NETDEV_UP, ifa); | 1093 | out: |
1081 | else { | 1094 | if (unlikely(err < 0)) { |
1082 | kfree(ifa); | 1095 | if (rt) |
1083 | in6_dev_put(idev); | 1096 | ip6_rt_put(rt); |
1097 | if (ifa) { | ||
1098 | if (ifa->idev) | ||
1099 | in6_dev_put(ifa->idev); | ||
1100 | kfree(ifa); | ||
1101 | } | ||
1084 | ifa = ERR_PTR(err); | 1102 | ifa = ERR_PTR(err); |
1085 | } | 1103 | } |
1086 | 1104 | ||
1087 | return ifa; | 1105 | return ifa; |
1088 | out: | ||
1089 | spin_unlock(&addrconf_hash_lock); | ||
1090 | goto out2; | ||
1091 | } | 1106 | } |
1092 | 1107 | ||
1093 | enum cleanup_prefix_rt_t { | 1108 | enum cleanup_prefix_rt_t { |
@@ -1334,7 +1349,7 @@ retry: | |||
1334 | 1349 | ||
1335 | ift = ipv6_add_addr(idev, &addr, NULL, tmp_plen, | 1350 | ift = ipv6_add_addr(idev, &addr, NULL, tmp_plen, |
1336 | ipv6_addr_scope(&addr), addr_flags, | 1351 | ipv6_addr_scope(&addr), addr_flags, |
1337 | tmp_valid_lft, tmp_prefered_lft); | 1352 | tmp_valid_lft, tmp_prefered_lft, true); |
1338 | if (IS_ERR(ift)) { | 1353 | if (IS_ERR(ift)) { |
1339 | in6_ifa_put(ifp); | 1354 | in6_ifa_put(ifp); |
1340 | in6_dev_put(idev); | 1355 | in6_dev_put(idev); |
@@ -2018,7 +2033,7 @@ void addrconf_dad_failure(struct inet6_ifaddr *ifp) | |||
2018 | 2033 | ||
2019 | ifp2 = ipv6_add_addr(idev, &new_addr, NULL, pfxlen, | 2034 | ifp2 = ipv6_add_addr(idev, &new_addr, NULL, pfxlen, |
2020 | scope, flags, valid_lft, | 2035 | scope, flags, valid_lft, |
2021 | preferred_lft); | 2036 | preferred_lft, false); |
2022 | if (IS_ERR(ifp2)) | 2037 | if (IS_ERR(ifp2)) |
2023 | goto lock_errdad; | 2038 | goto lock_errdad; |
2024 | 2039 | ||
@@ -2476,7 +2491,7 @@ int addrconf_prefix_rcv_add_addr(struct net *net, struct net_device *dev, | |||
2476 | pinfo->prefix_len, | 2491 | pinfo->prefix_len, |
2477 | addr_type&IPV6_ADDR_SCOPE_MASK, | 2492 | addr_type&IPV6_ADDR_SCOPE_MASK, |
2478 | addr_flags, valid_lft, | 2493 | addr_flags, valid_lft, |
2479 | prefered_lft); | 2494 | prefered_lft, false); |
2480 | 2495 | ||
2481 | if (IS_ERR_OR_NULL(ifp)) | 2496 | if (IS_ERR_OR_NULL(ifp)) |
2482 | return -1; | 2497 | return -1; |
@@ -2845,7 +2860,7 @@ static int inet6_addr_add(struct net *net, int ifindex, | |||
2845 | } | 2860 | } |
2846 | 2861 | ||
2847 | ifp = ipv6_add_addr(idev, pfx, peer_pfx, plen, scope, ifa_flags, | 2862 | ifp = ipv6_add_addr(idev, pfx, peer_pfx, plen, scope, ifa_flags, |
2848 | valid_lft, prefered_lft); | 2863 | valid_lft, prefered_lft, true); |
2849 | 2864 | ||
2850 | if (!IS_ERR(ifp)) { | 2865 | if (!IS_ERR(ifp)) { |
2851 | if (!(ifa_flags & IFA_F_NOPREFIXROUTE)) { | 2866 | if (!(ifa_flags & IFA_F_NOPREFIXROUTE)) { |
@@ -2960,7 +2975,8 @@ static void add_addr(struct inet6_dev *idev, const struct in6_addr *addr, | |||
2960 | 2975 | ||
2961 | ifp = ipv6_add_addr(idev, addr, NULL, plen, | 2976 | ifp = ipv6_add_addr(idev, addr, NULL, plen, |
2962 | scope, IFA_F_PERMANENT, | 2977 | scope, IFA_F_PERMANENT, |
2963 | INFINITY_LIFE_TIME, INFINITY_LIFE_TIME); | 2978 | INFINITY_LIFE_TIME, INFINITY_LIFE_TIME, |
2979 | true); | ||
2964 | if (!IS_ERR(ifp)) { | 2980 | if (!IS_ERR(ifp)) { |
2965 | spin_lock_bh(&ifp->lock); | 2981 | spin_lock_bh(&ifp->lock); |
2966 | ifp->flags &= ~IFA_F_TENTATIVE; | 2982 | ifp->flags &= ~IFA_F_TENTATIVE; |
@@ -3060,7 +3076,7 @@ void addrconf_add_linklocal(struct inet6_dev *idev, | |||
3060 | #endif | 3076 | #endif |
3061 | 3077 | ||
3062 | ifp = ipv6_add_addr(idev, addr, NULL, 64, IFA_LINK, addr_flags, | 3078 | 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); |
3064 | if (!IS_ERR(ifp)) { | 3080 | if (!IS_ERR(ifp)) { |
3065 | addrconf_prefix_route(&ifp->addr, ifp->prefix_len, idev->dev, 0, 0); | 3081 | addrconf_prefix_route(&ifp->addr, ifp->prefix_len, idev->dev, 0, 0); |
3066 | addrconf_dad_start(ifp); | 3082 | addrconf_dad_start(ifp); |