mcast-snoop: Add support to control Reports forwarding
[cascardo/ovs.git] / lib / mcast-snooping.c
1 /*
2  * Copyright (c) 2014 Red Hat, Inc.
3  *
4  * Based on mac-learning implementation.
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at:
9  *
10  *     http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #include <config.h>
20 #include "mcast-snooping.h"
21
22 #include <inttypes.h>
23 #include <stdlib.h>
24
25 #include "bitmap.h"
26 #include "byte-order.h"
27 #include "coverage.h"
28 #include "hash.h"
29 #include "list.h"
30 #include "poll-loop.h"
31 #include "timeval.h"
32 #include "entropy.h"
33 #include "unaligned.h"
34 #include "util.h"
35 #include "vlan-bitmap.h"
36 #include "openvswitch/vlog.h"
37
38 COVERAGE_DEFINE(mcast_snooping_learned);
39 COVERAGE_DEFINE(mcast_snooping_expired);
40
41 static struct mcast_port_bundle *
42 mcast_snooping_port_lookup(struct ovs_list *list, void *port);
43 static struct mcast_mrouter_bundle *
44 mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
45                               void *port)
46     OVS_REQ_RDLOCK(ms->rwlock);
47
48 bool
49 mcast_snooping_enabled(const struct mcast_snooping *ms)
50 {
51     return !!ms;
52 }
53
54 bool
55 mcast_snooping_flood_unreg(const struct mcast_snooping *ms)
56 {
57     return ms->flood_unreg;
58 }
59
60 bool
61 mcast_snooping_is_query(ovs_be16 igmp_type)
62 {
63     return igmp_type == htons(IGMP_HOST_MEMBERSHIP_QUERY);
64 }
65
66 bool
67 mcast_snooping_is_membership(ovs_be16 igmp_type)
68 {
69     switch (ntohs(igmp_type)) {
70     case IGMP_HOST_MEMBERSHIP_REPORT:
71     case IGMPV2_HOST_MEMBERSHIP_REPORT:
72     case IGMP_HOST_LEAVE_MESSAGE:
73         return true;
74     }
75     return false;
76 }
77
78 /* Returns the number of seconds since multicast group 'b' was learned in a
79  * port on 'ms'. */
80 int
81 mcast_bundle_age(const struct mcast_snooping *ms,
82                  const struct mcast_group_bundle *b)
83 {
84     time_t remaining = b->expires - time_now();
85     return ms->idle_time - remaining;
86 }
87
88 static uint32_t
89 mcast_table_hash(const struct mcast_snooping *ms, ovs_be32 grp_ip4,
90                  uint16_t vlan)
91 {
92     return hash_3words((OVS_FORCE uint32_t) grp_ip4, vlan, ms->secret);
93 }
94
95 static struct mcast_group_bundle *
96 mcast_group_bundle_from_lru_node(struct ovs_list *list)
97 {
98     return CONTAINER_OF(list, struct mcast_group_bundle, bundle_node);
99 }
100
101 static struct mcast_group *
102 mcast_group_from_lru_node(struct ovs_list *list)
103 {
104     return CONTAINER_OF(list, struct mcast_group, group_node);
105 }
106
107 /* Searches 'ms' for and returns an mcast group for destination address
108  * 'dip' in 'vlan'. */
109 struct mcast_group *
110 mcast_snooping_lookup(const struct mcast_snooping *ms, ovs_be32 dip,
111                       uint16_t vlan)
112     OVS_REQ_RDLOCK(ms->rwlock)
113 {
114     struct mcast_group *grp;
115     uint32_t hash;
116
117     hash = mcast_table_hash(ms, dip, vlan);
118     HMAP_FOR_EACH_WITH_HASH (grp, hmap_node, hash, &ms->table) {
119         if (grp->vlan == vlan && grp->ip4 == dip) {
120            return grp;
121         }
122     }
123     return NULL;
124 }
125
126 /* If the LRU list is not empty, stores the least-recently-used entry
127  * in '*e' and returns true.  Otherwise, if the LRU list is empty,
128  * stores NULL in '*e' and return false. */
129 static bool
130 group_get_lru(const struct mcast_snooping *ms, struct mcast_group **grp)
131     OVS_REQ_RDLOCK(ms->rwlock)
132 {
133     if (!list_is_empty(&ms->group_lru)) {
134         *grp = mcast_group_from_lru_node(ms->group_lru.next);
135         return true;
136     } else {
137         *grp = NULL;
138         return false;
139     }
140 }
141
142 static unsigned int
143 normalize_idle_time(unsigned int idle_time)
144 {
145     return (idle_time < 15 ? 15
146             : idle_time > 3600 ? 3600
147             : idle_time);
148 }
149
150 /* Creates and returns a new mcast table with an initial mcast aging
151  * timeout of MCAST_ENTRY_DEFAULT_IDLE_TIME seconds and an initial maximum of
152  * MCAST_DEFAULT_MAX entries. */
153 struct mcast_snooping *
154 mcast_snooping_create(void)
155 {
156     struct mcast_snooping *ms;
157
158     ms = xmalloc(sizeof *ms);
159     hmap_init(&ms->table);
160     list_init(&ms->group_lru);
161     list_init(&ms->mrouter_lru);
162     list_init(&ms->fport_list);
163     list_init(&ms->rport_list);
164     ms->secret = random_uint32();
165     ms->idle_time = MCAST_ENTRY_DEFAULT_IDLE_TIME;
166     ms->max_entries = MCAST_DEFAULT_MAX_ENTRIES;
167     ms->need_revalidate = false;
168     ms->flood_unreg = true;
169     ovs_refcount_init(&ms->ref_cnt);
170     ovs_rwlock_init(&ms->rwlock);
171     return ms;
172 }
173
174 struct mcast_snooping *
175 mcast_snooping_ref(const struct mcast_snooping *ms_)
176 {
177     struct mcast_snooping *ms = CONST_CAST(struct mcast_snooping *, ms_);
178     if (ms) {
179         ovs_refcount_ref(&ms->ref_cnt);
180     }
181     return ms;
182 }
183
184 /* Unreferences (and possibly destroys) mcast snooping table 'ms'. */
185 void
186 mcast_snooping_unref(struct mcast_snooping *ms)
187 {
188     if (!mcast_snooping_enabled(ms)) {
189         return;
190     }
191
192     if (ovs_refcount_unref_relaxed(&ms->ref_cnt) == 1) {
193         mcast_snooping_flush(ms);
194         hmap_destroy(&ms->table);
195         ovs_rwlock_destroy(&ms->rwlock);
196         free(ms);
197     }
198 }
199
200 /* Changes the mcast aging timeout of 'ms' to 'idle_time' seconds. */
201 void
202 mcast_snooping_set_idle_time(struct mcast_snooping *ms, unsigned int idle_time)
203     OVS_REQ_WRLOCK(ms->rwlock)
204 {
205     struct mcast_group *grp;
206     struct mcast_group_bundle *b;
207     int delta;
208
209     idle_time = normalize_idle_time(idle_time);
210     if (idle_time != ms->idle_time) {
211         delta = (int) idle_time - (int) ms->idle_time;
212         LIST_FOR_EACH (grp, group_node, &ms->group_lru) {
213             LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
214                 b->expires += delta;
215             }
216         }
217         ms->idle_time = idle_time;
218     }
219 }
220
221 /* Sets the maximum number of entries in 'ms' to 'max_entries', adjusting it
222  * to be within a reasonable range. */
223 void
224 mcast_snooping_set_max_entries(struct mcast_snooping *ms,
225                                size_t max_entries)
226     OVS_REQ_WRLOCK(ms->rwlock)
227 {
228     ms->max_entries = (max_entries < 10 ? 10
229                        : max_entries > 1000 * 1000 ? 1000 * 1000
230                        : max_entries);
231 }
232
233 /* Sets if unregistered multicast packets should be flooded to
234  * all ports or only to ports connected to multicast routers
235  *
236  * Returns true if previous state differs from current state,
237  * false otherwise. */
238 bool
239 mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable)
240     OVS_REQ_WRLOCK(ms->rwlock)
241 {
242     bool prev = ms->flood_unreg;
243     ms->flood_unreg = enable;
244     return prev != enable;
245 }
246
247 static struct mcast_group_bundle *
248 mcast_group_bundle_lookup(struct mcast_snooping *ms OVS_UNUSED,
249                           struct mcast_group *grp, void *port)
250     OVS_REQ_RDLOCK(ms->rwlock)
251 {
252     struct mcast_group_bundle *b;
253
254     LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
255         if (b->port == port) {
256             return b;
257         }
258     }
259     return NULL;
260 }
261
262 /* Insert a new bundle to the mcast group or update its
263  * position and expiration if it is already there. */
264 static struct mcast_group_bundle *
265 mcast_group_insert_bundle(struct mcast_snooping *ms OVS_UNUSED,
266                           struct mcast_group *grp, void *port, int idle_time)
267     OVS_REQ_WRLOCK(ms->rwlock)
268 {
269     struct mcast_group_bundle *b;
270
271     b = mcast_group_bundle_lookup(ms, grp, port);
272     if (b) {
273         list_remove(&b->bundle_node);
274     } else {
275         b = xmalloc(sizeof *b);
276         list_init(&b->bundle_node);
277         b->port = port;
278     }
279
280     b->expires = time_now() + idle_time;
281     list_push_back(&grp->bundle_lru, &b->bundle_node);
282     return b;
283 }
284
285 /* Return true if multicast still has bundles associated.
286  * Return false if there is no bundles. */
287 static bool
288 mcast_group_has_bundles(struct mcast_group *grp)
289 {
290     return !list_is_empty(&grp->bundle_lru);
291 }
292
293 /* Delete 'grp' from the 'ms' hash table.
294  * Caller is responsible to clean bundle lru first. */
295 static void
296 mcast_snooping_flush_group__(struct mcast_snooping *ms,
297                              struct mcast_group *grp)
298 {
299     ovs_assert(list_is_empty(&grp->bundle_lru));
300     hmap_remove(&ms->table, &grp->hmap_node);
301     list_remove(&grp->group_node);
302     free(grp);
303 }
304
305 /* Flush out mcast group and its bundles */
306 static void
307 mcast_snooping_flush_group(struct mcast_snooping *ms, struct mcast_group *grp)
308     OVS_REQ_WRLOCK(ms->rwlock)
309 {
310     struct mcast_group_bundle *b, *next_b;
311
312     LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
313         list_remove(&b->bundle_node);
314         free(b);
315     }
316     mcast_snooping_flush_group__(ms, grp);
317     ms->need_revalidate = true;
318 }
319
320
321 /* Delete bundle returning true if it succeeds,
322  * false if it didn't find the group. */
323 static bool
324 mcast_group_delete_bundle(struct mcast_snooping *ms OVS_UNUSED,
325                           struct mcast_group *grp, void *port)
326     OVS_REQ_WRLOCK(ms->rwlock)
327 {
328     struct mcast_group_bundle *b;
329
330     LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
331         if (b->port == port) {
332             list_remove(&b->bundle_node);
333             free(b);
334             return true;
335         }
336     }
337     return false;
338 }
339
340 /* If any bundle has expired, delete it.  Returns the number of deleted
341  * bundles. */
342 static int
343 mcast_snooping_prune_expired(struct mcast_snooping *ms,
344                              struct mcast_group *grp)
345     OVS_REQ_WRLOCK(ms->rwlock)
346 {
347     int expired;
348     struct mcast_group_bundle *b, *next_b;
349     time_t timenow = time_now();
350
351     expired = 0;
352     LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
353         /* This list is sorted on expiration time. */
354         if (b->expires > timenow) {
355             break;
356         }
357         list_remove(&b->bundle_node);
358         free(b);
359         expired++;
360     }
361
362     if (!mcast_group_has_bundles(grp)) {
363         mcast_snooping_flush_group__(ms, grp);
364         expired++;
365     }
366
367     if (expired) {
368         ms->need_revalidate = true;
369         COVERAGE_ADD(mcast_snooping_expired, expired);
370     }
371
372     return expired;
373 }
374
375 /* Add a multicast group to the mdb. If it exists, then
376  * move to the last position in the LRU list.
377  */
378 bool
379 mcast_snooping_add_group(struct mcast_snooping *ms, ovs_be32 ip4,
380                          uint16_t vlan, void *port)
381     OVS_REQ_WRLOCK(ms->rwlock)
382 {
383     bool learned;
384     struct mcast_group *grp;
385
386     /* Avoid duplicate packets. */
387     if (mcast_snooping_mrouter_lookup(ms, vlan, port)
388         || mcast_snooping_port_lookup(&ms->fport_list, port)) {
389         return false;
390     }
391
392     learned = false;
393     grp = mcast_snooping_lookup(ms, ip4, vlan);
394     if (!grp) {
395         uint32_t hash = mcast_table_hash(ms, ip4, vlan);
396
397         if (hmap_count(&ms->table) >= ms->max_entries) {
398             group_get_lru(ms, &grp);
399             mcast_snooping_flush_group(ms, grp);
400         }
401
402         grp = xmalloc(sizeof *grp);
403         hmap_insert(&ms->table, &grp->hmap_node, hash);
404         grp->ip4 = ip4;
405         grp->vlan = vlan;
406         list_init(&grp->bundle_lru);
407         learned = true;
408         ms->need_revalidate = true;
409         COVERAGE_INC(mcast_snooping_learned);
410     } else {
411         list_remove(&grp->group_node);
412     }
413     mcast_group_insert_bundle(ms, grp, port, ms->idle_time);
414
415     /* Mark 'grp' as recently used. */
416     list_push_back(&ms->group_lru, &grp->group_node);
417     return learned;
418 }
419
420 bool
421 mcast_snooping_leave_group(struct mcast_snooping *ms, ovs_be32 ip4,
422                            uint16_t vlan, void *port)
423     OVS_REQ_WRLOCK(ms->rwlock)
424 {
425     struct mcast_group *grp;
426
427     /* Ports flagged to forward Reports usually have more
428      * than one host behind it, so don't leave the group
429      * on the first message and just let it expire */
430     if (mcast_snooping_port_lookup(&ms->rport_list, port)) {
431         return false;
432     }
433
434     grp = mcast_snooping_lookup(ms, ip4, vlan);
435     if (grp && mcast_group_delete_bundle(ms, grp, port)) {
436         ms->need_revalidate = true;
437         return true;
438     }
439     return false;
440 }
441
442 \f
443 /* Router ports. */
444
445 /* Returns the number of seconds since the multicast router
446  * was learned in a port. */
447 int
448 mcast_mrouter_age(const struct mcast_snooping *ms OVS_UNUSED,
449                   const struct mcast_mrouter_bundle *mrouter)
450 {
451     time_t remaining = mrouter->expires - time_now();
452     return MCAST_MROUTER_PORT_IDLE_TIME - remaining;
453 }
454
455 static struct mcast_mrouter_bundle *
456 mcast_mrouter_from_lru_node(struct ovs_list *list)
457 {
458     return CONTAINER_OF(list, struct mcast_mrouter_bundle, mrouter_node);
459 }
460
461 /* If the LRU list is not empty, stores the least-recently-used mrouter
462  * in '*m' and returns true.  Otherwise, if the LRU list is empty,
463  * stores NULL in '*m' and return false. */
464 static bool
465 mrouter_get_lru(const struct mcast_snooping *ms,
466                 struct mcast_mrouter_bundle **m)
467     OVS_REQ_RDLOCK(ms->rwlock)
468 {
469     if (!list_is_empty(&ms->mrouter_lru)) {
470         *m = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
471         return true;
472     } else {
473         *m = NULL;
474         return false;
475     }
476 }
477
478 static struct mcast_mrouter_bundle *
479 mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
480                               void *port)
481     OVS_REQ_RDLOCK(ms->rwlock)
482 {
483     struct mcast_mrouter_bundle *mrouter;
484
485     LIST_FOR_EACH (mrouter, mrouter_node, &ms->mrouter_lru) {
486         if (mrouter->vlan == vlan && mrouter->port == port) {
487             return mrouter;
488         }
489     }
490     return NULL;
491 }
492
493 bool
494 mcast_snooping_add_mrouter(struct mcast_snooping *ms, uint16_t vlan,
495                            void *port)
496     OVS_REQ_WRLOCK(ms->rwlock)
497 {
498     struct mcast_mrouter_bundle *mrouter;
499
500     /* Avoid duplicate packets. */
501     if (mcast_snooping_port_lookup(&ms->fport_list, port)) {
502         return false;
503     }
504
505     mrouter = mcast_snooping_mrouter_lookup(ms, vlan, port);
506     if (mrouter) {
507         list_remove(&mrouter->mrouter_node);
508     } else {
509         mrouter = xmalloc(sizeof *mrouter);
510         mrouter->vlan = vlan;
511         mrouter->port = port;
512         COVERAGE_INC(mcast_snooping_learned);
513         ms->need_revalidate = true;
514     }
515
516     mrouter->expires = time_now() + MCAST_MROUTER_PORT_IDLE_TIME;
517     list_push_back(&ms->mrouter_lru, &mrouter->mrouter_node);
518     return ms->need_revalidate;
519 }
520
521 static void
522 mcast_snooping_flush_mrouter(struct mcast_mrouter_bundle *mrouter)
523 {
524     list_remove(&mrouter->mrouter_node);
525     free(mrouter);
526 }
527 \f
528 /* Ports */
529
530 static struct mcast_port_bundle *
531 mcast_port_from_list_node(struct ovs_list *list)
532 {
533     return CONTAINER_OF(list, struct mcast_port_bundle, node);
534 }
535
536 /* If the list is not empty, stores the fport in '*f' and returns true.
537  * Otherwise, if the list is empty, stores NULL in '*f' and return false. */
538 static bool
539 mcast_snooping_port_get(const struct ovs_list *list,
540                         struct mcast_port_bundle **f)
541 {
542     if (!list_is_empty(list)) {
543         *f = mcast_port_from_list_node(list->next);
544         return true;
545     } else {
546         *f = NULL;
547         return false;
548     }
549 }
550
551 static struct mcast_port_bundle *
552 mcast_snooping_port_lookup(struct ovs_list *list, void *port)
553 {
554     struct mcast_port_bundle *pbundle;
555
556     LIST_FOR_EACH (pbundle, node, list) {
557         if (pbundle->port == port) {
558             return pbundle;
559         }
560     }
561     return NULL;
562 }
563
564 static void
565 mcast_snooping_add_port(struct ovs_list *list, void *port)
566 {
567     struct mcast_port_bundle *pbundle;
568
569     pbundle = xmalloc(sizeof *pbundle);
570     pbundle->port = port;
571     list_insert(list, &pbundle->node);
572 }
573
574 static void
575 mcast_snooping_flush_port(struct mcast_port_bundle *pbundle)
576 {
577     list_remove(&pbundle->node);
578     free(pbundle);
579 }
580
581 \f
582 /* Flood ports. */
583 void
584 mcast_snooping_set_port_flood(struct mcast_snooping *ms, void *port,
585                               bool flood)
586     OVS_REQ_WRLOCK(ms->rwlock)
587 {
588     struct mcast_port_bundle *fbundle;
589
590     fbundle = mcast_snooping_port_lookup(&ms->fport_list, port);
591     if (flood && !fbundle) {
592         mcast_snooping_add_port(&ms->fport_list, port);
593         ms->need_revalidate = true;
594     } else if (!flood && fbundle) {
595         mcast_snooping_flush_port(fbundle);
596         ms->need_revalidate = true;
597     }
598 }
599 \f
600 /* Flood Reports ports. */
601
602 void
603 mcast_snooping_set_port_flood_reports(struct mcast_snooping *ms, void *port,
604                                       bool flood)
605     OVS_REQ_WRLOCK(ms->rwlock)
606 {
607     struct mcast_port_bundle *pbundle;
608
609     pbundle = mcast_snooping_port_lookup(&ms->rport_list, port);
610     if (flood && !pbundle) {
611         mcast_snooping_add_port(&ms->rport_list, port);
612         ms->need_revalidate = true;
613     } else if (!flood && pbundle) {
614         mcast_snooping_flush_port(pbundle);
615         ms->need_revalidate = true;
616     }
617 }
618 \f
619 /* Run and flush. */
620
621 static void
622 mcast_snooping_mdb_flush__(struct mcast_snooping *ms)
623     OVS_REQ_WRLOCK(ms->rwlock)
624 {
625     struct mcast_group *grp;
626     struct mcast_mrouter_bundle *mrouter;
627
628     while (group_get_lru(ms, &grp)) {
629         mcast_snooping_flush_group(ms, grp);
630     }
631
632     hmap_shrink(&ms->table);
633
634     while (mrouter_get_lru(ms, &mrouter)) {
635         mcast_snooping_flush_mrouter(mrouter);
636     }
637 }
638
639 void
640 mcast_snooping_mdb_flush(struct mcast_snooping *ms)
641 {
642     if (!mcast_snooping_enabled(ms)) {
643         return;
644     }
645
646     ovs_rwlock_wrlock(&ms->rwlock);
647     mcast_snooping_mdb_flush__(ms);
648     ovs_rwlock_unlock(&ms->rwlock);
649 }
650
651 /* Flushes mdb and flood ports. */
652 static void
653 mcast_snooping_flush__(struct mcast_snooping *ms)
654     OVS_REQ_WRLOCK(ms->rwlock)
655 {
656     struct mcast_group *grp;
657     struct mcast_mrouter_bundle *mrouter;
658     struct mcast_port_bundle *pbundle;
659
660     while (group_get_lru(ms, &grp)) {
661         mcast_snooping_flush_group(ms, grp);
662     }
663
664     hmap_shrink(&ms->table);
665
666     /* flush multicast routers */
667     while (mrouter_get_lru(ms, &mrouter)) {
668         mcast_snooping_flush_mrouter(mrouter);
669     }
670
671     /* flush flood ports */
672     while (mcast_snooping_port_get(&ms->fport_list, &pbundle)) {
673         mcast_snooping_flush_port(pbundle);
674     }
675
676     /* flush flood report ports */
677     while (mcast_snooping_port_get(&ms->rport_list, &pbundle)) {
678         mcast_snooping_flush_port(pbundle);
679     }
680 }
681
682 void
683 mcast_snooping_flush(struct mcast_snooping *ms)
684 {
685     if (!mcast_snooping_enabled(ms)) {
686         return;
687     }
688
689     ovs_rwlock_wrlock(&ms->rwlock);
690     mcast_snooping_flush__(ms);
691     ovs_rwlock_unlock(&ms->rwlock);
692 }
693
694 static bool
695 mcast_snooping_run__(struct mcast_snooping *ms)
696     OVS_REQ_WRLOCK(ms->rwlock)
697 {
698     bool need_revalidate;
699     struct mcast_group *grp;
700     struct mcast_mrouter_bundle *mrouter;
701     int mrouter_expired;
702
703     while (group_get_lru(ms, &grp)) {
704         if (hmap_count(&ms->table) > ms->max_entries) {
705             mcast_snooping_flush_group(ms, grp);
706         } else {
707             if (!mcast_snooping_prune_expired(ms, grp)) {
708                 break;
709             }
710         }
711     }
712
713     hmap_shrink(&ms->table);
714
715     mrouter_expired = 0;
716     while (mrouter_get_lru(ms, &mrouter)
717            && time_now() >= mrouter->expires) {
718         mcast_snooping_flush_mrouter(mrouter);
719         mrouter_expired++;
720     }
721
722     if (mrouter_expired) {
723         ms->need_revalidate = true;
724         COVERAGE_ADD(mcast_snooping_expired, mrouter_expired);
725     }
726
727     need_revalidate = ms->need_revalidate;
728     ms->need_revalidate = false;
729     return need_revalidate;
730 }
731
732 /* Does periodic work required by 'ms'. Returns true if something changed
733  * that may require flow revalidation. */
734 bool
735 mcast_snooping_run(struct mcast_snooping *ms)
736 {
737     bool need_revalidate;
738
739     if (!mcast_snooping_enabled(ms)) {
740         return false;
741     }
742
743     ovs_rwlock_wrlock(&ms->rwlock);
744     need_revalidate = mcast_snooping_run__(ms);
745     ovs_rwlock_unlock(&ms->rwlock);
746
747     return need_revalidate;
748 }
749
750 static void
751 mcast_snooping_wait__(struct mcast_snooping *ms)
752     OVS_REQ_RDLOCK(ms->rwlock)
753 {
754     if (hmap_count(&ms->table) > ms->max_entries
755         || ms->need_revalidate) {
756         poll_immediate_wake();
757     } else {
758         struct mcast_group *grp;
759         struct mcast_group_bundle *bundle;
760         struct mcast_mrouter_bundle *mrouter;
761         long long int mrouter_msec;
762         long long int msec = 0;
763
764         if (!list_is_empty(&ms->group_lru)) {
765             grp = mcast_group_from_lru_node(ms->group_lru.next);
766             bundle = mcast_group_bundle_from_lru_node(grp->bundle_lru.next);
767             msec = bundle->expires * 1000LL;
768         }
769
770         if (!list_is_empty(&ms->mrouter_lru)) {
771             mrouter = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
772             mrouter_msec = mrouter->expires * 1000LL;
773             msec = msec ? MIN(msec, mrouter_msec) : mrouter_msec;
774         }
775
776         if (msec) {
777             poll_timer_wait_until(msec);
778         }
779     }
780 }
781
782 void
783 mcast_snooping_wait(struct mcast_snooping *ms)
784 {
785     if (!mcast_snooping_enabled(ms)) {
786         return;
787     }
788
789     ovs_rwlock_rdlock(&ms->rwlock);
790     mcast_snooping_wait__(ms);
791     ovs_rwlock_unlock(&ms->rwlock);
792 }