|
@@ -1217,6 +1217,57 @@ static void igmp_group_added(struct ip_mc_list *im)
|
|
|
* Multicast list managers
|
|
|
*/
|
|
|
|
|
|
+static u32 ip_mc_hash(const struct ip_mc_list *im)
|
|
|
+{
|
|
|
+ return hash_32((u32)im->multiaddr, MC_HASH_SZ_LOG);
|
|
|
+}
|
|
|
+
|
|
|
+static void ip_mc_hash_add(struct in_device *in_dev,
|
|
|
+ struct ip_mc_list *im)
|
|
|
+{
|
|
|
+ struct ip_mc_list __rcu **mc_hash;
|
|
|
+ u32 hash;
|
|
|
+
|
|
|
+ mc_hash = rtnl_dereference(in_dev->mc_hash);
|
|
|
+ if (mc_hash) {
|
|
|
+ hash = ip_mc_hash(im);
|
|
|
+ im->next_hash = rtnl_dereference(mc_hash[hash]);
|
|
|
+ rcu_assign_pointer(mc_hash[hash], im);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* do not use a hash table for small number of items */
|
|
|
+ if (in_dev->mc_count < 4)
|
|
|
+ return;
|
|
|
+
|
|
|
+ mc_hash = kzalloc(sizeof(struct ip_mc_list *) << MC_HASH_SZ_LOG,
|
|
|
+ GFP_KERNEL);
|
|
|
+ if (!mc_hash)
|
|
|
+ return;
|
|
|
+
|
|
|
+ for_each_pmc_rtnl(in_dev, im) {
|
|
|
+ hash = ip_mc_hash(im);
|
|
|
+ im->next_hash = rtnl_dereference(mc_hash[hash]);
|
|
|
+ RCU_INIT_POINTER(mc_hash[hash], im);
|
|
|
+ }
|
|
|
+
|
|
|
+ rcu_assign_pointer(in_dev->mc_hash, mc_hash);
|
|
|
+}
|
|
|
+
|
|
|
+static void ip_mc_hash_remove(struct in_device *in_dev,
|
|
|
+ struct ip_mc_list *im)
|
|
|
+{
|
|
|
+ struct ip_mc_list __rcu **mc_hash = rtnl_dereference(in_dev->mc_hash);
|
|
|
+ struct ip_mc_list *aux;
|
|
|
+
|
|
|
+ if (!mc_hash)
|
|
|
+ return;
|
|
|
+ mc_hash += ip_mc_hash(im);
|
|
|
+ while ((aux = rtnl_dereference(*mc_hash)) != im)
|
|
|
+ mc_hash = &aux->next_hash;
|
|
|
+ *mc_hash = im->next_hash;
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
/*
|
|
|
* A socket has joined a multicast group on device dev.
|
|
@@ -1258,6 +1309,8 @@ void ip_mc_inc_group(struct in_device *in_dev, __be32 addr)
|
|
|
in_dev->mc_count++;
|
|
|
rcu_assign_pointer(in_dev->mc_list, im);
|
|
|
|
|
|
+ ip_mc_hash_add(in_dev, im);
|
|
|
+
|
|
|
#ifdef CONFIG_IP_MULTICAST
|
|
|
igmpv3_del_delrec(in_dev, im->multiaddr);
|
|
|
#endif
|
|
@@ -1314,6 +1367,7 @@ void ip_mc_dec_group(struct in_device *in_dev, __be32 addr)
|
|
|
ip = &i->next_rcu) {
|
|
|
if (i->multiaddr == addr) {
|
|
|
if (--i->users == 0) {
|
|
|
+ ip_mc_hash_remove(in_dev, i);
|
|
|
*ip = i->next_rcu;
|
|
|
in_dev->mc_count--;
|
|
|
igmp_group_dropped(i);
|
|
@@ -2321,12 +2375,25 @@ void ip_mc_drop_socket(struct sock *sk)
|
|
|
int ip_check_mc_rcu(struct in_device *in_dev, __be32 mc_addr, __be32 src_addr, u16 proto)
|
|
|
{
|
|
|
struct ip_mc_list *im;
|
|
|
+ struct ip_mc_list __rcu **mc_hash;
|
|
|
struct ip_sf_list *psf;
|
|
|
int rv = 0;
|
|
|
|
|
|
- for_each_pmc_rcu(in_dev, im) {
|
|
|
- if (im->multiaddr == mc_addr)
|
|
|
- break;
|
|
|
+ mc_hash = rcu_dereference(in_dev->mc_hash);
|
|
|
+ if (mc_hash) {
|
|
|
+ u32 hash = hash_32((u32)mc_addr, MC_HASH_SZ_LOG);
|
|
|
+
|
|
|
+ for (im = rcu_dereference(mc_hash[hash]);
|
|
|
+ im != NULL;
|
|
|
+ im = rcu_dereference(im->next_hash)) {
|
|
|
+ if (im->multiaddr == mc_addr)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ for_each_pmc_rcu(in_dev, im) {
|
|
|
+ if (im->multiaddr == mc_addr)
|
|
|
+ break;
|
|
|
+ }
|
|
|
}
|
|
|
if (im && proto == IPPROTO_IGMP) {
|
|
|
rv = 1;
|