.. | .. |
---|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
---|
1 | 2 | /* |
---|
2 | 3 | * Anycast support for IPv6 |
---|
3 | 4 | * Linux INET6 implementation |
---|
.. | .. |
---|
6 | 7 | * David L Stevens (dlstevens@us.ibm.com) |
---|
7 | 8 | * |
---|
8 | 9 | * based heavily on net/ipv6/mcast.c |
---|
9 | | - * |
---|
10 | | - * This program is free software; you can redistribute it and/or |
---|
11 | | - * modify it under the terms of the GNU General Public License |
---|
12 | | - * as published by the Free Software Foundation; either version |
---|
13 | | - * 2 of the License, or (at your option) any later version. |
---|
14 | 10 | */ |
---|
15 | 11 | |
---|
16 | 12 | #include <linux/capability.h> |
---|
.. | .. |
---|
44 | 40 | |
---|
45 | 41 | #include <net/checksum.h> |
---|
46 | 42 | |
---|
| 43 | +#define IN6_ADDR_HSIZE_SHIFT 8 |
---|
| 44 | +#define IN6_ADDR_HSIZE BIT(IN6_ADDR_HSIZE_SHIFT) |
---|
| 45 | +/* anycast address hash table |
---|
| 46 | + */ |
---|
| 47 | +static struct hlist_head inet6_acaddr_lst[IN6_ADDR_HSIZE]; |
---|
| 48 | +static DEFINE_SPINLOCK(acaddr_hash_lock); |
---|
| 49 | + |
---|
47 | 50 | static int ipv6_dev_ac_dec(struct net_device *dev, const struct in6_addr *addr); |
---|
| 51 | + |
---|
| 52 | +static u32 inet6_acaddr_hash(struct net *net, const struct in6_addr *addr) |
---|
| 53 | +{ |
---|
| 54 | + u32 val = ipv6_addr_hash(addr) ^ net_hash_mix(net); |
---|
| 55 | + |
---|
| 56 | + return hash_32(val, IN6_ADDR_HSIZE_SHIFT); |
---|
| 57 | +} |
---|
48 | 58 | |
---|
49 | 59 | /* |
---|
50 | 60 | * socket join an anycast group |
---|
.. | .. |
---|
211 | 221 | rtnl_unlock(); |
---|
212 | 222 | } |
---|
213 | 223 | |
---|
| 224 | +static void ipv6_add_acaddr_hash(struct net *net, struct ifacaddr6 *aca) |
---|
| 225 | +{ |
---|
| 226 | + unsigned int hash = inet6_acaddr_hash(net, &aca->aca_addr); |
---|
| 227 | + |
---|
| 228 | + spin_lock(&acaddr_hash_lock); |
---|
| 229 | + hlist_add_head_rcu(&aca->aca_addr_lst, &inet6_acaddr_lst[hash]); |
---|
| 230 | + spin_unlock(&acaddr_hash_lock); |
---|
| 231 | +} |
---|
| 232 | + |
---|
| 233 | +static void ipv6_del_acaddr_hash(struct ifacaddr6 *aca) |
---|
| 234 | +{ |
---|
| 235 | + spin_lock(&acaddr_hash_lock); |
---|
| 236 | + hlist_del_init_rcu(&aca->aca_addr_lst); |
---|
| 237 | + spin_unlock(&acaddr_hash_lock); |
---|
| 238 | +} |
---|
| 239 | + |
---|
214 | 240 | static void aca_get(struct ifacaddr6 *aca) |
---|
215 | 241 | { |
---|
216 | 242 | refcount_inc(&aca->aca_refcnt); |
---|
217 | 243 | } |
---|
218 | 244 | |
---|
| 245 | +static void aca_free_rcu(struct rcu_head *h) |
---|
| 246 | +{ |
---|
| 247 | + struct ifacaddr6 *aca = container_of(h, struct ifacaddr6, rcu); |
---|
| 248 | + |
---|
| 249 | + fib6_info_release(aca->aca_rt); |
---|
| 250 | + kfree(aca); |
---|
| 251 | +} |
---|
| 252 | + |
---|
219 | 253 | static void aca_put(struct ifacaddr6 *ac) |
---|
220 | 254 | { |
---|
221 | 255 | if (refcount_dec_and_test(&ac->aca_refcnt)) { |
---|
222 | | - fib6_info_release(ac->aca_rt); |
---|
223 | | - kfree(ac); |
---|
| 256 | + call_rcu(&ac->rcu, aca_free_rcu); |
---|
224 | 257 | } |
---|
225 | 258 | } |
---|
226 | 259 | |
---|
.. | .. |
---|
236 | 269 | aca->aca_addr = *addr; |
---|
237 | 270 | fib6_info_hold(f6i); |
---|
238 | 271 | aca->aca_rt = f6i; |
---|
| 272 | + INIT_HLIST_NODE(&aca->aca_addr_lst); |
---|
239 | 273 | aca->aca_users = 1; |
---|
240 | 274 | /* aca_tstamp should be updated upon changes */ |
---|
241 | 275 | aca->aca_cstamp = aca->aca_tstamp = jiffies; |
---|
.. | .. |
---|
292 | 326 | aca_get(aca); |
---|
293 | 327 | write_unlock_bh(&idev->lock); |
---|
294 | 328 | |
---|
| 329 | + ipv6_add_acaddr_hash(net, aca); |
---|
| 330 | + |
---|
295 | 331 | ip6_ins_rt(net, f6i); |
---|
296 | 332 | |
---|
297 | 333 | addrconf_join_solict(idev->dev, &aca->aca_addr); |
---|
.. | .. |
---|
332 | 368 | else |
---|
333 | 369 | idev->ac_list = aca->aca_next; |
---|
334 | 370 | write_unlock_bh(&idev->lock); |
---|
| 371 | + ipv6_del_acaddr_hash(aca); |
---|
335 | 372 | addrconf_leave_solict(idev, &aca->aca_addr); |
---|
336 | 373 | |
---|
337 | | - ip6_del_rt(dev_net(idev->dev), aca->aca_rt); |
---|
| 374 | + ip6_del_rt(dev_net(idev->dev), aca->aca_rt, false); |
---|
338 | 375 | |
---|
339 | 376 | aca_put(aca); |
---|
340 | 377 | return 0; |
---|
.. | .. |
---|
359 | 396 | idev->ac_list = aca->aca_next; |
---|
360 | 397 | write_unlock_bh(&idev->lock); |
---|
361 | 398 | |
---|
| 399 | + ipv6_del_acaddr_hash(aca); |
---|
| 400 | + |
---|
362 | 401 | addrconf_leave_solict(idev, &aca->aca_addr); |
---|
363 | 402 | |
---|
364 | | - ip6_del_rt(dev_net(idev->dev), aca->aca_rt); |
---|
| 403 | + ip6_del_rt(dev_net(idev->dev), aca->aca_rt, false); |
---|
365 | 404 | |
---|
366 | 405 | aca_put(aca); |
---|
367 | 406 | |
---|
.. | .. |
---|
397 | 436 | bool ipv6_chk_acast_addr(struct net *net, struct net_device *dev, |
---|
398 | 437 | const struct in6_addr *addr) |
---|
399 | 438 | { |
---|
| 439 | + struct net_device *nh_dev; |
---|
| 440 | + struct ifacaddr6 *aca; |
---|
400 | 441 | bool found = false; |
---|
401 | 442 | |
---|
402 | 443 | rcu_read_lock(); |
---|
403 | 444 | if (dev) |
---|
404 | 445 | found = ipv6_chk_acast_dev(dev, addr); |
---|
405 | | - else |
---|
406 | | - for_each_netdev_rcu(net, dev) |
---|
407 | | - if (ipv6_chk_acast_dev(dev, addr)) { |
---|
| 446 | + else { |
---|
| 447 | + unsigned int hash = inet6_acaddr_hash(net, addr); |
---|
| 448 | + |
---|
| 449 | + hlist_for_each_entry_rcu(aca, &inet6_acaddr_lst[hash], |
---|
| 450 | + aca_addr_lst) { |
---|
| 451 | + nh_dev = fib6_info_nh_dev(aca->aca_rt); |
---|
| 452 | + if (!nh_dev || !net_eq(dev_net(nh_dev), net)) |
---|
| 453 | + continue; |
---|
| 454 | + if (ipv6_addr_equal(&aca->aca_addr, addr)) { |
---|
408 | 455 | found = true; |
---|
409 | 456 | break; |
---|
410 | 457 | } |
---|
| 458 | + } |
---|
| 459 | + } |
---|
411 | 460 | rcu_read_unlock(); |
---|
412 | 461 | return found; |
---|
413 | 462 | } |
---|
.. | .. |
---|
547 | 596 | remove_proc_entry("anycast6", net->proc_net); |
---|
548 | 597 | } |
---|
549 | 598 | #endif |
---|
| 599 | + |
---|
| 600 | +/* Init / cleanup code |
---|
| 601 | + */ |
---|
| 602 | +int __init ipv6_anycast_init(void) |
---|
| 603 | +{ |
---|
| 604 | + int i; |
---|
| 605 | + |
---|
| 606 | + for (i = 0; i < IN6_ADDR_HSIZE; i++) |
---|
| 607 | + INIT_HLIST_HEAD(&inet6_acaddr_lst[i]); |
---|
| 608 | + return 0; |
---|
| 609 | +} |
---|
| 610 | + |
---|
| 611 | +void ipv6_anycast_cleanup(void) |
---|
| 612 | +{ |
---|
| 613 | + int i; |
---|
| 614 | + |
---|
| 615 | + spin_lock(&acaddr_hash_lock); |
---|
| 616 | + for (i = 0; i < IN6_ADDR_HSIZE; i++) |
---|
| 617 | + WARN_ON(!hlist_empty(&inet6_acaddr_lst[i])); |
---|
| 618 | + spin_unlock(&acaddr_hash_lock); |
---|
| 619 | +} |
---|