Branch data Line data Source code
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 "openvswitch/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 : 71668 : COVERAGE_DEFINE(mcast_snooping_learned);
39 : 71668 : 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 : 376759 : mcast_snooping_enabled(const struct mcast_snooping *ms)
50 : : {
51 : 376759 : return !!ms;
52 : : }
53 : :
54 : : bool
55 : 0 : mcast_snooping_flood_unreg(const struct mcast_snooping *ms)
56 : : {
57 : 0 : return ms->flood_unreg;
58 : : }
59 : :
60 : : bool
61 : 0 : mcast_snooping_is_query(ovs_be16 igmp_type)
62 : : {
63 : 0 : return igmp_type == htons(IGMP_HOST_MEMBERSHIP_QUERY);
64 : : }
65 : :
66 : : bool
67 : 0 : mcast_snooping_is_membership(ovs_be16 igmp_type)
68 : : {
69 [ # # ]: 0 : 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:
74 : 0 : return true;
75 : : }
76 : 0 : return false;
77 : : }
78 : :
79 : : /* Returns the number of seconds since multicast group 'b' was learned in a
80 : : * port on 'ms'. */
81 : : int
82 : 0 : mcast_bundle_age(const struct mcast_snooping *ms,
83 : : const struct mcast_group_bundle *b)
84 : : {
85 : 0 : time_t remaining = b->expires - time_now();
86 : 0 : return ms->idle_time - remaining;
87 : : }
88 : :
89 : : static uint32_t
90 : 0 : mcast_table_hash(const struct mcast_snooping *ms,
91 : : const struct in6_addr *grp_addr, uint16_t vlan)
92 : : {
93 : 0 : return hash_bytes(grp_addr->s6_addr, 16,
94 : : hash_2words(ms->secret, vlan));
95 : : }
96 : :
97 : : static struct mcast_group_bundle *
98 : 0 : mcast_group_bundle_from_lru_node(struct ovs_list *list)
99 : : {
100 : 0 : return CONTAINER_OF(list, struct mcast_group_bundle, bundle_node);
101 : : }
102 : :
103 : : static struct mcast_group *
104 : 0 : mcast_group_from_lru_node(struct ovs_list *list)
105 : : {
106 : 0 : return CONTAINER_OF(list, struct mcast_group, group_node);
107 : : }
108 : :
109 : : /* Searches 'ms' for and returns an mcast group for destination address
110 : : * 'dip' in 'vlan'. */
111 : : struct mcast_group *
112 : 0 : mcast_snooping_lookup(const struct mcast_snooping *ms,
113 : : const struct in6_addr *dip, uint16_t vlan)
114 : : OVS_REQ_RDLOCK(ms->rwlock)
115 : : {
116 : : struct mcast_group *grp;
117 : : uint32_t hash;
118 : :
119 : 0 : hash = mcast_table_hash(ms, dip, vlan);
120 [ # # ][ # # ]: 0 : HMAP_FOR_EACH_WITH_HASH (grp, hmap_node, hash, &ms->table) {
121 [ # # ][ # # ]: 0 : if (grp->vlan == vlan && ipv6_addr_equals(&grp->addr, dip)) {
122 : 0 : return grp;
123 : : }
124 : : }
125 : 0 : return NULL;
126 : : }
127 : :
128 : : struct mcast_group *
129 : 0 : mcast_snooping_lookup4(const struct mcast_snooping *ms, ovs_be32 ip4,
130 : : uint16_t vlan)
131 : : OVS_REQ_RDLOCK(ms->rwlock)
132 : : {
133 : 0 : struct in6_addr addr = in6_addr_mapped_ipv4(ip4);
134 : 0 : return mcast_snooping_lookup(ms, &addr, vlan);
135 : : }
136 : :
137 : : /* If the LRU list is not empty, stores the least-recently-used entry
138 : : * in '*e' and returns true. Otherwise, if the LRU list is empty,
139 : : * stores NULL in '*e' and return false. */
140 : : static bool
141 : 0 : group_get_lru(const struct mcast_snooping *ms, struct mcast_group **grp)
142 : : OVS_REQ_RDLOCK(ms->rwlock)
143 : : {
144 [ # # ]: 0 : if (!ovs_list_is_empty(&ms->group_lru)) {
145 : 0 : *grp = mcast_group_from_lru_node(ms->group_lru.next);
146 : 0 : return true;
147 : : } else {
148 : 0 : *grp = NULL;
149 : 0 : return false;
150 : : }
151 : : }
152 : :
153 : : static unsigned int
154 : 0 : normalize_idle_time(unsigned int idle_time)
155 : : {
156 : 0 : return (idle_time < 15 ? 15
157 [ # # ]: 0 : : idle_time > 3600 ? 3600
158 : : : idle_time);
159 : : }
160 : :
161 : : /* Creates and returns a new mcast table with an initial mcast aging
162 : : * timeout of MCAST_ENTRY_DEFAULT_IDLE_TIME seconds and an initial maximum of
163 : : * MCAST_DEFAULT_MAX entries. */
164 : : struct mcast_snooping *
165 : 0 : mcast_snooping_create(void)
166 : : {
167 : : struct mcast_snooping *ms;
168 : :
169 : 0 : ms = xmalloc(sizeof *ms);
170 : 0 : hmap_init(&ms->table);
171 : 0 : ovs_list_init(&ms->group_lru);
172 : 0 : ovs_list_init(&ms->mrouter_lru);
173 : 0 : ovs_list_init(&ms->fport_list);
174 : 0 : ovs_list_init(&ms->rport_list);
175 : 0 : ms->secret = random_uint32();
176 : 0 : ms->idle_time = MCAST_ENTRY_DEFAULT_IDLE_TIME;
177 : 0 : ms->max_entries = MCAST_DEFAULT_MAX_ENTRIES;
178 : 0 : ms->need_revalidate = false;
179 : 0 : ms->flood_unreg = true;
180 : 0 : ovs_refcount_init(&ms->ref_cnt);
181 : 0 : ovs_rwlock_init(&ms->rwlock);
182 : 0 : return ms;
183 : : }
184 : :
185 : : struct mcast_snooping *
186 : 0 : mcast_snooping_ref(const struct mcast_snooping *ms_)
187 : : {
188 : 0 : struct mcast_snooping *ms = CONST_CAST(struct mcast_snooping *, ms_);
189 [ # # ]: 0 : if (ms) {
190 : 0 : ovs_refcount_ref(&ms->ref_cnt);
191 : : }
192 : 0 : return ms;
193 : : }
194 : :
195 : : /* Unreferences (and possibly destroys) mcast snooping table 'ms'. */
196 : : void
197 : 47223 : mcast_snooping_unref(struct mcast_snooping *ms)
198 : : {
199 [ + - ]: 47223 : if (!mcast_snooping_enabled(ms)) {
200 : 47223 : return;
201 : : }
202 : :
203 [ # # ]: 0 : if (ovs_refcount_unref_relaxed(&ms->ref_cnt) == 1) {
204 : 0 : mcast_snooping_flush(ms);
205 : 0 : hmap_destroy(&ms->table);
206 : 0 : ovs_rwlock_destroy(&ms->rwlock);
207 : 0 : free(ms);
208 : : }
209 : : }
210 : :
211 : : /* Changes the mcast aging timeout of 'ms' to 'idle_time' seconds. */
212 : : void
213 : 0 : mcast_snooping_set_idle_time(struct mcast_snooping *ms, unsigned int idle_time)
214 : : OVS_REQ_WRLOCK(ms->rwlock)
215 : : {
216 : : struct mcast_group *grp;
217 : : struct mcast_group_bundle *b;
218 : : int delta;
219 : :
220 : 0 : idle_time = normalize_idle_time(idle_time);
221 [ # # ]: 0 : if (idle_time != ms->idle_time) {
222 : 0 : delta = (int) idle_time - (int) ms->idle_time;
223 [ # # ]: 0 : LIST_FOR_EACH (grp, group_node, &ms->group_lru) {
224 [ # # ]: 0 : LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
225 : 0 : b->expires += delta;
226 : : }
227 : : }
228 : 0 : ms->idle_time = idle_time;
229 : : }
230 : 0 : }
231 : :
232 : : /* Sets the maximum number of entries in 'ms' to 'max_entries', adjusting it
233 : : * to be within a reasonable range. */
234 : : void
235 : 0 : mcast_snooping_set_max_entries(struct mcast_snooping *ms,
236 : : size_t max_entries)
237 : : OVS_REQ_WRLOCK(ms->rwlock)
238 : : {
239 : 0 : ms->max_entries = (max_entries < 10 ? 10
240 [ # # ]: 0 : : max_entries > 1000 * 1000 ? 1000 * 1000
241 : : : max_entries);
242 : 0 : }
243 : :
244 : : /* Sets if unregistered multicast packets should be flooded to
245 : : * all ports or only to ports connected to multicast routers
246 : : *
247 : : * Returns true if previous state differs from current state,
248 : : * false otherwise. */
249 : : bool
250 : 0 : mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable)
251 : : OVS_REQ_WRLOCK(ms->rwlock)
252 : : {
253 : 0 : bool prev = ms->flood_unreg;
254 : 0 : ms->flood_unreg = enable;
255 : 0 : return prev != enable;
256 : : }
257 : :
258 : : static struct mcast_group_bundle *
259 : 0 : mcast_group_bundle_lookup(struct mcast_snooping *ms OVS_UNUSED,
260 : : struct mcast_group *grp, void *port)
261 : : OVS_REQ_RDLOCK(ms->rwlock)
262 : : {
263 : : struct mcast_group_bundle *b;
264 : :
265 [ # # ]: 0 : LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
266 [ # # ]: 0 : if (b->port == port) {
267 : 0 : return b;
268 : : }
269 : : }
270 : 0 : return NULL;
271 : : }
272 : :
273 : : /* Insert a new bundle to the mcast group or update its
274 : : * position and expiration if it is already there. */
275 : : static struct mcast_group_bundle *
276 : 0 : mcast_group_insert_bundle(struct mcast_snooping *ms OVS_UNUSED,
277 : : struct mcast_group *grp, void *port, int idle_time)
278 : : OVS_REQ_WRLOCK(ms->rwlock)
279 : : {
280 : : struct mcast_group_bundle *b;
281 : :
282 : 0 : b = mcast_group_bundle_lookup(ms, grp, port);
283 [ # # ]: 0 : if (b) {
284 : 0 : ovs_list_remove(&b->bundle_node);
285 : : } else {
286 : 0 : b = xmalloc(sizeof *b);
287 : 0 : ovs_list_init(&b->bundle_node);
288 : 0 : b->port = port;
289 : 0 : ms->need_revalidate = true;
290 : : }
291 : :
292 : 0 : b->expires = time_now() + idle_time;
293 : 0 : ovs_list_push_back(&grp->bundle_lru, &b->bundle_node);
294 : 0 : return b;
295 : : }
296 : :
297 : : /* Return true if multicast still has bundles associated.
298 : : * Return false if there is no bundles. */
299 : : static bool
300 : 0 : mcast_group_has_bundles(struct mcast_group *grp)
301 : : {
302 : 0 : return !ovs_list_is_empty(&grp->bundle_lru);
303 : : }
304 : :
305 : : /* Delete 'grp' from the 'ms' hash table.
306 : : * Caller is responsible to clean bundle lru first. */
307 : : static void
308 : 0 : mcast_snooping_flush_group__(struct mcast_snooping *ms,
309 : : struct mcast_group *grp)
310 : : {
311 [ # # ]: 0 : ovs_assert(ovs_list_is_empty(&grp->bundle_lru));
312 : 0 : hmap_remove(&ms->table, &grp->hmap_node);
313 : 0 : ovs_list_remove(&grp->group_node);
314 : 0 : free(grp);
315 : 0 : }
316 : :
317 : : /* Flush out mcast group and its bundles */
318 : : static void
319 : 0 : mcast_snooping_flush_group(struct mcast_snooping *ms, struct mcast_group *grp)
320 : : OVS_REQ_WRLOCK(ms->rwlock)
321 : : {
322 : : struct mcast_group_bundle *b;
323 : :
324 [ # # ]: 0 : LIST_FOR_EACH_POP (b, bundle_node, &grp->bundle_lru) {
325 : 0 : free(b);
326 : : }
327 : 0 : mcast_snooping_flush_group__(ms, grp);
328 : 0 : ms->need_revalidate = true;
329 : 0 : }
330 : :
331 : :
332 : : /* Delete bundle returning true if it succeeds,
333 : : * false if it didn't find the group. */
334 : : static bool
335 : 0 : mcast_group_delete_bundle(struct mcast_snooping *ms OVS_UNUSED,
336 : : struct mcast_group *grp, void *port)
337 : : OVS_REQ_WRLOCK(ms->rwlock)
338 : : {
339 : : struct mcast_group_bundle *b;
340 : :
341 [ # # ]: 0 : LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) {
342 [ # # ]: 0 : if (b->port == port) {
343 : 0 : ovs_list_remove(&b->bundle_node);
344 : 0 : free(b);
345 : 0 : return true;
346 : : }
347 : : }
348 : 0 : return false;
349 : : }
350 : :
351 : : /* If any bundle has expired, delete it. Returns the number of deleted
352 : : * bundles. */
353 : : static int
354 : 0 : mcast_snooping_prune_expired(struct mcast_snooping *ms,
355 : : struct mcast_group *grp)
356 : : OVS_REQ_WRLOCK(ms->rwlock)
357 : : {
358 : : int expired;
359 : : struct mcast_group_bundle *b, *next_b;
360 : 0 : time_t timenow = time_now();
361 : :
362 : 0 : expired = 0;
363 [ # # ][ # # ]: 0 : LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) {
364 : : /* This list is sorted on expiration time. */
365 [ # # ]: 0 : if (b->expires > timenow) {
366 : 0 : break;
367 : : }
368 : 0 : ovs_list_remove(&b->bundle_node);
369 : 0 : free(b);
370 : 0 : expired++;
371 : : }
372 : :
373 [ # # ]: 0 : if (!mcast_group_has_bundles(grp)) {
374 : 0 : mcast_snooping_flush_group__(ms, grp);
375 : 0 : expired++;
376 : : }
377 : :
378 [ # # ]: 0 : if (expired) {
379 : 0 : ms->need_revalidate = true;
380 : 0 : COVERAGE_ADD(mcast_snooping_expired, expired);
381 : : }
382 : :
383 : 0 : return expired;
384 : : }
385 : :
386 : : /* Add a multicast group to the mdb. If it exists, then
387 : : * move to the last position in the LRU list.
388 : : */
389 : : bool
390 : 0 : mcast_snooping_add_group(struct mcast_snooping *ms,
391 : : const struct in6_addr *addr,
392 : : uint16_t vlan, void *port)
393 : : OVS_REQ_WRLOCK(ms->rwlock)
394 : : {
395 : : bool learned;
396 : : struct mcast_group *grp;
397 : :
398 : : /* Avoid duplicate packets. */
399 [ # # ]: 0 : if (mcast_snooping_mrouter_lookup(ms, vlan, port)
400 [ # # ]: 0 : || mcast_snooping_port_lookup(&ms->fport_list, port)) {
401 : 0 : return false;
402 : : }
403 : :
404 : 0 : learned = false;
405 : 0 : grp = mcast_snooping_lookup(ms, addr, vlan);
406 [ # # ]: 0 : if (!grp) {
407 : 0 : uint32_t hash = mcast_table_hash(ms, addr, vlan);
408 : :
409 [ # # ]: 0 : if (hmap_count(&ms->table) >= ms->max_entries) {
410 : 0 : group_get_lru(ms, &grp);
411 : 0 : mcast_snooping_flush_group(ms, grp);
412 : : }
413 : :
414 : 0 : grp = xmalloc(sizeof *grp);
415 : 0 : hmap_insert(&ms->table, &grp->hmap_node, hash);
416 : 0 : grp->addr = *addr;
417 : 0 : grp->vlan = vlan;
418 : 0 : ovs_list_init(&grp->bundle_lru);
419 : 0 : learned = true;
420 : 0 : ms->need_revalidate = true;
421 : 0 : COVERAGE_INC(mcast_snooping_learned);
422 : : } else {
423 : 0 : ovs_list_remove(&grp->group_node);
424 : : }
425 : 0 : mcast_group_insert_bundle(ms, grp, port, ms->idle_time);
426 : :
427 : : /* Mark 'grp' as recently used. */
428 : 0 : ovs_list_push_back(&ms->group_lru, &grp->group_node);
429 : 0 : return learned;
430 : : }
431 : :
432 : : bool
433 : 0 : mcast_snooping_add_group4(struct mcast_snooping *ms, ovs_be32 ip4,
434 : : uint16_t vlan, void *port)
435 : : OVS_REQ_WRLOCK(ms->rwlock)
436 : : {
437 : 0 : struct in6_addr addr = in6_addr_mapped_ipv4(ip4);
438 : 0 : return mcast_snooping_add_group(ms, &addr, vlan, port);
439 : : }
440 : :
441 : : int
442 : 0 : mcast_snooping_add_report(struct mcast_snooping *ms,
443 : : const struct dp_packet *p,
444 : : uint16_t vlan, void *port)
445 : : {
446 : : ovs_be32 ip4;
447 : : size_t offset;
448 : : const struct igmpv3_header *igmpv3;
449 : : const struct igmpv3_record *record;
450 : 0 : int count = 0;
451 : : int ngrp;
452 : :
453 : 0 : offset = (char *) dp_packet_l4(p) - (char *) dp_packet_data(p);
454 : 0 : igmpv3 = dp_packet_at(p, offset, IGMPV3_HEADER_LEN);
455 [ # # ]: 0 : if (!igmpv3) {
456 : 0 : return 0;
457 : : }
458 : 0 : ngrp = ntohs(igmpv3->ngrp);
459 : 0 : offset += IGMPV3_HEADER_LEN;
460 [ # # ]: 0 : while (ngrp--) {
461 : : bool ret;
462 : 0 : record = dp_packet_at(p, offset, sizeof(struct igmpv3_record));
463 [ # # ]: 0 : if (!record) {
464 : 0 : break;
465 : : }
466 : : /* Only consider known record types. */
467 [ # # ]: 0 : if (record->type < IGMPV3_MODE_IS_INCLUDE
468 [ # # ]: 0 : || record->type > IGMPV3_BLOCK_OLD_SOURCES) {
469 : 0 : continue;
470 : : }
471 : 0 : ip4 = get_16aligned_be32(&record->maddr);
472 : : /*
473 : : * If record is INCLUDE MODE and there are no sources, it's equivalent
474 : : * to a LEAVE.
475 : : */
476 [ # # ]: 0 : if (ntohs(record->nsrcs) == 0
477 [ # # ]: 0 : && (record->type == IGMPV3_MODE_IS_INCLUDE
478 [ # # ]: 0 : || record->type == IGMPV3_CHANGE_TO_INCLUDE_MODE)) {
479 : 0 : ret = mcast_snooping_leave_group4(ms, ip4, vlan, port);
480 : : } else {
481 : 0 : ret = mcast_snooping_add_group4(ms, ip4, vlan, port);
482 : : }
483 [ # # ]: 0 : if (ret) {
484 : 0 : count++;
485 : : }
486 : 0 : offset += sizeof(*record)
487 : 0 : + ntohs(record->nsrcs) * sizeof(ovs_be32) + record->aux_len;
488 : : }
489 : 0 : return count;
490 : : }
491 : :
492 : : int
493 : 0 : mcast_snooping_add_mld(struct mcast_snooping *ms,
494 : : const struct dp_packet *p,
495 : : uint16_t vlan, void *port)
496 : : {
497 : : const struct in6_addr *addr;
498 : : size_t offset;
499 : : const struct mld_header *mld;
500 : : const struct mld2_record *record;
501 : 0 : int count = 0;
502 : : int ngrp;
503 : : bool ret;
504 : :
505 : 0 : offset = (char *) dp_packet_l4(p) - (char *) dp_packet_data(p);
506 : 0 : mld = dp_packet_at(p, offset, MLD_HEADER_LEN);
507 [ # # ]: 0 : if (!mld) {
508 : 0 : return 0;
509 : : }
510 : 0 : ngrp = ntohs(mld->ngrp);
511 : 0 : offset += MLD_HEADER_LEN;
512 : 0 : addr = dp_packet_at(p, offset, sizeof(struct in6_addr));
513 : :
514 [ # # # # ]: 0 : switch (mld->type) {
515 : : case MLD_REPORT:
516 : 0 : ret = mcast_snooping_add_group(ms, addr, vlan, port);
517 [ # # ]: 0 : if (ret) {
518 : 0 : count++;
519 : : }
520 : 0 : break;
521 : : case MLD_DONE:
522 : 0 : ret = mcast_snooping_leave_group(ms, addr, vlan, port);
523 [ # # ]: 0 : if (ret) {
524 : 0 : count++;
525 : : }
526 : 0 : break;
527 : : case MLD2_REPORT:
528 [ # # ]: 0 : while (ngrp--) {
529 : 0 : record = dp_packet_at(p, offset, sizeof(struct mld2_record));
530 [ # # ]: 0 : if (!record) {
531 : 0 : break;
532 : : }
533 : : /* Only consider known record types. */
534 [ # # ]: 0 : if (record->type >= IGMPV3_MODE_IS_INCLUDE
535 [ # # ]: 0 : && record->type <= IGMPV3_BLOCK_OLD_SOURCES) {
536 : : struct in6_addr maddr;
537 : 0 : memcpy(maddr.s6_addr, record->maddr.be16, 16);
538 : 0 : addr = &maddr;
539 : : /*
540 : : * If record is INCLUDE MODE and there are no sources, it's
541 : : * equivalent to a LEAVE.
542 : : */
543 [ # # ]: 0 : if (record->nsrcs == htons(0)
544 [ # # ]: 0 : && (record->type == IGMPV3_MODE_IS_INCLUDE
545 [ # # ]: 0 : || record->type == IGMPV3_CHANGE_TO_INCLUDE_MODE)) {
546 : 0 : ret = mcast_snooping_leave_group(ms, addr, vlan, port);
547 : : } else {
548 : 0 : ret = mcast_snooping_add_group(ms, addr, vlan, port);
549 : : }
550 [ # # ]: 0 : if (ret) {
551 : 0 : count++;
552 : : }
553 : : }
554 : 0 : offset += sizeof(*record)
555 : 0 : + ntohs(record->nsrcs) * sizeof(struct in6_addr)
556 : 0 : + record->aux_len;
557 : : }
558 : : }
559 : :
560 : 0 : return count;
561 : : }
562 : :
563 : : bool
564 : 0 : mcast_snooping_leave_group(struct mcast_snooping *ms,
565 : : const struct in6_addr *addr,
566 : : uint16_t vlan, void *port)
567 : : OVS_REQ_WRLOCK(ms->rwlock)
568 : : {
569 : : struct mcast_group *grp;
570 : :
571 : : /* Ports flagged to forward Reports usually have more
572 : : * than one host behind it, so don't leave the group
573 : : * on the first message and just let it expire */
574 [ # # ]: 0 : if (mcast_snooping_port_lookup(&ms->rport_list, port)) {
575 : 0 : return false;
576 : : }
577 : :
578 : 0 : grp = mcast_snooping_lookup(ms, addr, vlan);
579 [ # # ][ # # ]: 0 : if (grp && mcast_group_delete_bundle(ms, grp, port)) {
580 : 0 : ms->need_revalidate = true;
581 : 0 : return true;
582 : : }
583 : 0 : return false;
584 : : }
585 : :
586 : : bool
587 : 0 : mcast_snooping_leave_group4(struct mcast_snooping *ms, ovs_be32 ip4,
588 : : uint16_t vlan, void *port)
589 : : {
590 : 0 : struct in6_addr addr = in6_addr_mapped_ipv4(ip4);
591 : 0 : return mcast_snooping_leave_group(ms, &addr, vlan, port);
592 : : }
593 : :
594 : :
595 : : /* Router ports. */
596 : :
597 : : /* Returns the number of seconds since the multicast router
598 : : * was learned in a port. */
599 : : int
600 : 0 : mcast_mrouter_age(const struct mcast_snooping *ms OVS_UNUSED,
601 : : const struct mcast_mrouter_bundle *mrouter)
602 : : {
603 : 0 : time_t remaining = mrouter->expires - time_now();
604 : 0 : return MCAST_MROUTER_PORT_IDLE_TIME - remaining;
605 : : }
606 : :
607 : : static struct mcast_mrouter_bundle *
608 : 0 : mcast_mrouter_from_lru_node(struct ovs_list *list)
609 : : {
610 : 0 : return CONTAINER_OF(list, struct mcast_mrouter_bundle, mrouter_node);
611 : : }
612 : :
613 : : /* If the LRU list is not empty, stores the least-recently-used mrouter
614 : : * in '*m' and returns true. Otherwise, if the LRU list is empty,
615 : : * stores NULL in '*m' and return false. */
616 : : static bool
617 : 0 : mrouter_get_lru(const struct mcast_snooping *ms,
618 : : struct mcast_mrouter_bundle **m)
619 : : OVS_REQ_RDLOCK(ms->rwlock)
620 : : {
621 [ # # ]: 0 : if (!ovs_list_is_empty(&ms->mrouter_lru)) {
622 : 0 : *m = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
623 : 0 : return true;
624 : : } else {
625 : 0 : *m = NULL;
626 : 0 : return false;
627 : : }
628 : : }
629 : :
630 : : static struct mcast_mrouter_bundle *
631 : 0 : mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan,
632 : : void *port)
633 : : OVS_REQ_RDLOCK(ms->rwlock)
634 : : {
635 : : struct mcast_mrouter_bundle *mrouter;
636 : :
637 [ # # ]: 0 : LIST_FOR_EACH (mrouter, mrouter_node, &ms->mrouter_lru) {
638 [ # # ][ # # ]: 0 : if (mrouter->vlan == vlan && mrouter->port == port) {
639 : 0 : return mrouter;
640 : : }
641 : : }
642 : 0 : return NULL;
643 : : }
644 : :
645 : : bool
646 : 0 : mcast_snooping_add_mrouter(struct mcast_snooping *ms, uint16_t vlan,
647 : : void *port)
648 : : OVS_REQ_WRLOCK(ms->rwlock)
649 : : {
650 : : struct mcast_mrouter_bundle *mrouter;
651 : :
652 : : /* Avoid duplicate packets. */
653 [ # # ]: 0 : if (mcast_snooping_port_lookup(&ms->fport_list, port)) {
654 : 0 : return false;
655 : : }
656 : :
657 : 0 : mrouter = mcast_snooping_mrouter_lookup(ms, vlan, port);
658 [ # # ]: 0 : if (mrouter) {
659 : 0 : ovs_list_remove(&mrouter->mrouter_node);
660 : : } else {
661 : 0 : mrouter = xmalloc(sizeof *mrouter);
662 : 0 : mrouter->vlan = vlan;
663 : 0 : mrouter->port = port;
664 : 0 : COVERAGE_INC(mcast_snooping_learned);
665 : 0 : ms->need_revalidate = true;
666 : : }
667 : :
668 : 0 : mrouter->expires = time_now() + MCAST_MROUTER_PORT_IDLE_TIME;
669 : 0 : ovs_list_push_back(&ms->mrouter_lru, &mrouter->mrouter_node);
670 : 0 : return ms->need_revalidate;
671 : : }
672 : :
673 : : static void
674 : 0 : mcast_snooping_flush_mrouter(struct mcast_mrouter_bundle *mrouter)
675 : : {
676 : 0 : ovs_list_remove(&mrouter->mrouter_node);
677 : 0 : free(mrouter);
678 : 0 : }
679 : :
680 : : /* Ports */
681 : :
682 : : static struct mcast_port_bundle *
683 : 0 : mcast_port_from_list_node(struct ovs_list *list)
684 : : {
685 : 0 : return CONTAINER_OF(list, struct mcast_port_bundle, node);
686 : : }
687 : :
688 : : /* If the list is not empty, stores the fport in '*f' and returns true.
689 : : * Otherwise, if the list is empty, stores NULL in '*f' and return false. */
690 : : static bool
691 : 0 : mcast_snooping_port_get(const struct ovs_list *list,
692 : : struct mcast_port_bundle **f)
693 : : {
694 [ # # ]: 0 : if (!ovs_list_is_empty(list)) {
695 : 0 : *f = mcast_port_from_list_node(list->next);
696 : 0 : return true;
697 : : } else {
698 : 0 : *f = NULL;
699 : 0 : return false;
700 : : }
701 : : }
702 : :
703 : : static struct mcast_port_bundle *
704 : 0 : mcast_snooping_port_lookup(struct ovs_list *list, void *port)
705 : : {
706 : : struct mcast_port_bundle *pbundle;
707 : :
708 [ # # ]: 0 : LIST_FOR_EACH (pbundle, node, list) {
709 [ # # ]: 0 : if (pbundle->port == port) {
710 : 0 : return pbundle;
711 : : }
712 : : }
713 : 0 : return NULL;
714 : : }
715 : :
716 : : static void
717 : 0 : mcast_snooping_add_port(struct ovs_list *list, void *port)
718 : : {
719 : : struct mcast_port_bundle *pbundle;
720 : :
721 : 0 : pbundle = xmalloc(sizeof *pbundle);
722 : 0 : pbundle->port = port;
723 : 0 : ovs_list_insert(list, &pbundle->node);
724 : 0 : }
725 : :
726 : : static void
727 : 0 : mcast_snooping_flush_port(struct mcast_port_bundle *pbundle)
728 : : {
729 : 0 : ovs_list_remove(&pbundle->node);
730 : 0 : free(pbundle);
731 : 0 : }
732 : :
733 : :
734 : : /* Flood ports. */
735 : : void
736 : 0 : mcast_snooping_set_port_flood(struct mcast_snooping *ms, void *port,
737 : : bool flood)
738 : : OVS_REQ_WRLOCK(ms->rwlock)
739 : : {
740 : : struct mcast_port_bundle *fbundle;
741 : :
742 : 0 : fbundle = mcast_snooping_port_lookup(&ms->fport_list, port);
743 [ # # ][ # # ]: 0 : if (flood && !fbundle) {
744 : 0 : mcast_snooping_add_port(&ms->fport_list, port);
745 : 0 : ms->need_revalidate = true;
746 [ # # ][ # # ]: 0 : } else if (!flood && fbundle) {
747 : 0 : mcast_snooping_flush_port(fbundle);
748 : 0 : ms->need_revalidate = true;
749 : : }
750 : 0 : }
751 : :
752 : : /* Flood Reports ports. */
753 : :
754 : : void
755 : 0 : mcast_snooping_set_port_flood_reports(struct mcast_snooping *ms, void *port,
756 : : bool flood)
757 : : OVS_REQ_WRLOCK(ms->rwlock)
758 : : {
759 : : struct mcast_port_bundle *pbundle;
760 : :
761 : 0 : pbundle = mcast_snooping_port_lookup(&ms->rport_list, port);
762 [ # # ][ # # ]: 0 : if (flood && !pbundle) {
763 : 0 : mcast_snooping_add_port(&ms->rport_list, port);
764 : 0 : ms->need_revalidate = true;
765 [ # # ][ # # ]: 0 : } else if (!flood && pbundle) {
766 : 0 : mcast_snooping_flush_port(pbundle);
767 : 0 : ms->need_revalidate = true;
768 : : }
769 : 0 : }
770 : :
771 : : /* Run and flush. */
772 : :
773 : : static void
774 : 0 : mcast_snooping_mdb_flush__(struct mcast_snooping *ms)
775 : : OVS_REQ_WRLOCK(ms->rwlock)
776 : : {
777 : : struct mcast_group *grp;
778 : : struct mcast_mrouter_bundle *mrouter;
779 : :
780 [ # # ]: 0 : while (group_get_lru(ms, &grp)) {
781 : 0 : mcast_snooping_flush_group(ms, grp);
782 : : }
783 : :
784 : 0 : hmap_shrink(&ms->table);
785 : :
786 [ # # ]: 0 : while (mrouter_get_lru(ms, &mrouter)) {
787 : 0 : mcast_snooping_flush_mrouter(mrouter);
788 : : }
789 : 0 : }
790 : :
791 : : void
792 : 7 : mcast_snooping_mdb_flush(struct mcast_snooping *ms)
793 : : {
794 [ + - ]: 7 : if (!mcast_snooping_enabled(ms)) {
795 : 7 : return;
796 : : }
797 : :
798 : 0 : ovs_rwlock_wrlock(&ms->rwlock);
799 : 0 : mcast_snooping_mdb_flush__(ms);
800 : 0 : ovs_rwlock_unlock(&ms->rwlock);
801 : : }
802 : :
803 : : /* Flushes mdb and flood ports. */
804 : : static void
805 : 0 : mcast_snooping_flush__(struct mcast_snooping *ms)
806 : : OVS_REQ_WRLOCK(ms->rwlock)
807 : : {
808 : : struct mcast_group *grp;
809 : : struct mcast_mrouter_bundle *mrouter;
810 : : struct mcast_port_bundle *pbundle;
811 : :
812 [ # # ]: 0 : while (group_get_lru(ms, &grp)) {
813 : 0 : mcast_snooping_flush_group(ms, grp);
814 : : }
815 : :
816 : 0 : hmap_shrink(&ms->table);
817 : :
818 : : /* flush multicast routers */
819 [ # # ]: 0 : while (mrouter_get_lru(ms, &mrouter)) {
820 : 0 : mcast_snooping_flush_mrouter(mrouter);
821 : : }
822 : :
823 : : /* flush flood ports */
824 [ # # ]: 0 : while (mcast_snooping_port_get(&ms->fport_list, &pbundle)) {
825 : 0 : mcast_snooping_flush_port(pbundle);
826 : : }
827 : :
828 : : /* flush flood report ports */
829 [ # # ]: 0 : while (mcast_snooping_port_get(&ms->rport_list, &pbundle)) {
830 : 0 : mcast_snooping_flush_port(pbundle);
831 : : }
832 : 0 : }
833 : :
834 : : void
835 : 0 : mcast_snooping_flush(struct mcast_snooping *ms)
836 : : {
837 [ # # ]: 0 : if (!mcast_snooping_enabled(ms)) {
838 : 0 : return;
839 : : }
840 : :
841 : 0 : ovs_rwlock_wrlock(&ms->rwlock);
842 : 0 : mcast_snooping_flush__(ms);
843 : 0 : ovs_rwlock_unlock(&ms->rwlock);
844 : : }
845 : :
846 : : static bool
847 : 0 : mcast_snooping_run__(struct mcast_snooping *ms)
848 : : OVS_REQ_WRLOCK(ms->rwlock)
849 : : {
850 : : bool need_revalidate;
851 : : struct mcast_group *grp;
852 : : struct mcast_mrouter_bundle *mrouter;
853 : : int mrouter_expired;
854 : :
855 [ # # ]: 0 : while (group_get_lru(ms, &grp)) {
856 [ # # ]: 0 : if (hmap_count(&ms->table) > ms->max_entries) {
857 : 0 : mcast_snooping_flush_group(ms, grp);
858 : : } else {
859 [ # # ]: 0 : if (!mcast_snooping_prune_expired(ms, grp)) {
860 : 0 : break;
861 : : }
862 : : }
863 : : }
864 : :
865 : 0 : hmap_shrink(&ms->table);
866 : :
867 : 0 : mrouter_expired = 0;
868 [ # # ]: 0 : while (mrouter_get_lru(ms, &mrouter)
869 [ # # ]: 0 : && time_now() >= mrouter->expires) {
870 : 0 : mcast_snooping_flush_mrouter(mrouter);
871 : 0 : mrouter_expired++;
872 : : }
873 : :
874 [ # # ]: 0 : if (mrouter_expired) {
875 : 0 : ms->need_revalidate = true;
876 : 0 : COVERAGE_ADD(mcast_snooping_expired, mrouter_expired);
877 : : }
878 : :
879 : 0 : need_revalidate = ms->need_revalidate;
880 : 0 : ms->need_revalidate = false;
881 : 0 : return need_revalidate;
882 : : }
883 : :
884 : : /* Does periodic work required by 'ms'. Returns true if something changed
885 : : * that may require flow revalidation. */
886 : : bool
887 : 157605 : mcast_snooping_run(struct mcast_snooping *ms)
888 : : {
889 : : bool need_revalidate;
890 : :
891 [ + - ]: 157605 : if (!mcast_snooping_enabled(ms)) {
892 : 157605 : return false;
893 : : }
894 : :
895 : 0 : ovs_rwlock_wrlock(&ms->rwlock);
896 : 0 : need_revalidate = mcast_snooping_run__(ms);
897 : 0 : ovs_rwlock_unlock(&ms->rwlock);
898 : :
899 : 0 : return need_revalidate;
900 : : }
901 : :
902 : : static void
903 : 0 : mcast_snooping_wait__(struct mcast_snooping *ms)
904 : : OVS_REQ_RDLOCK(ms->rwlock)
905 : : {
906 [ # # ]: 0 : if (hmap_count(&ms->table) > ms->max_entries
907 [ # # ]: 0 : || ms->need_revalidate) {
908 : 0 : poll_immediate_wake();
909 : : } else {
910 : : struct mcast_group *grp;
911 : : struct mcast_group_bundle *bundle;
912 : : struct mcast_mrouter_bundle *mrouter;
913 : : long long int mrouter_msec;
914 : 0 : long long int msec = 0;
915 : :
916 [ # # ]: 0 : if (!ovs_list_is_empty(&ms->group_lru)) {
917 : 0 : grp = mcast_group_from_lru_node(ms->group_lru.next);
918 : 0 : bundle = mcast_group_bundle_from_lru_node(grp->bundle_lru.next);
919 : 0 : msec = bundle->expires * 1000LL;
920 : : }
921 : :
922 [ # # ]: 0 : if (!ovs_list_is_empty(&ms->mrouter_lru)) {
923 : 0 : mrouter = mcast_mrouter_from_lru_node(ms->mrouter_lru.next);
924 : 0 : mrouter_msec = mrouter->expires * 1000LL;
925 [ # # ]: 0 : msec = msec ? MIN(msec, mrouter_msec) : mrouter_msec;
926 : : }
927 : :
928 [ # # ]: 0 : if (msec) {
929 : 0 : poll_timer_wait_until(msec);
930 : : }
931 : : }
932 : 0 : }
933 : :
934 : : void
935 : 153567 : mcast_snooping_wait(struct mcast_snooping *ms)
936 : : {
937 [ + - ]: 153567 : if (!mcast_snooping_enabled(ms)) {
938 : 153567 : return;
939 : : }
940 : :
941 : 0 : ovs_rwlock_rdlock(&ms->rwlock);
942 : 0 : mcast_snooping_wait__(ms);
943 : 0 : ovs_rwlock_unlock(&ms->rwlock);
944 : : }
|