Skip to content

Commit

Permalink
net/ipv6: Add anycast addresses to a global hashtable
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Jeff Barnhill authored and David S. Miller committed Nov 3, 2018
1 parent 7b900ea commit 2384d02
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 4 deletions.
2 changes: 2 additions & 0 deletions include/net/addrconf.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions include/net/if_inet6.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions net/ipv6/af_inet6.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -1091,6 +1094,8 @@ static int __init inet6_init(void)
ipv6_exthdrs_fail:
addrconf_cleanup();
addrconf_fail:
ipv6_anycast_cleanup();
ipv6_anycast_fail:
ip6_flowlabel_cleanup();
ip6_flowlabel_fail:
ndisc_late_cleanup();
Expand Down
80 changes: 76 additions & 4 deletions net/ipv6/anycast.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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);
}
}

Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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

0 comments on commit 2384d02

Please sign in to comment.