mcast_snoop: make mcast_fport_bundle generic
[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     ms->secret = random_uint32();
164     ms->idle_time = MCAST_ENTRY_DEFAULT_IDLE_TIME;
165     ms->max_entries = MCAST_DEFAULT_MAX_ENTRIES;
166     ms->need_revalidate = false;
167     ms->flood_unreg = true;
168     ovs_refcount_init(&ms->ref_cnt);
169     ovs_rwlock_init(&ms->rwlock);
170     return ms;
171 }
172
173 struct mcast_snooping *
174 mcast_snooping_ref(const struct mcast_snooping *ms_)
175 {
176     struct mcast_snooping *ms = CONST_CAST(struct mcast_snooping *, ms_);
177     if (ms) {
178         ovs_refcount_ref(&ms->ref_cnt);
179     }
180     return ms;
181 }
182
183 /* Unreferences (and possibly destroys) mcast snooping table 'ms'. */
184 void
185 mcast_snooping_unref(struct mcast_snooping *ms)
186 {
187     if (!mcast_snooping_enabled(ms)) {
188         return;
189     }
190
191     if (ovs_refcount_unref_relaxed(&ms->ref_cnt) == 1) {
192         mcast_snooping_flush(ms);
193         hmap_destroy(&ms->table);
194         ovs_rwlock_destroy(&ms->rwlock);
195         free(ms);
196     }
197 }
198
199 /* Changes the mcast aging timeout of 'ms' to 'idle_time' seconds. */
200 void
201 mcast_snooping_set_idle_time(struct mcast_snooping *ms, unsigned int idle_time)
202     OVS_REQ_WRLOCK(ms->rwlock)
203 {
204     struct mcast_group *grp;
205     struct mcast_group_bundle *b;
206     int delta;
207
208     idle_time = normalize_idle_time(idle_time);
209     if (idle_time != ms->idle_time) {
210         delta = (int) idle_time - (int) ms->idle_time;
211         LIST_FOR_EACH (grp, group_node, &ms->group_lru) {
212             LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
213                 b->expires += delta;
214             }
215         }
216         ms->idle_time = idle_time;
217     }
218 }
219
220 /* Sets the maximum number of entries in 'ms' to 'max_entries', adjusting it
221  * to be within a reasonable range. */
222 void
223 mcast_snooping_set_max_entries(struct mcast_snooping *ms,
224                                size_t max_entries)
225     OVS_REQ_WRLOCK(ms->rwlock)
226 {
227     ms->max_entries = (max_entries < 10 ? 10
228                        : max_entries > 1000 * 1000 ? 1000 * 1000
229                        : max_entries);
230 }
231
232 /* Sets if unregistered multicast packets should be flooded to
233  * all ports or only to ports connected to multicast routers
234  *
235  * Returns true if previous state differs from current state,
236  * false otherwise. */
237 bool
238 mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable)
239     OVS_REQ_WRLOCK(ms->rwlock)
240 {
241     bool prev = ms->flood_unreg;
242     ms->flood_unreg = enable;
243     return prev != enable;
244 }
245
246 static struct mcast_group_bundle *
247 mcast_group_bundle_lookup(struct mcast_snooping *ms OVS_UNUSED,
248                           struct mcast_group *grp, void *port)
249     OVS_REQ_RDLOCK(ms->rwlock)
250 {
251     struct mcast_group_bundle *b;
252
253     LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
254         if (b->port == port) {
255             return b;
256         }
257     }
258     return NULL;
259 }
260
261 /* Insert a new bundle to the mcast group or update its
262  * position and expiration if it is already there. */
263 static struct mcast_group_bundle *
264 mcast_group_insert_bundle(struct mcast_snooping *ms OVS_UNUSED,
265                           struct mcast_group *grp, void *port, int idle_time)
266     OVS_REQ_WRLOCK(ms->rwlock)
267 {
268     struct mcast_group_bundle *b;
269
270     b = mcast_group_bundle_lookup(ms, grp, port);
271     if (b) {
272         list_remove(&b->bundle_node);
273     } else {
274         b = xmalloc(sizeof *b);
275         list_init(&b->bundle_node);
276         b->port = port;
277     }
278
279     b->expires = time_now() + idle_time;
280     list_push_back(&grp->bundle_lru, &b->bundle_node);
281     return b;
282 }
283
284 /* Return true if multicast still has bundles associated.
285  * Return false if there is no bundles. */
286 static bool
287 mcast_group_has_bundles(struct mcast_group *grp)
288 {
289     return !list_is_empty(&grp->bundle_lru);
290 }
291
292 /* Delete 'grp' from the 'ms' hash table.
293  * Caller is responsible to clean bundle lru first. */
294 static void
295 mcast_snooping_flush_group__(struct mcast_snooping *ms,
296                              struct mcast_group *grp)
297 {
298     ovs_assert(list_is_empty(&grp->bundle_lru));
299     hmap_remove(&ms->table, &grp->hmap_node);
300     list_remove(&grp->group_node);
301     free(grp);
302 }
303
304 /* Flush out mcast group and its bundles */
305 static void
306 mcast_snooping_flush_group(struct mcast_snooping *ms, struct mcast_group *grp)
307     OVS_REQ_WRLOCK(ms->rwlock)
308 {
309     struct mcast_group_bundle *b, *next_b;
310
311     LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
312         list_remove(&b->bundle_node);
313         free(b);
314     }
315     mcast_snooping_flush_group__(ms, grp);
316     ms->need_revalidate = true;
317 }
318
319
320 /* Delete bundle returning true if it succeeds,
321  * false if it didn't find the group. */
322 static bool
323 mcast_group_delete_bundle(struct mcast_snooping *ms OVS_UNUSED,
324                           struct mcast_group *grp, void *port)
325     OVS_REQ_WRLOCK(ms->rwlock)
326 {
327     struct mcast_group_bundle *b;
328
329     LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
330         if (b->port == port) {
331             list_remove(&b->bundle_node);
332             free(b);
333             return true;
334         }
335     }
336     return false;
337 }
338
339 /* If any bundle has expired, delete it.  Returns the number of deleted
340  * bundles. */
341 static int
342 mcast_snooping_prune_expired(struct mcast_snooping *ms,
343                              struct mcast_group *grp)
344     OVS_REQ_WRLOCK(ms->rwlock)
345 {
346     int expired;
347     struct mcast_group_bundle *b, *next_b;
348     time_t timenow = time_now();
349
350     expired = 0;
351     LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
352         /* This list is sorted on expiration time. */
353         if (b->expires > timenow) {
354             break;
355         }
356         list_remove(&b->bundle_node);
357         free(b);
358         expired++;
359     }
360
361     if (!mcast_group_has_bundles(grp)) {
362         mcast_snooping_flush_group__(ms, grp);
363         expired++;
364     }
365
366     if (expired) {
367         ms->need_revalidate = true;
368         COVERAGE_ADD(mcast_snooping_expired, expired);
369     }
370
371     return expired;
372 }
373
374 /* Add a multicast group to the mdb. If it exists, then
375  * move to the last position in the LRU list.
376  */
377 bool
378 mcast_snooping_add_group(struct mcast_snooping *ms, ovs_be32 ip4,
379                          uint16_t vlan, void *port)
380     OVS_REQ_WRLOCK(ms->rwlock)
381 {
382     bool learned;
383     struct mcast_group *grp;
384
385     /* Avoid duplicate packets. */
386     if (mcast_snooping_mrouter_lookup(ms, vlan, port)
387         || mcast_snooping_port_lookup(&ms->fport_list, port)) {
388         return false;
389     }
390
391     learned = false;
392     grp = mcast_snooping_lookup(ms, ip4, vlan);
393     if (!grp) {
394         uint32_t hash = mcast_table_hash(ms, ip4, vlan);
395
396         if (hmap_count(&ms->table) >= ms->max_entries) {
397             group_get_lru(ms, &grp);
398             mcast_snooping_flush_group(ms, grp);
399         }
400
401         grp = xmalloc(sizeof *grp);
402         hmap_insert(&ms->table, &grp->hmap_node, hash);
403         grp->ip4 = ip4;
404         grp->vlan = vlan;
405         list_init(&grp->bundle_lru);
406         learned = true;
407         ms->need_revalidate = true;
408         COVERAGE_INC(mcast_snooping_learned);
409     } else {
410         list_remove(&grp->group_node);
411     }
412     mcast_group_insert_bundle(ms, grp, port, ms->idle_time);
413
414     /* Mark 'grp' as recently used. */
415     list_push_back(&ms->group_lru, &grp->group_node);
416     return learned;
417 }
418
419 bool
420 mcast_snooping_leave_group(struct mcast_snooping *ms, ovs_be32 ip4,
421                            uint16_t vlan, void *port)
422     OVS_REQ_WRLOCK(ms->rwlock)
423 {
424     struct mcast_group *grp;
425
426     grp = mcast_snooping_lookup(ms, ip4, vlan);
427     if (grp && mcast_group_delete_bundle(ms, grp, port)) {
428         ms->need_revalidate = true;
429         return true;
430     }
431     return false;
432 }
433
434 \f
435 /* Router ports. */
436
437 /* Returns the number of seconds since the multicast router
438  * was learned in a port. */
439 int
440 mcast_mrouter_age(const struct mcast_snooping *ms OVS_UNUSED,
441                   const struct mcast_mrouter_bundle *mrouter)
442 {
443     time_t remaining = mrouter->expires - time_now();
444     return MCAST_MROUTER_PORT_IDLE_TIME - remaining;
445 }
446
447 static struct mcast_mrouter_bundle *
448 mcast_mrouter_from_lru_node(struct ovs_list *list)
449 {
450     return CONTAINER_OF(list, struct mcast_mrouter_bundle, mrouter_node);
451 }
452
453 /* If the LRU list is not empty, stores the least-recently-used mrouter
454  * in '*m' and returns true.  Otherwise, if the LRU list is empty,
455  * stores NULL in '*m' and return false. */
456 static bool
457 mrouter_get_lru(const struct mcast_snooping *ms,
458                 struct mcast_mrouter_bundle **m)
459     OVS_REQ_RDLOCK(ms->rwlock)
460 {
461     if (!list_is_empty(&ms->mrouter_lru)) {
462         *m = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
463         return true;
464     } else {
465         *m = NULL;
466         return false;
467     }
468 }
469
470 static struct mcast_mrouter_bundle *
471 mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
472                               void *port)
473     OVS_REQ_RDLOCK(ms->rwlock)
474 {
475     struct mcast_mrouter_bundle *mrouter;
476
477     LIST_FOR_EACH (mrouter, mrouter_node, &ms->mrouter_lru) {
478         if (mrouter->vlan == vlan && mrouter->port == port) {
479             return mrouter;
480         }
481     }
482     return NULL;
483 }
484
485 bool
486 mcast_snooping_add_mrouter(struct mcast_snooping *ms, uint16_t vlan,
487                            void *port)
488     OVS_REQ_WRLOCK(ms->rwlock)
489 {
490     struct mcast_mrouter_bundle *mrouter;
491
492     /* Avoid duplicate packets. */
493     if (mcast_snooping_port_lookup(&ms->fport_list, port)) {
494         return false;
495     }
496
497     mrouter = mcast_snooping_mrouter_lookup(ms, vlan, port);
498     if (mrouter) {
499         list_remove(&mrouter->mrouter_node);
500     } else {
501         mrouter = xmalloc(sizeof *mrouter);
502         mrouter->vlan = vlan;
503         mrouter->port = port;
504         COVERAGE_INC(mcast_snooping_learned);
505         ms->need_revalidate = true;
506     }
507
508     mrouter->expires = time_now() + MCAST_MROUTER_PORT_IDLE_TIME;
509     list_push_back(&ms->mrouter_lru, &mrouter->mrouter_node);
510     return ms->need_revalidate;
511 }
512
513 static void
514 mcast_snooping_flush_mrouter(struct mcast_mrouter_bundle *mrouter)
515 {
516     list_remove(&mrouter->mrouter_node);
517     free(mrouter);
518 }
519 \f
520 /* Ports */
521
522 static struct mcast_port_bundle *
523 mcast_port_from_list_node(struct ovs_list *list)
524 {
525     return CONTAINER_OF(list, struct mcast_port_bundle, node);
526 }
527
528 /* If the list is not empty, stores the fport in '*f' and returns true.
529  * Otherwise, if the list is empty, stores NULL in '*f' and return false. */
530 static bool
531 mcast_snooping_port_get(const struct ovs_list *list,
532                         struct mcast_port_bundle **f)
533 {
534     if (!list_is_empty(list)) {
535         *f = mcast_port_from_list_node(list->next);
536         return true;
537     } else {
538         *f = NULL;
539         return false;
540     }
541 }
542
543 static struct mcast_port_bundle *
544 mcast_snooping_port_lookup(struct ovs_list *list, void *port)
545 {
546     struct mcast_port_bundle *pbundle;
547
548     LIST_FOR_EACH (pbundle, node, list) {
549         if (pbundle->port == port) {
550             return pbundle;
551         }
552     }
553     return NULL;
554 }
555
556 static void
557 mcast_snooping_add_port(struct ovs_list *list, void *port)
558 {
559     struct mcast_port_bundle *pbundle;
560
561     pbundle = xmalloc(sizeof *pbundle);
562     pbundle->port = port;
563     list_insert(list, &pbundle->node);
564 }
565
566 static void
567 mcast_snooping_flush_port(struct mcast_port_bundle *pbundle)
568 {
569     list_remove(&pbundle->node);
570     free(pbundle);
571 }
572
573 \f
574 /* Flood ports. */
575 void
576 mcast_snooping_set_port_flood(struct mcast_snooping *ms, void *port,
577                               bool flood)
578     OVS_REQ_WRLOCK(ms->rwlock)
579 {
580     struct mcast_port_bundle *fbundle;
581
582     fbundle = mcast_snooping_port_lookup(&ms->fport_list, port);
583     if (flood && !fbundle) {
584         mcast_snooping_add_port(&ms->fport_list, port);
585         ms->need_revalidate = true;
586     } else if (!flood && fbundle) {
587         mcast_snooping_flush_port(fbundle);
588         ms->need_revalidate = true;
589     }
590 }
591 \f
592 /* Run and flush. */
593
594 static void
595 mcast_snooping_mdb_flush__(struct mcast_snooping *ms)
596     OVS_REQ_WRLOCK(ms->rwlock)
597 {
598     struct mcast_group *grp;
599     struct mcast_mrouter_bundle *mrouter;
600
601     while (group_get_lru(ms, &grp)) {
602         mcast_snooping_flush_group(ms, grp);
603     }
604
605     hmap_shrink(&ms->table);
606
607     while (mrouter_get_lru(ms, &mrouter)) {
608         mcast_snooping_flush_mrouter(mrouter);
609     }
610 }
611
612 void
613 mcast_snooping_mdb_flush(struct mcast_snooping *ms)
614 {
615     if (!mcast_snooping_enabled(ms)) {
616         return;
617     }
618
619     ovs_rwlock_wrlock(&ms->rwlock);
620     mcast_snooping_mdb_flush__(ms);
621     ovs_rwlock_unlock(&ms->rwlock);
622 }
623
624 /* Flushes mdb and flood ports. */
625 static void
626 mcast_snooping_flush__(struct mcast_snooping *ms)
627     OVS_REQ_WRLOCK(ms->rwlock)
628 {
629     struct mcast_group *grp;
630     struct mcast_mrouter_bundle *mrouter;
631     struct mcast_port_bundle *pbundle;
632
633     while (group_get_lru(ms, &grp)) {
634         mcast_snooping_flush_group(ms, grp);
635     }
636
637     hmap_shrink(&ms->table);
638
639     while (mrouter_get_lru(ms, &mrouter)) {
640         mcast_snooping_flush_mrouter(mrouter);
641     }
642
643     while (mcast_snooping_port_get(&ms->fport_list, &pbundle)) {
644         mcast_snooping_flush_port(pbundle);
645     }
646 }
647
648 void
649 mcast_snooping_flush(struct mcast_snooping *ms)
650 {
651     if (!mcast_snooping_enabled(ms)) {
652         return;
653     }
654
655     ovs_rwlock_wrlock(&ms->rwlock);
656     mcast_snooping_flush__(ms);
657     ovs_rwlock_unlock(&ms->rwlock);
658 }
659
660 static bool
661 mcast_snooping_run__(struct mcast_snooping *ms)
662     OVS_REQ_WRLOCK(ms->rwlock)
663 {
664     bool need_revalidate;
665     struct mcast_group *grp;
666     struct mcast_mrouter_bundle *mrouter;
667     int mrouter_expired;
668
669     while (group_get_lru(ms, &grp)) {
670         if (hmap_count(&ms->table) > ms->max_entries) {
671             mcast_snooping_flush_group(ms, grp);
672         } else {
673             if (!mcast_snooping_prune_expired(ms, grp)) {
674                 break;
675             }
676         }
677     }
678
679     hmap_shrink(&ms->table);
680
681     mrouter_expired = 0;
682     while (mrouter_get_lru(ms, &mrouter)
683            && time_now() >= mrouter->expires) {
684         mcast_snooping_flush_mrouter(mrouter);
685         mrouter_expired++;
686     }
687
688     if (mrouter_expired) {
689         ms->need_revalidate = true;
690         COVERAGE_ADD(mcast_snooping_expired, mrouter_expired);
691     }
692
693     need_revalidate = ms->need_revalidate;
694     ms->need_revalidate = false;
695     return need_revalidate;
696 }
697
698 /* Does periodic work required by 'ms'. Returns true if something changed
699  * that may require flow revalidation. */
700 bool
701 mcast_snooping_run(struct mcast_snooping *ms)
702 {
703     bool need_revalidate;
704
705     if (!mcast_snooping_enabled(ms)) {
706         return false;
707     }
708
709     ovs_rwlock_wrlock(&ms->rwlock);
710     need_revalidate = mcast_snooping_run__(ms);
711     ovs_rwlock_unlock(&ms->rwlock);
712
713     return need_revalidate;
714 }
715
716 static void
717 mcast_snooping_wait__(struct mcast_snooping *ms)
718     OVS_REQ_RDLOCK(ms->rwlock)
719 {
720     if (hmap_count(&ms->table) > ms->max_entries
721         || ms->need_revalidate) {
722         poll_immediate_wake();
723     } else {
724         struct mcast_group *grp;
725         struct mcast_group_bundle *bundle;
726         struct mcast_mrouter_bundle *mrouter;
727         long long int mrouter_msec;
728         long long int msec = 0;
729
730         if (!list_is_empty(&ms->group_lru)) {
731             grp = mcast_group_from_lru_node(ms->group_lru.next);
732             bundle = mcast_group_bundle_from_lru_node(grp->bundle_lru.next);
733             msec = bundle->expires * 1000LL;
734         }
735
736         if (!list_is_empty(&ms->mrouter_lru)) {
737             mrouter = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
738             mrouter_msec = mrouter->expires * 1000LL;
739             msec = msec ? MIN(msec, mrouter_msec) : mrouter_msec;
740         }
741
742         if (msec) {
743             poll_timer_wait_until(msec);
744         }
745     }
746 }
747
748 void
749 mcast_snooping_wait(struct mcast_snooping *ms)
750 {
751     if (!mcast_snooping_enabled(ms)) {
752         return;
753     }
754
755     ovs_rwlock_rdlock(&ms->rwlock);
756     mcast_snooping_wait__(ms);
757     ovs_rwlock_unlock(&ms->rwlock);
758 }