mirror of
https://github.com/torvalds/linux.git
synced 2024-11-22 12:11:40 +00:00
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: Jeff Barnhill <0xeffeff@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
7b900ead6c
commit
2384d02520
@ -317,6 +317,8 @@ bool ipv6_chk_acast_addr(struct net *net, struct net_device *dev,
|
||||
const struct in6_addr *addr);
|
||||
bool ipv6_chk_acast_addr_src(struct net *net, struct net_device *dev,
|
||||
const struct in6_addr *addr);
|
||||
int ipv6_anycast_init(void);
|
||||
void ipv6_anycast_cleanup(void);
|
||||
|
||||
/* Device notifier */
|
||||
int register_inet6addr_notifier(struct notifier_block *nb);
|
||||
|
@ -146,10 +146,12 @@ struct ifacaddr6 {
|
||||
struct in6_addr aca_addr;
|
||||
struct fib6_info *aca_rt;
|
||||
struct ifacaddr6 *aca_next;
|
||||
struct hlist_node aca_addr_lst;
|
||||
int aca_users;
|
||||
refcount_t aca_refcnt;
|
||||
unsigned long aca_cstamp;
|
||||
unsigned long aca_tstamp;
|
||||
struct rcu_head rcu;
|
||||
};
|
||||
|
||||
#define IFA_HOST IPV6_ADDR_LOOPBACK
|
||||
|
@ -1001,6 +1001,9 @@ static int __init inet6_init(void)
|
||||
err = ip6_flowlabel_init();
|
||||
if (err)
|
||||
goto ip6_flowlabel_fail;
|
||||
err = ipv6_anycast_init();
|
||||
if (err)
|
||||
goto ipv6_anycast_fail;
|
||||
err = addrconf_init();
|
||||
if (err)
|
||||
goto addrconf_fail;
|
||||
@ -1091,6 +1094,8 @@ ipv6_frag_fail:
|
||||
ipv6_exthdrs_fail:
|
||||
addrconf_cleanup();
|
||||
addrconf_fail:
|
||||
ipv6_anycast_cleanup();
|
||||
ipv6_anycast_fail:
|
||||
ip6_flowlabel_cleanup();
|
||||
ip6_flowlabel_fail:
|
||||
ndisc_late_cleanup();
|
||||
|
@ -44,8 +44,22 @@
|
||||
|
||||
#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 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
|
||||
*/
|
||||
@ -204,16 +218,39 @@ void ipv6_sock_ac_close(struct sock *sk)
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (refcount_dec_and_test(&ac->aca_refcnt)) {
|
||||
fib6_info_release(ac->aca_rt);
|
||||
kfree(ac);
|
||||
call_rcu(&ac->rcu, aca_free_rcu);
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,6 +266,7 @@ static struct ifacaddr6 *aca_alloc(struct fib6_info *f6i,
|
||||
aca->aca_addr = *addr;
|
||||
fib6_info_hold(f6i);
|
||||
aca->aca_rt = f6i;
|
||||
INIT_HLIST_NODE(&aca->aca_addr_lst);
|
||||
aca->aca_users = 1;
|
||||
/* aca_tstamp should be updated upon changes */
|
||||
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)
|
||||
aca_get(aca);
|
||||
write_unlock_bh(&idev->lock);
|
||||
|
||||
ipv6_add_acaddr_hash(net, aca);
|
||||
|
||||
ip6_ins_rt(net, f6i);
|
||||
|
||||
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)
|
||||
else
|
||||
idev->ac_list = aca->aca_next;
|
||||
write_unlock_bh(&idev->lock);
|
||||
ipv6_del_acaddr_hash(aca);
|
||||
addrconf_leave_solict(idev, &aca->aca_addr);
|
||||
|
||||
ip6_del_rt(dev_net(idev->dev), aca->aca_rt);
|
||||
@ -352,6 +393,8 @@ void ipv6_ac_destroy_dev(struct inet6_dev *idev)
|
||||
idev->ac_list = aca->aca_next;
|
||||
write_unlock_bh(&idev->lock);
|
||||
|
||||
ipv6_del_acaddr_hash(aca);
|
||||
|
||||
addrconf_leave_solict(idev, &aca->aca_addr);
|
||||
|
||||
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
|
||||
bool ipv6_chk_acast_addr(struct net *net, struct net_device *dev,
|
||||
const struct in6_addr *addr)
|
||||
{
|
||||
unsigned int hash = inet6_acaddr_hash(net, addr);
|
||||
struct net_device *nh_dev;
|
||||
struct ifacaddr6 *aca;
|
||||
bool found = false;
|
||||
|
||||
rcu_read_lock();
|
||||
if (dev)
|
||||
found = ipv6_chk_acast_dev(dev, addr);
|
||||
else
|
||||
for_each_netdev_rcu(net, dev)
|
||||
if (ipv6_chk_acast_dev(dev, addr)) {
|
||||
hlist_for_each_entry_rcu(aca, &inet6_acaddr_lst[hash],
|
||||
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;
|
||||
break;
|
||||
}
|
||||
}
|
||||
rcu_read_unlock();
|
||||
return found;
|
||||
}
|
||||
@ -539,4 +590,25 @@ void ac6_proc_exit(struct net *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
|
||||
|
Loading…
Reference in New Issue
Block a user