Commit 2384d025 authored by Jeff Barnhill's avatar Jeff Barnhill Committed by David S. Miller

net/ipv6: Add anycast addresses to a global hashtable

icmp6_send() function is expensive on systems with a large number of
interfaces. Every time it’s called, it has to verify that the source
address does not correspond to an existing anycast address by looping
through every device and every anycast address on the device.  This can
result in significant delays for a CPU when there are a large number of
neighbors and ND timers are frequently timing out and calling
neigh_invalidate().

Add anycast addresses to a global hashtable to allow quick searching for
matching anycast addresses.  This is based on inet6_addr_lst in addrconf.c.
Signed-off-by: default avatarJeff Barnhill <0xeffeff@gmail.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 7b900ead
...@@ -317,6 +317,8 @@ bool ipv6_chk_acast_addr(struct net *net, struct net_device *dev, ...@@ -317,6 +317,8 @@ bool ipv6_chk_acast_addr(struct net *net, struct net_device *dev,
const struct in6_addr *addr); const struct in6_addr *addr);
bool ipv6_chk_acast_addr_src(struct net *net, struct net_device *dev, bool ipv6_chk_acast_addr_src(struct net *net, struct net_device *dev,
const struct in6_addr *addr); const struct in6_addr *addr);
int ipv6_anycast_init(void);
void ipv6_anycast_cleanup(void);
/* Device notifier */ /* Device notifier */
int register_inet6addr_notifier(struct notifier_block *nb); int register_inet6addr_notifier(struct notifier_block *nb);
......
...@@ -146,10 +146,12 @@ struct ifacaddr6 { ...@@ -146,10 +146,12 @@ struct ifacaddr6 {
struct in6_addr aca_addr; struct in6_addr aca_addr;
struct fib6_info *aca_rt; struct fib6_info *aca_rt;
struct ifacaddr6 *aca_next; struct ifacaddr6 *aca_next;
struct hlist_node aca_addr_lst;
int aca_users; int aca_users;
refcount_t aca_refcnt; refcount_t aca_refcnt;
unsigned long aca_cstamp; unsigned long aca_cstamp;
unsigned long aca_tstamp; unsigned long aca_tstamp;
struct rcu_head rcu;
}; };
#define IFA_HOST IPV6_ADDR_LOOPBACK #define IFA_HOST IPV6_ADDR_LOOPBACK
......
...@@ -1001,6 +1001,9 @@ static int __init inet6_init(void) ...@@ -1001,6 +1001,9 @@ static int __init inet6_init(void)
err = ip6_flowlabel_init(); err = ip6_flowlabel_init();
if (err) if (err)
goto ip6_flowlabel_fail; goto ip6_flowlabel_fail;
err = ipv6_anycast_init();
if (err)
goto ipv6_anycast_fail;
err = addrconf_init(); err = addrconf_init();
if (err) if (err)
goto addrconf_fail; goto addrconf_fail;
...@@ -1091,6 +1094,8 @@ static int __init inet6_init(void) ...@@ -1091,6 +1094,8 @@ static int __init inet6_init(void)
ipv6_exthdrs_fail: ipv6_exthdrs_fail:
addrconf_cleanup(); addrconf_cleanup();
addrconf_fail: addrconf_fail:
ipv6_anycast_cleanup();
ipv6_anycast_fail:
ip6_flowlabel_cleanup(); ip6_flowlabel_cleanup();
ip6_flowlabel_fail: ip6_flowlabel_fail:
ndisc_late_cleanup(); ndisc_late_cleanup();
......
...@@ -44,8 +44,22 @@ ...@@ -44,8 +44,22 @@
#include <net/checksum.h> #include <net/checksum.h>
#define IN6_ADDR_HSIZE_SHIFT 8
#define IN6_ADDR_HSIZE BIT(IN6_ADDR_HSIZE_SHIFT)
/* anycast address hash table
*/
static struct hlist_head inet6_acaddr_lst[IN6_ADDR_HSIZE];
static DEFINE_SPINLOCK(acaddr_hash_lock);
static int ipv6_dev_ac_dec(struct net_device *dev, const struct in6_addr *addr); static int ipv6_dev_ac_dec(struct net_device *dev, const struct in6_addr *addr);
static u32 inet6_acaddr_hash(struct net *net, const struct in6_addr *addr)
{
u32 val = ipv6_addr_hash(addr) ^ net_hash_mix(net);
return hash_32(val, IN6_ADDR_HSIZE_SHIFT);
}
/* /*
* socket join an anycast group * socket join an anycast group
*/ */
...@@ -204,16 +218,39 @@ void ipv6_sock_ac_close(struct sock *sk) ...@@ -204,16 +218,39 @@ void ipv6_sock_ac_close(struct sock *sk)
rtnl_unlock(); rtnl_unlock();
} }
static void ipv6_add_acaddr_hash(struct net *net, struct ifacaddr6 *aca)
{
unsigned int hash = inet6_acaddr_hash(net, &aca->aca_addr);
spin_lock(&acaddr_hash_lock);
hlist_add_head_rcu(&aca->aca_addr_lst, &inet6_acaddr_lst[hash]);
spin_unlock(&acaddr_hash_lock);
}
static void ipv6_del_acaddr_hash(struct ifacaddr6 *aca)
{
spin_lock(&acaddr_hash_lock);
hlist_del_init_rcu(&aca->aca_addr_lst);
spin_unlock(&acaddr_hash_lock);
}
static void aca_get(struct ifacaddr6 *aca) static void aca_get(struct ifacaddr6 *aca)
{ {
refcount_inc(&aca->aca_refcnt); refcount_inc(&aca->aca_refcnt);
} }
static void aca_free_rcu(struct rcu_head *h)
{
struct ifacaddr6 *aca = container_of(h, struct ifacaddr6, rcu);
fib6_info_release(aca->aca_rt);
kfree(aca);
}
static void aca_put(struct ifacaddr6 *ac) static void aca_put(struct ifacaddr6 *ac)
{ {
if (refcount_dec_and_test(&ac->aca_refcnt)) { if (refcount_dec_and_test(&ac->aca_refcnt)) {
fib6_info_release(ac->aca_rt); call_rcu(&ac->rcu, aca_free_rcu);
kfree(ac);
} }
} }
...@@ -229,6 +266,7 @@ static struct ifacaddr6 *aca_alloc(struct fib6_info *f6i, ...@@ -229,6 +266,7 @@ static struct ifacaddr6 *aca_alloc(struct fib6_info *f6i,
aca->aca_addr = *addr; aca->aca_addr = *addr;
fib6_info_hold(f6i); fib6_info_hold(f6i);
aca->aca_rt = f6i; aca->aca_rt = f6i;
INIT_HLIST_NODE(&aca->aca_addr_lst);
aca->aca_users = 1; aca->aca_users = 1;
/* aca_tstamp should be updated upon changes */ /* aca_tstamp should be updated upon changes */
aca->aca_cstamp = aca->aca_tstamp = jiffies; aca->aca_cstamp = aca->aca_tstamp = jiffies;
...@@ -285,6 +323,8 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr) ...@@ -285,6 +323,8 @@ int __ipv6_dev_ac_inc(struct inet6_dev *idev, const struct in6_addr *addr)
aca_get(aca); aca_get(aca);
write_unlock_bh(&idev->lock); write_unlock_bh(&idev->lock);
ipv6_add_acaddr_hash(net, aca);
ip6_ins_rt(net, f6i); ip6_ins_rt(net, f6i);
addrconf_join_solict(idev->dev, &aca->aca_addr); addrconf_join_solict(idev->dev, &aca->aca_addr);
...@@ -325,6 +365,7 @@ int __ipv6_dev_ac_dec(struct inet6_dev *idev, const struct in6_addr *addr) ...@@ -325,6 +365,7 @@ int __ipv6_dev_ac_dec(struct inet6_dev *idev, const struct in6_addr *addr)
else else
idev->ac_list = aca->aca_next; idev->ac_list = aca->aca_next;
write_unlock_bh(&idev->lock); write_unlock_bh(&idev->lock);
ipv6_del_acaddr_hash(aca);
addrconf_leave_solict(idev, &aca->aca_addr); addrconf_leave_solict(idev, &aca->aca_addr);
ip6_del_rt(dev_net(idev->dev), aca->aca_rt); ip6_del_rt(dev_net(idev->dev), aca->aca_rt);
...@@ -352,6 +393,8 @@ void ipv6_ac_destroy_dev(struct inet6_dev *idev) ...@@ -352,6 +393,8 @@ void ipv6_ac_destroy_dev(struct inet6_dev *idev)
idev->ac_list = aca->aca_next; idev->ac_list = aca->aca_next;
write_unlock_bh(&idev->lock); write_unlock_bh(&idev->lock);
ipv6_del_acaddr_hash(aca);
addrconf_leave_solict(idev, &aca->aca_addr); addrconf_leave_solict(idev, &aca->aca_addr);
ip6_del_rt(dev_net(idev->dev), aca->aca_rt); ip6_del_rt(dev_net(idev->dev), aca->aca_rt);
...@@ -390,17 +433,25 @@ static bool ipv6_chk_acast_dev(struct net_device *dev, const struct in6_addr *ad ...@@ -390,17 +433,25 @@ static bool ipv6_chk_acast_dev(struct net_device *dev, const struct in6_addr *ad
bool ipv6_chk_acast_addr(struct net *net, struct net_device *dev, bool ipv6_chk_acast_addr(struct net *net, struct net_device *dev,
const struct in6_addr *addr) const struct in6_addr *addr)
{ {
unsigned int hash = inet6_acaddr_hash(net, addr);
struct net_device *nh_dev;
struct ifacaddr6 *aca;
bool found = false; bool found = false;
rcu_read_lock(); rcu_read_lock();
if (dev) if (dev)
found = ipv6_chk_acast_dev(dev, addr); found = ipv6_chk_acast_dev(dev, addr);
else else
for_each_netdev_rcu(net, dev) hlist_for_each_entry_rcu(aca, &inet6_acaddr_lst[hash],
if (ipv6_chk_acast_dev(dev, addr)) { aca_addr_lst) {
nh_dev = fib6_info_nh_dev(aca->aca_rt);
if (!nh_dev || !net_eq(dev_net(nh_dev), net))
continue;
if (ipv6_addr_equal(&aca->aca_addr, addr)) {
found = true; found = true;
break; break;
} }
}
rcu_read_unlock(); rcu_read_unlock();
return found; return found;
} }
...@@ -539,4 +590,25 @@ void ac6_proc_exit(struct net *net) ...@@ -539,4 +590,25 @@ void ac6_proc_exit(struct net *net)
{ {
remove_proc_entry("anycast6", net->proc_net); remove_proc_entry("anycast6", net->proc_net);
} }
/* Init / cleanup code
*/
int __init ipv6_anycast_init(void)
{
int i;
for (i = 0; i < IN6_ADDR_HSIZE; i++)
INIT_HLIST_HEAD(&inet6_acaddr_lst[i]);
return 0;
}
void ipv6_anycast_cleanup(void)
{
int i;
spin_lock(&acaddr_hash_lock);
for (i = 0; i < IN6_ADDR_HSIZE; i++)
WARN_ON(!hlist_empty(&inet6_acaddr_lst[i]));
spin_unlock(&acaddr_hash_lock);
}
#endif #endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment