|
@@ -44,8 +44,22 @@
|
|
|
|
|
|
#include <net/checksum.h>
|
|
#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 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
|
|
* socket join an anycast group
|
|
*/
|
|
*/
|
|
@@ -204,16 +218,39 @@ void ipv6_sock_ac_close(struct sock *sk)
|
|
rtnl_unlock();
|
|
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)
|
|
static void aca_get(struct ifacaddr6 *aca)
|
|
{
|
|
{
|
|
refcount_inc(&aca->aca_refcnt);
|
|
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)
|
|
static void aca_put(struct ifacaddr6 *ac)
|
|
{
|
|
{
|
|
if (refcount_dec_and_test(&ac->aca_refcnt)) {
|
|
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;
|
|
aca->aca_addr = *addr;
|
|
fib6_info_hold(f6i);
|
|
fib6_info_hold(f6i);
|
|
aca->aca_rt = f6i;
|
|
aca->aca_rt = f6i;
|
|
|
|
+ INIT_HLIST_NODE(&aca->aca_addr_lst);
|
|
aca->aca_users = 1;
|
|
aca->aca_users = 1;
|
|
/* aca_tstamp should be updated upon changes */
|
|
/* aca_tstamp should be updated upon changes */
|
|
aca->aca_cstamp = aca->aca_tstamp = jiffies;
|
|
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);
|
|
aca_get(aca);
|
|
write_unlock_bh(&idev->lock);
|
|
write_unlock_bh(&idev->lock);
|
|
|
|
|
|
|
|
+ ipv6_add_acaddr_hash(net, aca);
|
|
|
|
+
|
|
ip6_ins_rt(net, f6i);
|
|
ip6_ins_rt(net, f6i);
|
|
|
|
|
|
addrconf_join_solict(idev->dev, &aca->aca_addr);
|
|
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
|
|
else
|
|
idev->ac_list = aca->aca_next;
|
|
idev->ac_list = aca->aca_next;
|
|
write_unlock_bh(&idev->lock);
|
|
write_unlock_bh(&idev->lock);
|
|
|
|
+ ipv6_del_acaddr_hash(aca);
|
|
addrconf_leave_solict(idev, &aca->aca_addr);
|
|
addrconf_leave_solict(idev, &aca->aca_addr);
|
|
|
|
|
|
ip6_del_rt(dev_net(idev->dev), aca->aca_rt);
|
|
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;
|
|
idev->ac_list = aca->aca_next;
|
|
write_unlock_bh(&idev->lock);
|
|
write_unlock_bh(&idev->lock);
|
|
|
|
|
|
|
|
+ ipv6_del_acaddr_hash(aca);
|
|
|
|
+
|
|
addrconf_leave_solict(idev, &aca->aca_addr);
|
|
addrconf_leave_solict(idev, &aca->aca_addr);
|
|
|
|
|
|
ip6_del_rt(dev_net(idev->dev), aca->aca_rt);
|
|
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,
|
|
bool ipv6_chk_acast_addr(struct net *net, struct net_device *dev,
|
|
const struct in6_addr *addr)
|
|
const struct in6_addr *addr)
|
|
{
|
|
{
|
|
|
|
+ unsigned int hash = inet6_acaddr_hash(net, addr);
|
|
|
|
+ struct net_device *nh_dev;
|
|
|
|
+ struct ifacaddr6 *aca;
|
|
bool found = false;
|
|
bool found = false;
|
|
|
|
|
|
rcu_read_lock();
|
|
rcu_read_lock();
|
|
if (dev)
|
|
if (dev)
|
|
found = ipv6_chk_acast_dev(dev, addr);
|
|
found = ipv6_chk_acast_dev(dev, addr);
|
|
else
|
|
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;
|
|
found = true;
|
|
break;
|
|
break;
|
|
}
|
|
}
|
|
|
|
+ }
|
|
rcu_read_unlock();
|
|
rcu_read_unlock();
|
|
return found;
|
|
return found;
|
|
}
|
|
}
|
|
@@ -539,4 +590,25 @@ void ac6_proc_exit(struct net *net)
|
|
{
|
|
{
|
|
remove_proc_entry("anycast6", net->proc_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
|
|
#endif
|