Skip to content

Commit 2384d02

Browse files
Jeff Barnhilldavem330
authored andcommitted
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 <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 7b900ea commit 2384d02

File tree

4 files changed

+85
-4
lines changed

4 files changed

+85
-4
lines changed

include/net/addrconf.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,8 @@ bool ipv6_chk_acast_addr(struct net *net, struct net_device *dev,
317317
const struct in6_addr *addr);
318318
bool ipv6_chk_acast_addr_src(struct net *net, struct net_device *dev,
319319
const struct in6_addr *addr);
320+
int ipv6_anycast_init(void);
321+
void ipv6_anycast_cleanup(void);
320322

321323
/* Device notifier */
322324
int register_inet6addr_notifier(struct notifier_block *nb);

include/net/if_inet6.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,12 @@ struct ifacaddr6 {
146146
struct in6_addr aca_addr;
147147
struct fib6_info *aca_rt;
148148
struct ifacaddr6 *aca_next;
149+
struct hlist_node aca_addr_lst;
149150
int aca_users;
150151
refcount_t aca_refcnt;
151152
unsigned long aca_cstamp;
152153
unsigned long aca_tstamp;
154+
struct rcu_head rcu;
153155
};
154156

155157
#define IFA_HOST IPV6_ADDR_LOOPBACK

net/ipv6/af_inet6.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1001,6 +1001,9 @@ static int __init inet6_init(void)
10011001
err = ip6_flowlabel_init();
10021002
if (err)
10031003
goto ip6_flowlabel_fail;
1004+
err = ipv6_anycast_init();
1005+
if (err)
1006+
goto ipv6_anycast_fail;
10041007
err = addrconf_init();
10051008
if (err)
10061009
goto addrconf_fail;
@@ -1091,6 +1094,8 @@ static int __init inet6_init(void)
10911094
ipv6_exthdrs_fail:
10921095
addrconf_cleanup();
10931096
addrconf_fail:
1097+
ipv6_anycast_cleanup();
1098+
ipv6_anycast_fail:
10941099
ip6_flowlabel_cleanup();
10951100
ip6_flowlabel_fail:
10961101
ndisc_late_cleanup();

net/ipv6/anycast.c

Lines changed: 76 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,22 @@
4444

4545
#include <net/checksum.h>
4646

47+
#define IN6_ADDR_HSIZE_SHIFT 8
48+
#define IN6_ADDR_HSIZE BIT(IN6_ADDR_HSIZE_SHIFT)
49+
/* anycast address hash table
50+
*/
51+
static struct hlist_head inet6_acaddr_lst[IN6_ADDR_HSIZE];
52+
static DEFINE_SPINLOCK(acaddr_hash_lock);
53+
4754
static int ipv6_dev_ac_dec(struct net_device *dev, const struct in6_addr *addr);
4855

56+
static u32 inet6_acaddr_hash(struct net *net, const struct in6_addr *addr)
57+
{
58+
u32 val = ipv6_addr_hash(addr) ^ net_hash_mix(net);
59+
60+
return hash_32(val, IN6_ADDR_HSIZE_SHIFT);
61+
}
62+
4963
/*
5064
* socket join an anycast group
5165
*/
@@ -204,16 +218,39 @@ void ipv6_sock_ac_close(struct sock *sk)
204218
rtnl_unlock();
205219
}
206220

221+
static void ipv6_add_acaddr_hash(struct net *net, struct ifacaddr6 *aca)
222+
{
223+
unsigned int hash = inet6_acaddr_hash(net, &aca->aca_addr);
224+
225+
spin_lock(&acaddr_hash_lock);
226+
hlist_add_head_rcu(&aca->aca_addr_lst, &inet6_acaddr_lst[hash]);
227+
spin_unlock(&acaddr_hash_lock);
228+
}
229+
230+
static void ipv6_del_acaddr_hash(struct ifacaddr6 *aca)
231+
{
232+
spin_lock(&acaddr_hash_lock);
233+
hlist_del_init_rcu(&aca->aca_addr_lst);
234+
spin_unlock(&acaddr_hash_lock);
235+
}
236+
207237
static void aca_get(struct ifacaddr6 *aca)
208238
{
209239
refcount_inc(&aca->aca_refcnt);
210240
}
211241

242+
static void aca_free_rcu(struct rcu_head *h)
243+
{
244+
struct ifacaddr6 *aca = container_of(h, struct ifacaddr6, rcu);
245+
246+
fib6_info_release(aca->aca_rt);
247+
kfree(aca);
248+
}
249+
212250
static void aca_put(struct ifacaddr6 *ac)
213251
{
214252
if (refcount_dec_and_test(&ac->aca_refcnt)) {
215-
fib6_info_release(ac->aca_rt);
216-
kfree(ac);
253+
call_rcu(&ac->rcu, aca_free_rcu);
217254
}
218255
}
219256

@@ -229,6 +266,7 @@ static struct ifacaddr6 *aca_alloc(struct fib6_info *f6i,
229266
aca->aca_addr = *addr;
230267
fib6_info_hold(f6i);
231268
aca->aca_rt = f6i;
269+
INIT_HLIST_NODE(&aca->aca_addr_lst);
232270
aca->aca_users = 1;
233271
/* aca_tstamp should be updated upon changes */
234272
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)
285323
aca_get(aca);
286324
write_unlock_bh(&idev->lock);
287325

326+
ipv6_add_acaddr_hash(net, aca);
327+
288328
ip6_ins_rt(net, f6i);
289329

290330
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)
325365
else
326366
idev->ac_list = aca->aca_next;
327367
write_unlock_bh(&idev->lock);
368+
ipv6_del_acaddr_hash(aca);
328369
addrconf_leave_solict(idev, &aca->aca_addr);
329370

330371
ip6_del_rt(dev_net(idev->dev), aca->aca_rt);
@@ -352,6 +393,8 @@ void ipv6_ac_destroy_dev(struct inet6_dev *idev)
352393
idev->ac_list = aca->aca_next;
353394
write_unlock_bh(&idev->lock);
354395

396+
ipv6_del_acaddr_hash(aca);
397+
355398
addrconf_leave_solict(idev, &aca->aca_addr);
356399

357400
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
390433
bool ipv6_chk_acast_addr(struct net *net, struct net_device *dev,
391434
const struct in6_addr *addr)
392435
{
436+
unsigned int hash = inet6_acaddr_hash(net, addr);
437+
struct net_device *nh_dev;
438+
struct ifacaddr6 *aca;
393439
bool found = false;
394440

395441
rcu_read_lock();
396442
if (dev)
397443
found = ipv6_chk_acast_dev(dev, addr);
398444
else
399-
for_each_netdev_rcu(net, dev)
400-
if (ipv6_chk_acast_dev(dev, addr)) {
445+
hlist_for_each_entry_rcu(aca, &inet6_acaddr_lst[hash],
446+
aca_addr_lst) {
447+
nh_dev = fib6_info_nh_dev(aca->aca_rt);
448+
if (!nh_dev || !net_eq(dev_net(nh_dev), net))
449+
continue;
450+
if (ipv6_addr_equal(&aca->aca_addr, addr)) {
401451
found = true;
402452
break;
403453
}
454+
}
404455
rcu_read_unlock();
405456
return found;
406457
}
@@ -539,4 +590,25 @@ void ac6_proc_exit(struct net *net)
539590
{
540591
remove_proc_entry("anycast6", net->proc_net);
541592
}
593+
594+
/* Init / cleanup code
595+
*/
596+
int __init ipv6_anycast_init(void)
597+
{
598+
int i;
599+
600+
for (i = 0; i < IN6_ADDR_HSIZE; i++)
601+
INIT_HLIST_HEAD(&inet6_acaddr_lst[i]);
602+
return 0;
603+
}
604+
605+
void ipv6_anycast_cleanup(void)
606+
{
607+
int i;
608+
609+
spin_lock(&acaddr_hash_lock);
610+
for (i = 0; i < IN6_ADDR_HSIZE; i++)
611+
WARN_ON(!hlist_empty(&inet6_acaddr_lst[i]));
612+
spin_unlock(&acaddr_hash_lock);
613+
}
542614
#endif

0 commit comments

Comments
 (0)