]> git.proxmox.com Git - mirror_ovs.git/blame - lib/mcast-snooping.c
lib: Expose SAT_MUT as OVS_SAT_MUL in <openvswitch/util.h>
[mirror_ovs.git] / lib / mcast-snooping.c
CommitLineData
4a95091d
FL
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
38COVERAGE_DEFINE(mcast_snooping_learned);
39COVERAGE_DEFINE(mcast_snooping_expired);
40
41static struct mcast_mrouter_bundle *
42mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
43 void *port)
44 OVS_REQ_RDLOCK(ms->rwlock);
45
46bool
47mcast_snooping_enabled(const struct mcast_snooping *ms)
48{
49 return !!ms;
50}
51
52bool
53mcast_snooping_flood_unreg(const struct mcast_snooping *ms)
54{
55 return ms->flood_unreg;
56}
57
58bool
59mcast_snooping_is_query(ovs_be16 igmp_type)
60{
61 return igmp_type == htons(IGMP_HOST_MEMBERSHIP_QUERY);
62}
63
64bool
65mcast_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'. */
78int
79mcast_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
86static uint32_t
87mcast_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
93static struct mcast_group_bundle *
94mcast_group_bundle_from_lru_node(struct list *list)
95{
96 return CONTAINER_OF(list, struct mcast_group_bundle, bundle_node);
97}
98
99static struct mcast_group *
100mcast_group_from_lru_node(struct 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'. */
107struct mcast_group *
108mcast_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. */
127static bool
128group_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
140static unsigned int
141normalize_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. */
151struct mcast_snooping *
152mcast_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
171struct mcast_snooping *
172mcast_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'. */
182void
183mcast_snooping_unref(struct mcast_snooping *ms)
184{
185 if (!mcast_snooping_enabled(ms)) {
186 return;
187 }
188
24f83812 189 if (ovs_refcount_unref_relaxed(&ms->ref_cnt) == 1) {
4a95091d
FL
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. */
198void
199mcast_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. */
220void
221mcast_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. */
235bool
236mcast_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
244static struct mcast_group_bundle *
245mcast_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. */
261static struct mcast_group_bundle *
262mcast_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. */
284static bool
285mcast_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. */
292static void
293mcast_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 */
303static void
304mcast_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. */
320static bool
321mcast_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. */
339static int
340mcast_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 */
375bool
376mcast_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
417bool
418mcast_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. */
437int
438mcast_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
445static struct mcast_mrouter_bundle *
446mcast_mrouter_from_lru_node(struct 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. */
454static bool
455mrouter_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
468static struct mcast_mrouter_bundle *
469mcast_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
483bool
484mcast_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
511static void
512mcast_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
520static struct mcast_fport_bundle *
521mcast_fport_from_list_node(struct 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. */
528static bool
529fport_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
541struct mcast_fport_bundle *
542mcast_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
556static void
557mcast_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
568static void
569mcast_snooping_flush_fport(struct mcast_fport_bundle *fport)
570{
571 list_remove(&fport->fport_node);
572 free(fport);
573}
574
575void
576mcast_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
594static void
595mcast_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
612void
613mcast_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. */
625static void
626mcast_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
648void
649mcast_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
660static bool
661mcast_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. */
700bool
701mcast_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
716static void
717mcast_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
748void
749mcast_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}