d99f01c13b07707547d1cf839bda3dd612da3b93
[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 "vlog.h"
37
38 COVERAGE_DEFINE(mcast_snooping_learned);
39 COVERAGE_DEFINE(mcast_snooping_expired);
40
41 static struct mcast_mrouter_bundle *
42 mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
43                               void *port)
44     OVS_REQ_RDLOCK(ms->rwlock);
45
46 bool
47 mcast_snooping_enabled(const struct mcast_snooping *ms)
48 {
49     return !!ms;
50 }
51
52 bool
53 mcast_snooping_flood_unreg(const struct mcast_snooping *ms)
54 {
55     return ms->flood_unreg;
56 }
57
58 bool
59 mcast_snooping_is_query(ovs_be16 igmp_type)
60 {
61     return igmp_type == htons(IGMP_HOST_MEMBERSHIP_QUERY);
62 }
63
64 bool
65 mcast_snooping_is_membership(ovs_be16 igmp_type)
66 {
67     switch (ntohs(igmp_type)) {
68     case IGMP_HOST_MEMBERSHIP_REPORT:
69     case IGMPV2_HOST_MEMBERSHIP_REPORT:
70     case IGMP_HOST_LEAVE_MESSAGE:
71         return true;
72     }
73     return false;
74 }
75
76 /* Returns the number of seconds since multicast group 'b' was learned in a
77  * port on 'ms'. */
78 int
79 mcast_bundle_age(const struct mcast_snooping *ms,
80                  const struct mcast_group_bundle *b)
81 {
82     time_t remaining = b->expires - time_now();
83     return ms->idle_time - remaining;
84 }
85
86 static uint32_t
87 mcast_table_hash(const struct mcast_snooping *ms, ovs_be32 grp_ip4,
88                  uint16_t vlan)
89 {
90     return hash_3words((OVS_FORCE uint32_t) grp_ip4, vlan, ms->secret);
91 }
92
93 static struct mcast_group_bundle *
94 mcast_group_bundle_from_lru_node(struct ovs_list *list)
95 {
96     return CONTAINER_OF(list, struct mcast_group_bundle, bundle_node);
97 }
98
99 static struct mcast_group *
100 mcast_group_from_lru_node(struct ovs_list *list)
101 {
102     return CONTAINER_OF(list, struct mcast_group, group_node);
103 }
104
105 /* Searches 'ms' for and returns an mcast group for destination address
106  * 'dip' in 'vlan'. */
107 struct mcast_group *
108 mcast_snooping_lookup(const struct mcast_snooping *ms, ovs_be32 dip,
109                       uint16_t vlan)
110     OVS_REQ_RDLOCK(ms->rwlock)
111 {
112     struct mcast_group *grp;
113     uint32_t hash;
114
115     hash = mcast_table_hash(ms, dip, vlan);
116     HMAP_FOR_EACH_WITH_HASH (grp, hmap_node, hash, &ms->table) {
117         if (grp->vlan == vlan && grp->ip4 == dip) {
118            return grp;
119         }
120     }
121     return NULL;
122 }
123
124 /* If the LRU list is not empty, stores the least-recently-used entry
125  * in '*e' and returns true.  Otherwise, if the LRU list is empty,
126  * stores NULL in '*e' and return false. */
127 static bool
128 group_get_lru(const struct mcast_snooping *ms, struct mcast_group **grp)
129     OVS_REQ_RDLOCK(ms->rwlock)
130 {
131     if (!list_is_empty(&ms->group_lru)) {
132         *grp = mcast_group_from_lru_node(ms->group_lru.next);
133         return true;
134     } else {
135         *grp = NULL;
136         return false;
137     }
138 }
139
140 static unsigned int
141 normalize_idle_time(unsigned int idle_time)
142 {
143     return (idle_time < 15 ? 15
144             : idle_time > 3600 ? 3600
145             : idle_time);
146 }
147
148 /* Creates and returns a new mcast table with an initial mcast aging
149  * timeout of MCAST_ENTRY_DEFAULT_IDLE_TIME seconds and an initial maximum of
150  * MCAST_DEFAULT_MAX entries. */
151 struct mcast_snooping *
152 mcast_snooping_create(void)
153 {
154     struct mcast_snooping *ms;
155
156     ms = xmalloc(sizeof *ms);
157     hmap_init(&ms->table);
158     list_init(&ms->group_lru);
159     list_init(&ms->mrouter_lru);
160     list_init(&ms->fport_list);
161     ms->secret = random_uint32();
162     ms->idle_time = MCAST_ENTRY_DEFAULT_IDLE_TIME;
163     ms->max_entries = MCAST_DEFAULT_MAX_ENTRIES;
164     ms->need_revalidate = false;
165     ms->flood_unreg = true;
166     ovs_refcount_init(&ms->ref_cnt);
167     ovs_rwlock_init(&ms->rwlock);
168     return ms;
169 }
170
171 struct mcast_snooping *
172 mcast_snooping_ref(const struct mcast_snooping *ms_)
173 {
174     struct mcast_snooping *ms = CONST_CAST(struct mcast_snooping *, ms_);
175     if (ms) {
176         ovs_refcount_ref(&ms->ref_cnt);
177     }
178     return ms;
179 }
180
181 /* Unreferences (and possibly destroys) mcast snooping table 'ms'. */
182 void
183 mcast_snooping_unref(struct mcast_snooping *ms)
184 {
185     if (!mcast_snooping_enabled(ms)) {
186         return;
187     }
188
189     if (ovs_refcount_unref_relaxed(&ms->ref_cnt) == 1) {
190         mcast_snooping_flush(ms);
191         hmap_destroy(&ms->table);
192         ovs_rwlock_destroy(&ms->rwlock);
193         free(ms);
194     }
195 }
196
197 /* Changes the mcast aging timeout of 'ms' to 'idle_time' seconds. */
198 void
199 mcast_snooping_set_idle_time(struct mcast_snooping *ms, unsigned int idle_time)
200     OVS_REQ_WRLOCK(ms->rwlock)
201 {
202     struct mcast_group *grp;
203     struct mcast_group_bundle *b;
204     int delta;
205
206     idle_time = normalize_idle_time(idle_time);
207     if (idle_time != ms->idle_time) {
208         delta = (int) idle_time - (int) ms->idle_time;
209         LIST_FOR_EACH (grp, group_node, &ms->group_lru) {
210             LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
211                 b->expires += delta;
212             }
213         }
214         ms->idle_time = idle_time;
215     }
216 }
217
218 /* Sets the maximum number of entries in 'ms' to 'max_entries', adjusting it
219  * to be within a reasonable range. */
220 void
221 mcast_snooping_set_max_entries(struct mcast_snooping *ms,
222                                size_t max_entries)
223     OVS_REQ_WRLOCK(ms->rwlock)
224 {
225     ms->max_entries = (max_entries < 10 ? 10
226                        : max_entries > 1000 * 1000 ? 1000 * 1000
227                        : max_entries);
228 }
229
230 /* Sets if unregistered multicast packets should be flooded to
231  * all ports or only to ports connected to multicast routers
232  *
233  * Returns true if previous state differs from current state,
234  * false otherwise. */
235 bool
236 mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable)
237     OVS_REQ_WRLOCK(ms->rwlock)
238 {
239     bool prev = ms->flood_unreg;
240     ms->flood_unreg = enable;
241     return prev != enable;
242 }
243
244 static struct mcast_group_bundle *
245 mcast_group_bundle_lookup(struct mcast_snooping *ms OVS_UNUSED,
246                           struct mcast_group *grp, void *port)
247     OVS_REQ_RDLOCK(ms->rwlock)
248 {
249     struct mcast_group_bundle *b;
250
251     LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
252         if (b->port == port) {
253             return b;
254         }
255     }
256     return NULL;
257 }
258
259 /* Insert a new bundle to the mcast group or update its
260  * position and expiration if it is already there. */
261 static struct mcast_group_bundle *
262 mcast_group_insert_bundle(struct mcast_snooping *ms OVS_UNUSED,
263                           struct mcast_group *grp, void *port, int idle_time)
264     OVS_REQ_WRLOCK(ms->rwlock)
265 {
266     struct mcast_group_bundle *b;
267
268     b = mcast_group_bundle_lookup(ms, grp, port);
269     if (b) {
270         list_remove(&b->bundle_node);
271     } else {
272         b = xmalloc(sizeof *b);
273         list_init(&b->bundle_node);
274         b->port = port;
275     }
276
277     b->expires = time_now() + idle_time;
278     list_push_back(&grp->bundle_lru, &b->bundle_node);
279     return b;
280 }
281
282 /* Return true if multicast still has bundles associated.
283  * Return false if there is no bundles. */
284 static bool
285 mcast_group_has_bundles(struct mcast_group *grp)
286 {
287     return !list_is_empty(&grp->bundle_lru);
288 }
289
290 /* Delete 'grp' from the 'ms' hash table.
291  * Caller is responsible to clean bundle lru first. */
292 static void
293 mcast_snooping_flush_group__(struct mcast_snooping *ms,
294                              struct mcast_group *grp)
295 {
296     ovs_assert(list_is_empty(&grp->bundle_lru));
297     hmap_remove(&ms->table, &grp->hmap_node);
298     list_remove(&grp->group_node);
299     free(grp);
300 }
301
302 /* Flush out mcast group and its bundles */
303 static void
304 mcast_snooping_flush_group(struct mcast_snooping *ms, struct mcast_group *grp)
305     OVS_REQ_WRLOCK(ms->rwlock)
306 {
307     struct mcast_group_bundle *b, *next_b;
308
309     LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
310         list_remove(&b->bundle_node);
311         free(b);
312     }
313     mcast_snooping_flush_group__(ms, grp);
314     ms->need_revalidate = true;
315 }
316
317
318 /* Delete bundle returning true if it succeeds,
319  * false if it didn't find the group. */
320 static bool
321 mcast_group_delete_bundle(struct mcast_snooping *ms OVS_UNUSED,
322                           struct mcast_group *grp, void *port)
323     OVS_REQ_WRLOCK(ms->rwlock)
324 {
325     struct mcast_group_bundle *b;
326
327     LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
328         if (b->port == port) {
329             list_remove(&b->bundle_node);
330             free(b);
331             return true;
332         }
333     }
334     return false;
335 }
336
337 /* If any bundle has expired, delete it.  Returns the number of deleted
338  * bundles. */
339 static int
340 mcast_snooping_prune_expired(struct mcast_snooping *ms,
341                              struct mcast_group *grp)
342     OVS_REQ_WRLOCK(ms->rwlock)
343 {
344     int expired;
345     struct mcast_group_bundle *b, *next_b;
346     time_t timenow = time_now();
347
348     expired = 0;
349     LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
350         /* This list is sorted on expiration time. */
351         if (b->expires > timenow) {
352             break;
353         }
354         list_remove(&b->bundle_node);
355         free(b);
356         expired++;
357     }
358
359     if (!mcast_group_has_bundles(grp)) {
360         mcast_snooping_flush_group__(ms, grp);
361         expired++;
362     }
363
364     if (expired) {
365         ms->need_revalidate = true;
366         COVERAGE_ADD(mcast_snooping_expired, expired);
367     }
368
369     return expired;
370 }
371
372 /* Add a multicast group to the mdb. If it exists, then
373  * move to the last position in the LRU list.
374  */
375 bool
376 mcast_snooping_add_group(struct mcast_snooping *ms, ovs_be32 ip4,
377                          uint16_t vlan, void *port)
378     OVS_REQ_WRLOCK(ms->rwlock)
379 {
380     bool learned;
381     struct mcast_group *grp;
382
383     /* Avoid duplicate packets. */
384     if (mcast_snooping_mrouter_lookup(ms, vlan, port)
385         || mcast_snooping_fport_lookup(ms, vlan, port)) {
386         return false;
387     }
388
389     learned = false;
390     grp = mcast_snooping_lookup(ms, ip4, vlan);
391     if (!grp) {
392         uint32_t hash = mcast_table_hash(ms, ip4, vlan);
393
394         if (hmap_count(&ms->table) >= ms->max_entries) {
395             group_get_lru(ms, &grp);
396             mcast_snooping_flush_group(ms, grp);
397         }
398
399         grp = xmalloc(sizeof *grp);
400         hmap_insert(&ms->table, &grp->hmap_node, hash);
401         grp->ip4 = ip4;
402         grp->vlan = vlan;
403         list_init(&grp->bundle_lru);
404         learned = true;
405         ms->need_revalidate = true;
406         COVERAGE_INC(mcast_snooping_learned);
407     } else {
408         list_remove(&grp->group_node);
409     }
410     mcast_group_insert_bundle(ms, grp, port, ms->idle_time);
411
412     /* Mark 'grp' as recently used. */
413     list_push_back(&ms->group_lru, &grp->group_node);
414     return learned;
415 }
416
417 bool
418 mcast_snooping_leave_group(struct mcast_snooping *ms, ovs_be32 ip4,
419                            uint16_t vlan, void *port)
420     OVS_REQ_WRLOCK(ms->rwlock)
421 {
422     struct mcast_group *grp;
423
424     grp = mcast_snooping_lookup(ms, ip4, vlan);
425     if (grp && mcast_group_delete_bundle(ms, grp, port)) {
426         ms->need_revalidate = true;
427         return true;
428     }
429     return false;
430 }
431
432 \f
433 /* Router ports. */
434
435 /* Returns the number of seconds since the multicast router
436  * was learned in a port. */
437 int
438 mcast_mrouter_age(const struct mcast_snooping *ms OVS_UNUSED,
439                   const struct mcast_mrouter_bundle *mrouter)
440 {
441     time_t remaining = mrouter->expires - time_now();
442     return MCAST_MROUTER_PORT_IDLE_TIME - remaining;
443 }
444
445 static struct mcast_mrouter_bundle *
446 mcast_mrouter_from_lru_node(struct ovs_list *list)
447 {
448     return CONTAINER_OF(list, struct mcast_mrouter_bundle, mrouter_node);
449 }
450
451 /* If the LRU list is not empty, stores the least-recently-used mrouter
452  * in '*m' and returns true.  Otherwise, if the LRU list is empty,
453  * stores NULL in '*m' and return false. */
454 static bool
455 mrouter_get_lru(const struct mcast_snooping *ms,
456                 struct mcast_mrouter_bundle **m)
457     OVS_REQ_RDLOCK(ms->rwlock)
458 {
459     if (!list_is_empty(&ms->mrouter_lru)) {
460         *m = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
461         return true;
462     } else {
463         *m = NULL;
464         return false;
465     }
466 }
467
468 static struct mcast_mrouter_bundle *
469 mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
470                               void *port)
471     OVS_REQ_RDLOCK(ms->rwlock)
472 {
473     struct mcast_mrouter_bundle *mrouter;
474
475     LIST_FOR_EACH (mrouter, mrouter_node, &ms->mrouter_lru) {
476         if (mrouter->vlan == vlan && mrouter->port == port) {
477             return mrouter;
478         }
479     }
480     return NULL;
481 }
482
483 bool
484 mcast_snooping_add_mrouter(struct mcast_snooping *ms, uint16_t vlan,
485                            void *port)
486     OVS_REQ_WRLOCK(ms->rwlock)
487 {
488     struct mcast_mrouter_bundle *mrouter;
489
490     /* Avoid duplicate packets. */
491     if (mcast_snooping_fport_lookup(ms, vlan, port)) {
492         return false;
493     }
494
495     mrouter = mcast_snooping_mrouter_lookup(ms, vlan, port);
496     if (mrouter) {
497         list_remove(&mrouter->mrouter_node);
498     } else {
499         mrouter = xmalloc(sizeof *mrouter);
500         mrouter->vlan = vlan;
501         mrouter->port = port;
502         COVERAGE_INC(mcast_snooping_learned);
503         ms->need_revalidate = true;
504     }
505
506     mrouter->expires = time_now() + MCAST_MROUTER_PORT_IDLE_TIME;
507     list_push_back(&ms->mrouter_lru, &mrouter->mrouter_node);
508     return ms->need_revalidate;
509 }
510
511 static void
512 mcast_snooping_flush_mrouter(struct mcast_mrouter_bundle *mrouter)
513 {
514     list_remove(&mrouter->mrouter_node);
515     free(mrouter);
516 }
517 \f
518 /* Flood ports. */
519
520 static struct mcast_fport_bundle *
521 mcast_fport_from_list_node(struct ovs_list *list)
522 {
523     return CONTAINER_OF(list, struct mcast_fport_bundle, fport_node);
524 }
525
526 /* If the list is not empty, stores the fport in '*f' and returns true.
527  * Otherwise, if the list is empty, stores NULL in '*f' and return false. */
528 static bool
529 fport_get(const struct mcast_snooping *ms, struct mcast_fport_bundle **f)
530     OVS_REQ_RDLOCK(ms->rwlock)
531 {
532     if (!list_is_empty(&ms->fport_list)) {
533         *f = mcast_fport_from_list_node(ms->fport_list.next);
534         return true;
535     } else {
536         *f = NULL;
537         return false;
538     }
539 }
540
541 struct mcast_fport_bundle *
542 mcast_snooping_fport_lookup(struct mcast_snooping *ms, uint16_t vlan,
543                             void *port)
544     OVS_REQ_RDLOCK(ms->rwlock)
545 {
546     struct mcast_fport_bundle *fport;
547
548     LIST_FOR_EACH (fport, fport_node, &ms->fport_list) {
549         if (fport->vlan == vlan && fport->port == port) {
550             return fport;
551         }
552     }
553     return NULL;
554 }
555
556 static void
557 mcast_snooping_add_fport(struct mcast_snooping *ms, uint16_t vlan, void *port)
558     OVS_REQ_WRLOCK(ms->rwlock)
559 {
560     struct mcast_fport_bundle *fport;
561
562     fport = xmalloc(sizeof *fport);
563     fport->vlan = vlan;
564     fport->port = port;
565     list_insert(&ms->fport_list, &fport->fport_node);
566 }
567
568 static void
569 mcast_snooping_flush_fport(struct mcast_fport_bundle *fport)
570 {
571     list_remove(&fport->fport_node);
572     free(fport);
573 }
574
575 void
576 mcast_snooping_set_port_flood(struct mcast_snooping *ms, uint16_t vlan,
577                               void *port, bool flood)
578     OVS_REQ_WRLOCK(ms->rwlock)
579 {
580     struct mcast_fport_bundle *fport;
581
582     fport = mcast_snooping_fport_lookup(ms, vlan, port);
583     if (flood && !fport) {
584         mcast_snooping_add_fport(ms, vlan, port);
585         ms->need_revalidate = true;
586     } else if (!flood && fport) {
587         mcast_snooping_flush_fport(fport);
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_fport_bundle *fport;
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 (fport_get(ms, &fport)) {
644         mcast_snooping_flush_fport(fport);
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 }