| .. | .. |
|---|
| 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 | +} |
|---|