2 * Copyright (c) 2014 Red Hat, Inc.
4 * Based on mac-learning implementation.
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:
10 * http://www.apache.org/licenses/LICENSE-2.0
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.
20 #include "mcast-snooping.h"
26 #include "byte-order.h"
30 #include "poll-loop.h"
33 #include "unaligned.h"
35 #include "vlan-bitmap.h"
36 #include "openvswitch/vlog.h"
38 COVERAGE_DEFINE(mcast_snooping_learned);
39 COVERAGE_DEFINE(mcast_snooping_expired);
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,
46 OVS_REQ_RDLOCK(ms->rwlock);
49 mcast_snooping_enabled(const struct mcast_snooping *ms)
55 mcast_snooping_flood_unreg(const struct mcast_snooping *ms)
57 return ms->flood_unreg;
61 mcast_snooping_is_query(ovs_be16 igmp_type)
63 return igmp_type == htons(IGMP_HOST_MEMBERSHIP_QUERY);
67 mcast_snooping_is_membership(ovs_be16 igmp_type)
69 switch (ntohs(igmp_type)) {
70 case IGMP_HOST_MEMBERSHIP_REPORT:
71 case IGMPV2_HOST_MEMBERSHIP_REPORT:
72 case IGMPV3_HOST_MEMBERSHIP_REPORT:
73 case IGMP_HOST_LEAVE_MESSAGE:
79 /* Returns the number of seconds since multicast group 'b' was learned in a
82 mcast_bundle_age(const struct mcast_snooping *ms,
83 const struct mcast_group_bundle *b)
85 time_t remaining = b->expires - time_now();
86 return ms->idle_time - remaining;
90 mcast_table_hash(const struct mcast_snooping *ms,
91 const struct in6_addr *grp_addr, uint16_t vlan)
93 return hash_bytes(grp_addr->s6_addr, 16,
94 hash_2words(ms->secret, vlan));
97 static struct mcast_group_bundle *
98 mcast_group_bundle_from_lru_node(struct ovs_list *list)
100 return CONTAINER_OF(list, struct mcast_group_bundle, bundle_node);
103 static struct mcast_group *
104 mcast_group_from_lru_node(struct ovs_list *list)
106 return CONTAINER_OF(list, struct mcast_group, group_node);
109 /* Searches 'ms' for and returns an mcast group for destination address
110 * 'dip' in 'vlan'. */
112 mcast_snooping_lookup(const struct mcast_snooping *ms,
113 const struct in6_addr *dip, uint16_t vlan)
114 OVS_REQ_RDLOCK(ms->rwlock)
116 struct mcast_group *grp;
119 hash = mcast_table_hash(ms, dip, vlan);
120 HMAP_FOR_EACH_WITH_HASH (grp, hmap_node, hash, &ms->table) {
121 if (grp->vlan == vlan && ipv6_addr_equals(&grp->addr, dip)) {
129 in6_addr_set_mapped_ipv4(struct in6_addr *addr, ovs_be32 ip4)
131 union ovs_16aligned_in6_addr *taddr = (void *) addr;
132 memset(taddr->be16, 0, sizeof(taddr->be16));
133 taddr->be16[5] = OVS_BE16_MAX;
134 put_16aligned_be32(&taddr->be32[3], ip4);
138 mcast_snooping_lookup4(const struct mcast_snooping *ms, ovs_be32 ip4,
140 OVS_REQ_RDLOCK(ms->rwlock)
142 struct in6_addr addr;
143 in6_addr_set_mapped_ipv4(&addr, ip4);
144 return mcast_snooping_lookup(ms, &addr, vlan);
147 /* If the LRU list is not empty, stores the least-recently-used entry
148 * in '*e' and returns true. Otherwise, if the LRU list is empty,
149 * stores NULL in '*e' and return false. */
151 group_get_lru(const struct mcast_snooping *ms, struct mcast_group **grp)
152 OVS_REQ_RDLOCK(ms->rwlock)
154 if (!list_is_empty(&ms->group_lru)) {
155 *grp = mcast_group_from_lru_node(ms->group_lru.next);
164 normalize_idle_time(unsigned int idle_time)
166 return (idle_time < 15 ? 15
167 : idle_time > 3600 ? 3600
171 /* Creates and returns a new mcast table with an initial mcast aging
172 * timeout of MCAST_ENTRY_DEFAULT_IDLE_TIME seconds and an initial maximum of
173 * MCAST_DEFAULT_MAX entries. */
174 struct mcast_snooping *
175 mcast_snooping_create(void)
177 struct mcast_snooping *ms;
179 ms = xmalloc(sizeof *ms);
180 hmap_init(&ms->table);
181 list_init(&ms->group_lru);
182 list_init(&ms->mrouter_lru);
183 list_init(&ms->fport_list);
184 list_init(&ms->rport_list);
185 ms->secret = random_uint32();
186 ms->idle_time = MCAST_ENTRY_DEFAULT_IDLE_TIME;
187 ms->max_entries = MCAST_DEFAULT_MAX_ENTRIES;
188 ms->need_revalidate = false;
189 ms->flood_unreg = true;
190 ovs_refcount_init(&ms->ref_cnt);
191 ovs_rwlock_init(&ms->rwlock);
195 struct mcast_snooping *
196 mcast_snooping_ref(const struct mcast_snooping *ms_)
198 struct mcast_snooping *ms = CONST_CAST(struct mcast_snooping *, ms_);
200 ovs_refcount_ref(&ms->ref_cnt);
205 /* Unreferences (and possibly destroys) mcast snooping table 'ms'. */
207 mcast_snooping_unref(struct mcast_snooping *ms)
209 if (!mcast_snooping_enabled(ms)) {
213 if (ovs_refcount_unref_relaxed(&ms->ref_cnt) == 1) {
214 mcast_snooping_flush(ms);
215 hmap_destroy(&ms->table);
216 ovs_rwlock_destroy(&ms->rwlock);
221 /* Changes the mcast aging timeout of 'ms' to 'idle_time' seconds. */
223 mcast_snooping_set_idle_time(struct mcast_snooping *ms, unsigned int idle_time)
224 OVS_REQ_WRLOCK(ms->rwlock)
226 struct mcast_group *grp;
227 struct mcast_group_bundle *b;
230 idle_time = normalize_idle_time(idle_time);
231 if (idle_time != ms->idle_time) {
232 delta = (int) idle_time - (int) ms->idle_time;
233 LIST_FOR_EACH (grp, group_node, &ms->group_lru) {
234 LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
238 ms->idle_time = idle_time;
242 /* Sets the maximum number of entries in 'ms' to 'max_entries', adjusting it
243 * to be within a reasonable range. */
245 mcast_snooping_set_max_entries(struct mcast_snooping *ms,
247 OVS_REQ_WRLOCK(ms->rwlock)
249 ms->max_entries = (max_entries < 10 ? 10
250 : max_entries > 1000 * 1000 ? 1000 * 1000
254 /* Sets if unregistered multicast packets should be flooded to
255 * all ports or only to ports connected to multicast routers
257 * Returns true if previous state differs from current state,
258 * false otherwise. */
260 mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable)
261 OVS_REQ_WRLOCK(ms->rwlock)
263 bool prev = ms->flood_unreg;
264 ms->flood_unreg = enable;
265 return prev != enable;
268 static struct mcast_group_bundle *
269 mcast_group_bundle_lookup(struct mcast_snooping *ms OVS_UNUSED,
270 struct mcast_group *grp, void *port)
271 OVS_REQ_RDLOCK(ms->rwlock)
273 struct mcast_group_bundle *b;
275 LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
276 if (b->port == port) {
283 /* Insert a new bundle to the mcast group or update its
284 * position and expiration if it is already there. */
285 static struct mcast_group_bundle *
286 mcast_group_insert_bundle(struct mcast_snooping *ms OVS_UNUSED,
287 struct mcast_group *grp, void *port, int idle_time)
288 OVS_REQ_WRLOCK(ms->rwlock)
290 struct mcast_group_bundle *b;
292 b = mcast_group_bundle_lookup(ms, grp, port);
294 list_remove(&b->bundle_node);
296 b = xmalloc(sizeof *b);
297 list_init(&b->bundle_node);
301 b->expires = time_now() + idle_time;
302 list_push_back(&grp->bundle_lru, &b->bundle_node);
306 /* Return true if multicast still has bundles associated.
307 * Return false if there is no bundles. */
309 mcast_group_has_bundles(struct mcast_group *grp)
311 return !list_is_empty(&grp->bundle_lru);
314 /* Delete 'grp' from the 'ms' hash table.
315 * Caller is responsible to clean bundle lru first. */
317 mcast_snooping_flush_group__(struct mcast_snooping *ms,
318 struct mcast_group *grp)
320 ovs_assert(list_is_empty(&grp->bundle_lru));
321 hmap_remove(&ms->table, &grp->hmap_node);
322 list_remove(&grp->group_node);
326 /* Flush out mcast group and its bundles */
328 mcast_snooping_flush_group(struct mcast_snooping *ms, struct mcast_group *grp)
329 OVS_REQ_WRLOCK(ms->rwlock)
331 struct mcast_group_bundle *b;
333 LIST_FOR_EACH_POP (b, bundle_node, &grp->bundle_lru) {
336 mcast_snooping_flush_group__(ms, grp);
337 ms->need_revalidate = true;
341 /* Delete bundle returning true if it succeeds,
342 * false if it didn't find the group. */
344 mcast_group_delete_bundle(struct mcast_snooping *ms OVS_UNUSED,
345 struct mcast_group *grp, void *port)
346 OVS_REQ_WRLOCK(ms->rwlock)
348 struct mcast_group_bundle *b;
350 LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
351 if (b->port == port) {
352 list_remove(&b->bundle_node);
360 /* If any bundle has expired, delete it. Returns the number of deleted
363 mcast_snooping_prune_expired(struct mcast_snooping *ms,
364 struct mcast_group *grp)
365 OVS_REQ_WRLOCK(ms->rwlock)
368 struct mcast_group_bundle *b, *next_b;
369 time_t timenow = time_now();
372 LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
373 /* This list is sorted on expiration time. */
374 if (b->expires > timenow) {
377 list_remove(&b->bundle_node);
382 if (!mcast_group_has_bundles(grp)) {
383 mcast_snooping_flush_group__(ms, grp);
388 ms->need_revalidate = true;
389 COVERAGE_ADD(mcast_snooping_expired, expired);
395 /* Add a multicast group to the mdb. If it exists, then
396 * move to the last position in the LRU list.
399 mcast_snooping_add_group(struct mcast_snooping *ms,
400 const struct in6_addr *addr,
401 uint16_t vlan, void *port)
402 OVS_REQ_WRLOCK(ms->rwlock)
405 struct mcast_group *grp;
407 /* Avoid duplicate packets. */
408 if (mcast_snooping_mrouter_lookup(ms, vlan, port)
409 || mcast_snooping_port_lookup(&ms->fport_list, port)) {
414 grp = mcast_snooping_lookup(ms, addr, vlan);
416 uint32_t hash = mcast_table_hash(ms, addr, vlan);
418 if (hmap_count(&ms->table) >= ms->max_entries) {
419 group_get_lru(ms, &grp);
420 mcast_snooping_flush_group(ms, grp);
423 grp = xmalloc(sizeof *grp);
424 hmap_insert(&ms->table, &grp->hmap_node, hash);
427 list_init(&grp->bundle_lru);
429 ms->need_revalidate = true;
430 COVERAGE_INC(mcast_snooping_learned);
432 list_remove(&grp->group_node);
434 mcast_group_insert_bundle(ms, grp, port, ms->idle_time);
436 /* Mark 'grp' as recently used. */
437 list_push_back(&ms->group_lru, &grp->group_node);
442 mcast_snooping_add_group4(struct mcast_snooping *ms, ovs_be32 ip4,
443 uint16_t vlan, void *port)
444 OVS_REQ_WRLOCK(ms->rwlock)
446 struct in6_addr addr;
447 in6_addr_set_mapped_ipv4(&addr, ip4);
448 return mcast_snooping_add_group(ms, &addr, vlan, port);
452 mcast_snooping_add_report(struct mcast_snooping *ms,
453 const struct dp_packet *p,
454 uint16_t vlan, void *port)
458 const struct igmpv3_header *igmpv3;
459 const struct igmpv3_record *record;
463 offset = (char *) dp_packet_l4(p) - (char *) dp_packet_data(p);
464 igmpv3 = dp_packet_at(p, offset, IGMPV3_HEADER_LEN);
468 ngrp = ntohs(igmpv3->ngrp);
469 offset += IGMPV3_HEADER_LEN;
472 record = dp_packet_at(p, offset, sizeof(struct igmpv3_record));
476 /* Only consider known record types. */
477 if (record->type < IGMPV3_MODE_IS_INCLUDE
478 || record->type > IGMPV3_BLOCK_OLD_SOURCES) {
481 ip4 = get_16aligned_be32(&record->maddr);
483 * If record is INCLUDE MODE and there are no sources, it's equivalent
486 if (ntohs(record->nsrcs) == 0
487 && (record->type == IGMPV3_MODE_IS_INCLUDE
488 || record->type == IGMPV3_CHANGE_TO_INCLUDE_MODE)) {
489 ret = mcast_snooping_leave_group4(ms, ip4, vlan, port);
491 ret = mcast_snooping_add_group4(ms, ip4, vlan, port);
496 offset += sizeof(*record)
497 + ntohs(record->nsrcs) * sizeof(ovs_be32) + record->aux_len;
503 mcast_snooping_leave_group(struct mcast_snooping *ms,
504 const struct in6_addr *addr,
505 uint16_t vlan, void *port)
506 OVS_REQ_WRLOCK(ms->rwlock)
508 struct mcast_group *grp;
510 /* Ports flagged to forward Reports usually have more
511 * than one host behind it, so don't leave the group
512 * on the first message and just let it expire */
513 if (mcast_snooping_port_lookup(&ms->rport_list, port)) {
517 grp = mcast_snooping_lookup(ms, addr, vlan);
518 if (grp && mcast_group_delete_bundle(ms, grp, port)) {
519 ms->need_revalidate = true;
526 mcast_snooping_leave_group4(struct mcast_snooping *ms, ovs_be32 ip4,
527 uint16_t vlan, void *port)
529 struct in6_addr addr;
530 in6_addr_set_mapped_ipv4(&addr, ip4);
531 return mcast_snooping_leave_group(ms, &addr, vlan, port);
537 /* Returns the number of seconds since the multicast router
538 * was learned in a port. */
540 mcast_mrouter_age(const struct mcast_snooping *ms OVS_UNUSED,
541 const struct mcast_mrouter_bundle *mrouter)
543 time_t remaining = mrouter->expires - time_now();
544 return MCAST_MROUTER_PORT_IDLE_TIME - remaining;
547 static struct mcast_mrouter_bundle *
548 mcast_mrouter_from_lru_node(struct ovs_list *list)
550 return CONTAINER_OF(list, struct mcast_mrouter_bundle, mrouter_node);
553 /* If the LRU list is not empty, stores the least-recently-used mrouter
554 * in '*m' and returns true. Otherwise, if the LRU list is empty,
555 * stores NULL in '*m' and return false. */
557 mrouter_get_lru(const struct mcast_snooping *ms,
558 struct mcast_mrouter_bundle **m)
559 OVS_REQ_RDLOCK(ms->rwlock)
561 if (!list_is_empty(&ms->mrouter_lru)) {
562 *m = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
570 static struct mcast_mrouter_bundle *
571 mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
573 OVS_REQ_RDLOCK(ms->rwlock)
575 struct mcast_mrouter_bundle *mrouter;
577 LIST_FOR_EACH (mrouter, mrouter_node, &ms->mrouter_lru) {
578 if (mrouter->vlan == vlan && mrouter->port == port) {
586 mcast_snooping_add_mrouter(struct mcast_snooping *ms, uint16_t vlan,
588 OVS_REQ_WRLOCK(ms->rwlock)
590 struct mcast_mrouter_bundle *mrouter;
592 /* Avoid duplicate packets. */
593 if (mcast_snooping_port_lookup(&ms->fport_list, port)) {
597 mrouter = mcast_snooping_mrouter_lookup(ms, vlan, port);
599 list_remove(&mrouter->mrouter_node);
601 mrouter = xmalloc(sizeof *mrouter);
602 mrouter->vlan = vlan;
603 mrouter->port = port;
604 COVERAGE_INC(mcast_snooping_learned);
605 ms->need_revalidate = true;
608 mrouter->expires = time_now() + MCAST_MROUTER_PORT_IDLE_TIME;
609 list_push_back(&ms->mrouter_lru, &mrouter->mrouter_node);
610 return ms->need_revalidate;
614 mcast_snooping_flush_mrouter(struct mcast_mrouter_bundle *mrouter)
616 list_remove(&mrouter->mrouter_node);
622 static struct mcast_port_bundle *
623 mcast_port_from_list_node(struct ovs_list *list)
625 return CONTAINER_OF(list, struct mcast_port_bundle, node);
628 /* If the list is not empty, stores the fport in '*f' and returns true.
629 * Otherwise, if the list is empty, stores NULL in '*f' and return false. */
631 mcast_snooping_port_get(const struct ovs_list *list,
632 struct mcast_port_bundle **f)
634 if (!list_is_empty(list)) {
635 *f = mcast_port_from_list_node(list->next);
643 static struct mcast_port_bundle *
644 mcast_snooping_port_lookup(struct ovs_list *list, void *port)
646 struct mcast_port_bundle *pbundle;
648 LIST_FOR_EACH (pbundle, node, list) {
649 if (pbundle->port == port) {
657 mcast_snooping_add_port(struct ovs_list *list, void *port)
659 struct mcast_port_bundle *pbundle;
661 pbundle = xmalloc(sizeof *pbundle);
662 pbundle->port = port;
663 list_insert(list, &pbundle->node);
667 mcast_snooping_flush_port(struct mcast_port_bundle *pbundle)
669 list_remove(&pbundle->node);
676 mcast_snooping_set_port_flood(struct mcast_snooping *ms, void *port,
678 OVS_REQ_WRLOCK(ms->rwlock)
680 struct mcast_port_bundle *fbundle;
682 fbundle = mcast_snooping_port_lookup(&ms->fport_list, port);
683 if (flood && !fbundle) {
684 mcast_snooping_add_port(&ms->fport_list, port);
685 ms->need_revalidate = true;
686 } else if (!flood && fbundle) {
687 mcast_snooping_flush_port(fbundle);
688 ms->need_revalidate = true;
692 /* Flood Reports ports. */
695 mcast_snooping_set_port_flood_reports(struct mcast_snooping *ms, void *port,
697 OVS_REQ_WRLOCK(ms->rwlock)
699 struct mcast_port_bundle *pbundle;
701 pbundle = mcast_snooping_port_lookup(&ms->rport_list, port);
702 if (flood && !pbundle) {
703 mcast_snooping_add_port(&ms->rport_list, port);
704 ms->need_revalidate = true;
705 } else if (!flood && pbundle) {
706 mcast_snooping_flush_port(pbundle);
707 ms->need_revalidate = true;
714 mcast_snooping_mdb_flush__(struct mcast_snooping *ms)
715 OVS_REQ_WRLOCK(ms->rwlock)
717 struct mcast_group *grp;
718 struct mcast_mrouter_bundle *mrouter;
720 while (group_get_lru(ms, &grp)) {
721 mcast_snooping_flush_group(ms, grp);
724 hmap_shrink(&ms->table);
726 while (mrouter_get_lru(ms, &mrouter)) {
727 mcast_snooping_flush_mrouter(mrouter);
732 mcast_snooping_mdb_flush(struct mcast_snooping *ms)
734 if (!mcast_snooping_enabled(ms)) {
738 ovs_rwlock_wrlock(&ms->rwlock);
739 mcast_snooping_mdb_flush__(ms);
740 ovs_rwlock_unlock(&ms->rwlock);
743 /* Flushes mdb and flood ports. */
745 mcast_snooping_flush__(struct mcast_snooping *ms)
746 OVS_REQ_WRLOCK(ms->rwlock)
748 struct mcast_group *grp;
749 struct mcast_mrouter_bundle *mrouter;
750 struct mcast_port_bundle *pbundle;
752 while (group_get_lru(ms, &grp)) {
753 mcast_snooping_flush_group(ms, grp);
756 hmap_shrink(&ms->table);
758 /* flush multicast routers */
759 while (mrouter_get_lru(ms, &mrouter)) {
760 mcast_snooping_flush_mrouter(mrouter);
763 /* flush flood ports */
764 while (mcast_snooping_port_get(&ms->fport_list, &pbundle)) {
765 mcast_snooping_flush_port(pbundle);
768 /* flush flood report ports */
769 while (mcast_snooping_port_get(&ms->rport_list, &pbundle)) {
770 mcast_snooping_flush_port(pbundle);
775 mcast_snooping_flush(struct mcast_snooping *ms)
777 if (!mcast_snooping_enabled(ms)) {
781 ovs_rwlock_wrlock(&ms->rwlock);
782 mcast_snooping_flush__(ms);
783 ovs_rwlock_unlock(&ms->rwlock);
787 mcast_snooping_run__(struct mcast_snooping *ms)
788 OVS_REQ_WRLOCK(ms->rwlock)
790 bool need_revalidate;
791 struct mcast_group *grp;
792 struct mcast_mrouter_bundle *mrouter;
795 while (group_get_lru(ms, &grp)) {
796 if (hmap_count(&ms->table) > ms->max_entries) {
797 mcast_snooping_flush_group(ms, grp);
799 if (!mcast_snooping_prune_expired(ms, grp)) {
805 hmap_shrink(&ms->table);
808 while (mrouter_get_lru(ms, &mrouter)
809 && time_now() >= mrouter->expires) {
810 mcast_snooping_flush_mrouter(mrouter);
814 if (mrouter_expired) {
815 ms->need_revalidate = true;
816 COVERAGE_ADD(mcast_snooping_expired, mrouter_expired);
819 need_revalidate = ms->need_revalidate;
820 ms->need_revalidate = false;
821 return need_revalidate;
824 /* Does periodic work required by 'ms'. Returns true if something changed
825 * that may require flow revalidation. */
827 mcast_snooping_run(struct mcast_snooping *ms)
829 bool need_revalidate;
831 if (!mcast_snooping_enabled(ms)) {
835 ovs_rwlock_wrlock(&ms->rwlock);
836 need_revalidate = mcast_snooping_run__(ms);
837 ovs_rwlock_unlock(&ms->rwlock);
839 return need_revalidate;
843 mcast_snooping_wait__(struct mcast_snooping *ms)
844 OVS_REQ_RDLOCK(ms->rwlock)
846 if (hmap_count(&ms->table) > ms->max_entries
847 || ms->need_revalidate) {
848 poll_immediate_wake();
850 struct mcast_group *grp;
851 struct mcast_group_bundle *bundle;
852 struct mcast_mrouter_bundle *mrouter;
853 long long int mrouter_msec;
854 long long int msec = 0;
856 if (!list_is_empty(&ms->group_lru)) {
857 grp = mcast_group_from_lru_node(ms->group_lru.next);
858 bundle = mcast_group_bundle_from_lru_node(grp->bundle_lru.next);
859 msec = bundle->expires * 1000LL;
862 if (!list_is_empty(&ms->mrouter_lru)) {
863 mrouter = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
864 mrouter_msec = mrouter->expires * 1000LL;
865 msec = msec ? MIN(msec, mrouter_msec) : mrouter_msec;
869 poll_timer_wait_until(msec);
875 mcast_snooping_wait(struct mcast_snooping *ms)
877 if (!mcast_snooping_enabled(ms)) {
881 ovs_rwlock_rdlock(&ms->rwlock);
882 mcast_snooping_wait__(ms);
883 ovs_rwlock_unlock(&ms->rwlock);