aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDavid Ahern <dsahern@gmail.com>2017-10-18 12:56:52 -0400
committerDavid S. Miller <davem@davemloft.net>2017-10-20 08:15:07 -0400
commitf3d9832e56c48e4ca50bab0457e21bcaade4536d (patch)
tree925d3bc392d8c01e5b4f01f2043f6dd0a01d09fa
parent6b1f8edabad562dc0b2a6d59fa49061eddd91290 (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.c104
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
960static 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
978out:
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
962static struct inet6_ifaddr * 986static struct inet6_ifaddr *
963ipv6_add_addr(struct inet6_dev *idev, const struct in6_addr *addr, 987ipv6_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);
1076out2: 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); 1093out:
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;
1088out:
1089 spin_unlock(&addrconf_hash_lock);
1090 goto out2;
1091} 1106}
1092 1107
1093enum cleanup_prefix_rt_t { 1108enum 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);