]>
Commit | Line | Data |
---|---|---|
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" | |
b19bab5b | 29 | #include "openvswitch/list.h" |
4a95091d FL |
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" | |
e6211adc | 36 | #include "openvswitch/vlog.h" |
4a95091d FL |
37 | |
38 | COVERAGE_DEFINE(mcast_snooping_learned); | |
39 | COVERAGE_DEFINE(mcast_snooping_expired); | |
40 | ||
f4ae6e23 FL |
41 | static struct mcast_port_bundle * |
42 | mcast_snooping_port_lookup(struct ovs_list *list, void *port); | |
4a95091d FL |
43 | static struct mcast_mrouter_bundle * |
44 | mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan, | |
45 | void *port) | |
46 | OVS_REQ_RDLOCK(ms->rwlock); | |
47 | ||
48 | bool | |
49 | mcast_snooping_enabled(const struct mcast_snooping *ms) | |
50 | { | |
51 | return !!ms; | |
52 | } | |
53 | ||
54 | bool | |
55 | mcast_snooping_flood_unreg(const struct mcast_snooping *ms) | |
56 | { | |
57 | return ms->flood_unreg; | |
58 | } | |
59 | ||
60 | bool | |
61 | mcast_snooping_is_query(ovs_be16 igmp_type) | |
62 | { | |
63 | return igmp_type == htons(IGMP_HOST_MEMBERSHIP_QUERY); | |
64 | } | |
65 | ||
66 | bool | |
67 | mcast_snooping_is_membership(ovs_be16 igmp_type) | |
68 | { | |
69 | switch (ntohs(igmp_type)) { | |
70 | case IGMP_HOST_MEMBERSHIP_REPORT: | |
71 | case IGMPV2_HOST_MEMBERSHIP_REPORT: | |
e3102e42 | 72 | case IGMPV3_HOST_MEMBERSHIP_REPORT: |
4a95091d FL |
73 | case IGMP_HOST_LEAVE_MESSAGE: |
74 | return true; | |
75 | } | |
76 | 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 | mcast_bundle_age(const struct mcast_snooping *ms, | |
83 | const struct mcast_group_bundle *b) | |
84 | { | |
85 | time_t remaining = b->expires - time_now(); | |
86 | return ms->idle_time - remaining; | |
87 | } | |
88 | ||
89 | static uint32_t | |
964a4d5f TLSC |
90 | mcast_table_hash(const struct mcast_snooping *ms, |
91 | const struct in6_addr *grp_addr, uint16_t vlan) | |
4a95091d | 92 | { |
964a4d5f TLSC |
93 | return hash_bytes(grp_addr->s6_addr, 16, |
94 | hash_2words(ms->secret, vlan)); | |
4a95091d FL |
95 | } |
96 | ||
97 | static struct mcast_group_bundle * | |
ca6ba700 | 98 | mcast_group_bundle_from_lru_node(struct ovs_list *list) |
4a95091d FL |
99 | { |
100 | return CONTAINER_OF(list, struct mcast_group_bundle, bundle_node); | |
101 | } | |
102 | ||
103 | static struct mcast_group * | |
ca6ba700 | 104 | mcast_group_from_lru_node(struct ovs_list *list) |
4a95091d FL |
105 | { |
106 | 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 * | |
964a4d5f TLSC |
112 | mcast_snooping_lookup(const struct mcast_snooping *ms, |
113 | const struct in6_addr *dip, uint16_t vlan) | |
4a95091d FL |
114 | OVS_REQ_RDLOCK(ms->rwlock) |
115 | { | |
116 | struct mcast_group *grp; | |
117 | uint32_t hash; | |
118 | ||
119 | hash = mcast_table_hash(ms, dip, vlan); | |
120 | HMAP_FOR_EACH_WITH_HASH (grp, hmap_node, hash, &ms->table) { | |
964a4d5f | 121 | if (grp->vlan == vlan && ipv6_addr_equals(&grp->addr, dip)) { |
4a95091d FL |
122 | return grp; |
123 | } | |
124 | } | |
125 | return NULL; | |
126 | } | |
127 | ||
964a4d5f TLSC |
128 | struct mcast_group * |
129 | mcast_snooping_lookup4(const struct mcast_snooping *ms, ovs_be32 ip4, | |
130 | uint16_t vlan) | |
131 | OVS_REQ_RDLOCK(ms->rwlock) | |
132 | { | |
12d0ee08 | 133 | struct in6_addr addr = in6_addr_mapped_ipv4(ip4); |
964a4d5f TLSC |
134 | return mcast_snooping_lookup(ms, &addr, vlan); |
135 | } | |
136 | ||
4a95091d FL |
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 | group_get_lru(const struct mcast_snooping *ms, struct mcast_group **grp) | |
142 | OVS_REQ_RDLOCK(ms->rwlock) | |
143 | { | |
417e7e66 | 144 | if (!ovs_list_is_empty(&ms->group_lru)) { |
4a95091d FL |
145 | *grp = mcast_group_from_lru_node(ms->group_lru.next); |
146 | return true; | |
147 | } else { | |
148 | *grp = NULL; | |
149 | return false; | |
150 | } | |
151 | } | |
152 | ||
153 | static unsigned int | |
154 | normalize_idle_time(unsigned int idle_time) | |
155 | { | |
156 | return (idle_time < 15 ? 15 | |
157 | : 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 | mcast_snooping_create(void) | |
166 | { | |
167 | struct mcast_snooping *ms; | |
168 | ||
169 | ms = xmalloc(sizeof *ms); | |
170 | hmap_init(&ms->table); | |
417e7e66 BW |
171 | ovs_list_init(&ms->group_lru); |
172 | ovs_list_init(&ms->mrouter_lru); | |
173 | ovs_list_init(&ms->fport_list); | |
174 | ovs_list_init(&ms->rport_list); | |
4a95091d FL |
175 | ms->secret = random_uint32(); |
176 | ms->idle_time = MCAST_ENTRY_DEFAULT_IDLE_TIME; | |
177 | ms->max_entries = MCAST_DEFAULT_MAX_ENTRIES; | |
178 | ms->need_revalidate = false; | |
179 | ms->flood_unreg = true; | |
180 | ovs_refcount_init(&ms->ref_cnt); | |
181 | ovs_rwlock_init(&ms->rwlock); | |
182 | return ms; | |
183 | } | |
184 | ||
185 | struct mcast_snooping * | |
186 | mcast_snooping_ref(const struct mcast_snooping *ms_) | |
187 | { | |
188 | struct mcast_snooping *ms = CONST_CAST(struct mcast_snooping *, ms_); | |
189 | if (ms) { | |
190 | ovs_refcount_ref(&ms->ref_cnt); | |
191 | } | |
192 | return ms; | |
193 | } | |
194 | ||
195 | /* Unreferences (and possibly destroys) mcast snooping table 'ms'. */ | |
196 | void | |
197 | mcast_snooping_unref(struct mcast_snooping *ms) | |
198 | { | |
199 | if (!mcast_snooping_enabled(ms)) { | |
200 | return; | |
201 | } | |
202 | ||
24f83812 | 203 | if (ovs_refcount_unref_relaxed(&ms->ref_cnt) == 1) { |
4a95091d FL |
204 | mcast_snooping_flush(ms); |
205 | hmap_destroy(&ms->table); | |
206 | ovs_rwlock_destroy(&ms->rwlock); | |
207 | free(ms); | |
208 | } | |
209 | } | |
210 | ||
211 | /* Changes the mcast aging timeout of 'ms' to 'idle_time' seconds. */ | |
212 | void | |
213 | 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 | idle_time = normalize_idle_time(idle_time); | |
221 | if (idle_time != ms->idle_time) { | |
222 | delta = (int) idle_time - (int) ms->idle_time; | |
223 | LIST_FOR_EACH (grp, group_node, &ms->group_lru) { | |
224 | LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) { | |
225 | b->expires += delta; | |
226 | } | |
227 | } | |
228 | ms->idle_time = idle_time; | |
229 | } | |
230 | } | |
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 | mcast_snooping_set_max_entries(struct mcast_snooping *ms, | |
236 | size_t max_entries) | |
237 | OVS_REQ_WRLOCK(ms->rwlock) | |
238 | { | |
239 | ms->max_entries = (max_entries < 10 ? 10 | |
240 | : max_entries > 1000 * 1000 ? 1000 * 1000 | |
241 | : max_entries); | |
242 | } | |
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 | mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable) | |
251 | OVS_REQ_WRLOCK(ms->rwlock) | |
252 | { | |
253 | bool prev = ms->flood_unreg; | |
254 | ms->flood_unreg = enable; | |
255 | return prev != enable; | |
256 | } | |
257 | ||
258 | static struct mcast_group_bundle * | |
259 | 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 | LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) { | |
266 | if (b->port == port) { | |
267 | return b; | |
268 | } | |
269 | } | |
270 | 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 | 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 | b = mcast_group_bundle_lookup(ms, grp, port); | |
283 | if (b) { | |
417e7e66 | 284 | ovs_list_remove(&b->bundle_node); |
4a95091d FL |
285 | } else { |
286 | b = xmalloc(sizeof *b); | |
417e7e66 | 287 | ovs_list_init(&b->bundle_node); |
4a95091d | 288 | b->port = port; |
04418350 | 289 | ms->need_revalidate = true; |
4a95091d FL |
290 | } |
291 | ||
292 | b->expires = time_now() + idle_time; | |
417e7e66 | 293 | ovs_list_push_back(&grp->bundle_lru, &b->bundle_node); |
4a95091d FL |
294 | 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 | mcast_group_has_bundles(struct mcast_group *grp) | |
301 | { | |
417e7e66 | 302 | return !ovs_list_is_empty(&grp->bundle_lru); |
4a95091d FL |
303 | } |
304 | ||
305 | /* Delete 'grp' from the 'ms' hash table. | |
306 | * Caller is responsible to clean bundle lru first. */ | |
307 | static void | |
308 | mcast_snooping_flush_group__(struct mcast_snooping *ms, | |
309 | struct mcast_group *grp) | |
310 | { | |
417e7e66 | 311 | ovs_assert(ovs_list_is_empty(&grp->bundle_lru)); |
4a95091d | 312 | hmap_remove(&ms->table, &grp->hmap_node); |
417e7e66 | 313 | ovs_list_remove(&grp->group_node); |
4a95091d FL |
314 | free(grp); |
315 | } | |
316 | ||
317 | /* Flush out mcast group and its bundles */ | |
318 | static void | |
319 | mcast_snooping_flush_group(struct mcast_snooping *ms, struct mcast_group *grp) | |
320 | OVS_REQ_WRLOCK(ms->rwlock) | |
321 | { | |
5f03c983 | 322 | struct mcast_group_bundle *b; |
4a95091d | 323 | |
5f03c983 | 324 | LIST_FOR_EACH_POP (b, bundle_node, &grp->bundle_lru) { |
4a95091d FL |
325 | free(b); |
326 | } | |
327 | mcast_snooping_flush_group__(ms, grp); | |
328 | ms->need_revalidate = true; | |
329 | } | |
330 | ||
331 | ||
332 | /* Delete bundle returning true if it succeeds, | |
333 | * false if it didn't find the group. */ | |
334 | static bool | |
335 | 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 | LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) { | |
342 | if (b->port == port) { | |
417e7e66 | 343 | ovs_list_remove(&b->bundle_node); |
4a95091d FL |
344 | free(b); |
345 | return true; | |
346 | } | |
347 | } | |
348 | return false; | |
349 | } | |
350 | ||
351 | /* If any bundle has expired, delete it. Returns the number of deleted | |
352 | * bundles. */ | |
353 | static int | |
354 | 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 | time_t timenow = time_now(); | |
361 | ||
362 | expired = 0; | |
363 | LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) { | |
364 | /* This list is sorted on expiration time. */ | |
365 | if (b->expires > timenow) { | |
366 | break; | |
367 | } | |
417e7e66 | 368 | ovs_list_remove(&b->bundle_node); |
4a95091d FL |
369 | free(b); |
370 | expired++; | |
371 | } | |
372 | ||
373 | if (!mcast_group_has_bundles(grp)) { | |
374 | mcast_snooping_flush_group__(ms, grp); | |
375 | expired++; | |
376 | } | |
377 | ||
378 | if (expired) { | |
379 | ms->need_revalidate = true; | |
380 | COVERAGE_ADD(mcast_snooping_expired, expired); | |
381 | } | |
382 | ||
383 | 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 | |
964a4d5f TLSC |
390 | mcast_snooping_add_group(struct mcast_snooping *ms, |
391 | const struct in6_addr *addr, | |
4a95091d FL |
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 | if (mcast_snooping_mrouter_lookup(ms, vlan, port) | |
f4ae6e23 | 400 | || mcast_snooping_port_lookup(&ms->fport_list, port)) { |
4a95091d FL |
401 | return false; |
402 | } | |
403 | ||
404 | learned = false; | |
964a4d5f | 405 | grp = mcast_snooping_lookup(ms, addr, vlan); |
4a95091d | 406 | if (!grp) { |
964a4d5f | 407 | uint32_t hash = mcast_table_hash(ms, addr, vlan); |
4a95091d FL |
408 | |
409 | if (hmap_count(&ms->table) >= ms->max_entries) { | |
410 | group_get_lru(ms, &grp); | |
411 | mcast_snooping_flush_group(ms, grp); | |
412 | } | |
413 | ||
414 | grp = xmalloc(sizeof *grp); | |
415 | hmap_insert(&ms->table, &grp->hmap_node, hash); | |
964a4d5f | 416 | grp->addr = *addr; |
4a95091d | 417 | grp->vlan = vlan; |
417e7e66 | 418 | ovs_list_init(&grp->bundle_lru); |
4a95091d FL |
419 | learned = true; |
420 | ms->need_revalidate = true; | |
421 | COVERAGE_INC(mcast_snooping_learned); | |
422 | } else { | |
417e7e66 | 423 | ovs_list_remove(&grp->group_node); |
4a95091d FL |
424 | } |
425 | mcast_group_insert_bundle(ms, grp, port, ms->idle_time); | |
426 | ||
427 | /* Mark 'grp' as recently used. */ | |
417e7e66 | 428 | ovs_list_push_back(&ms->group_lru, &grp->group_node); |
4a95091d FL |
429 | return learned; |
430 | } | |
431 | ||
964a4d5f TLSC |
432 | bool |
433 | mcast_snooping_add_group4(struct mcast_snooping *ms, ovs_be32 ip4, | |
434 | uint16_t vlan, void *port) | |
435 | OVS_REQ_WRLOCK(ms->rwlock) | |
436 | { | |
12d0ee08 | 437 | struct in6_addr addr = in6_addr_mapped_ipv4(ip4); |
964a4d5f TLSC |
438 | return mcast_snooping_add_group(ms, &addr, vlan, port); |
439 | } | |
440 | ||
e3102e42 TLSC |
441 | int |
442 | 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 | int count = 0; | |
451 | int ngrp; | |
452 | ||
453 | offset = (char *) dp_packet_l4(p) - (char *) dp_packet_data(p); | |
454 | igmpv3 = dp_packet_at(p, offset, IGMPV3_HEADER_LEN); | |
455 | if (!igmpv3) { | |
456 | return 0; | |
457 | } | |
458 | ngrp = ntohs(igmpv3->ngrp); | |
459 | offset += IGMPV3_HEADER_LEN; | |
460 | while (ngrp--) { | |
461 | bool ret; | |
462 | record = dp_packet_at(p, offset, sizeof(struct igmpv3_record)); | |
463 | if (!record) { | |
464 | break; | |
465 | } | |
466 | /* Only consider known record types. */ | |
467 | if (record->type < IGMPV3_MODE_IS_INCLUDE | |
468 | || record->type > IGMPV3_BLOCK_OLD_SOURCES) { | |
469 | continue; | |
470 | } | |
471 | 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 | if (ntohs(record->nsrcs) == 0 | |
477 | && (record->type == IGMPV3_MODE_IS_INCLUDE | |
478 | || record->type == IGMPV3_CHANGE_TO_INCLUDE_MODE)) { | |
964a4d5f | 479 | ret = mcast_snooping_leave_group4(ms, ip4, vlan, port); |
e3102e42 | 480 | } else { |
964a4d5f | 481 | ret = mcast_snooping_add_group4(ms, ip4, vlan, port); |
e3102e42 TLSC |
482 | } |
483 | if (ret) { | |
484 | count++; | |
485 | } | |
486 | offset += sizeof(*record) | |
487 | + ntohs(record->nsrcs) * sizeof(ovs_be32) + record->aux_len; | |
488 | } | |
489 | return count; | |
490 | } | |
491 | ||
06994f87 TLSC |
492 | int |
493 | 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 | int count = 0; | |
502 | int ngrp; | |
503 | bool ret; | |
504 | ||
505 | offset = (char *) dp_packet_l4(p) - (char *) dp_packet_data(p); | |
506 | mld = dp_packet_at(p, offset, MLD_HEADER_LEN); | |
507 | if (!mld) { | |
508 | return 0; | |
509 | } | |
510 | ngrp = ntohs(mld->ngrp); | |
511 | offset += MLD_HEADER_LEN; | |
512 | addr = dp_packet_at(p, offset, sizeof(struct in6_addr)); | |
513 | ||
514 | switch (mld->type) { | |
515 | case MLD_REPORT: | |
516 | ret = mcast_snooping_add_group(ms, addr, vlan, port); | |
517 | if (ret) { | |
518 | count++; | |
519 | } | |
520 | break; | |
521 | case MLD_DONE: | |
522 | ret = mcast_snooping_leave_group(ms, addr, vlan, port); | |
523 | if (ret) { | |
524 | count++; | |
525 | } | |
526 | break; | |
527 | case MLD2_REPORT: | |
528 | while (ngrp--) { | |
529 | record = dp_packet_at(p, offset, sizeof(struct mld2_record)); | |
530 | if (!record) { | |
531 | break; | |
532 | } | |
533 | /* Only consider known record types. */ | |
534 | if (record->type >= IGMPV3_MODE_IS_INCLUDE | |
535 | && record->type <= IGMPV3_BLOCK_OLD_SOURCES) { | |
536 | struct in6_addr maddr; | |
537 | memcpy(maddr.s6_addr, record->maddr.be16, 16); | |
538 | addr = &maddr; | |
539 | /* | |
540 | * If record is INCLUDE MODE and there are no sources, it's | |
541 | * equivalent to a LEAVE. | |
542 | */ | |
543 | if (record->nsrcs == htons(0) | |
544 | && (record->type == IGMPV3_MODE_IS_INCLUDE | |
545 | || record->type == IGMPV3_CHANGE_TO_INCLUDE_MODE)) { | |
546 | ret = mcast_snooping_leave_group(ms, addr, vlan, port); | |
547 | } else { | |
548 | ret = mcast_snooping_add_group(ms, addr, vlan, port); | |
549 | } | |
550 | if (ret) { | |
551 | count++; | |
552 | } | |
553 | } | |
554 | offset += sizeof(*record) | |
555 | + ntohs(record->nsrcs) * sizeof(struct in6_addr) | |
556 | + record->aux_len; | |
557 | } | |
558 | } | |
559 | ||
560 | return count; | |
561 | } | |
562 | ||
4a95091d | 563 | bool |
964a4d5f TLSC |
564 | mcast_snooping_leave_group(struct mcast_snooping *ms, |
565 | const struct in6_addr *addr, | |
4a95091d FL |
566 | uint16_t vlan, void *port) |
567 | OVS_REQ_WRLOCK(ms->rwlock) | |
568 | { | |
569 | struct mcast_group *grp; | |
570 | ||
8e04a33f FL |
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 | if (mcast_snooping_port_lookup(&ms->rport_list, port)) { | |
575 | return false; | |
576 | } | |
577 | ||
964a4d5f | 578 | grp = mcast_snooping_lookup(ms, addr, vlan); |
4a95091d FL |
579 | if (grp && mcast_group_delete_bundle(ms, grp, port)) { |
580 | ms->need_revalidate = true; | |
581 | return true; | |
582 | } | |
583 | return false; | |
584 | } | |
585 | ||
964a4d5f TLSC |
586 | bool |
587 | mcast_snooping_leave_group4(struct mcast_snooping *ms, ovs_be32 ip4, | |
588 | uint16_t vlan, void *port) | |
589 | { | |
12d0ee08 | 590 | struct in6_addr addr = in6_addr_mapped_ipv4(ip4); |
964a4d5f TLSC |
591 | return mcast_snooping_leave_group(ms, &addr, vlan, port); |
592 | } | |
593 | ||
4a95091d FL |
594 | \f |
595 | /* Router ports. */ | |
596 | ||
597 | /* Returns the number of seconds since the multicast router | |
598 | * was learned in a port. */ | |
599 | int | |
600 | mcast_mrouter_age(const struct mcast_snooping *ms OVS_UNUSED, | |
601 | const struct mcast_mrouter_bundle *mrouter) | |
602 | { | |
603 | time_t remaining = mrouter->expires - time_now(); | |
604 | return MCAST_MROUTER_PORT_IDLE_TIME - remaining; | |
605 | } | |
606 | ||
607 | static struct mcast_mrouter_bundle * | |
ca6ba700 | 608 | mcast_mrouter_from_lru_node(struct ovs_list *list) |
4a95091d FL |
609 | { |
610 | 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 | mrouter_get_lru(const struct mcast_snooping *ms, | |
618 | struct mcast_mrouter_bundle **m) | |
619 | OVS_REQ_RDLOCK(ms->rwlock) | |
620 | { | |
417e7e66 | 621 | if (!ovs_list_is_empty(&ms->mrouter_lru)) { |
4a95091d FL |
622 | *m = mcast_mrouter_from_lru_node(ms->mrouter_lru.next); |
623 | return true; | |
624 | } else { | |
625 | *m = NULL; | |
626 | return false; | |
627 | } | |
628 | } | |
629 | ||
630 | static struct mcast_mrouter_bundle * | |
631 | 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 | LIST_FOR_EACH (mrouter, mrouter_node, &ms->mrouter_lru) { | |
638 | if (mrouter->vlan == vlan && mrouter->port == port) { | |
639 | return mrouter; | |
640 | } | |
641 | } | |
642 | return NULL; | |
643 | } | |
644 | ||
645 | bool | |
646 | 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. */ | |
f4ae6e23 | 653 | if (mcast_snooping_port_lookup(&ms->fport_list, port)) { |
4a95091d FL |
654 | return false; |
655 | } | |
656 | ||
657 | mrouter = mcast_snooping_mrouter_lookup(ms, vlan, port); | |
658 | if (mrouter) { | |
417e7e66 | 659 | ovs_list_remove(&mrouter->mrouter_node); |
4a95091d FL |
660 | } else { |
661 | mrouter = xmalloc(sizeof *mrouter); | |
662 | mrouter->vlan = vlan; | |
663 | mrouter->port = port; | |
664 | COVERAGE_INC(mcast_snooping_learned); | |
665 | ms->need_revalidate = true; | |
666 | } | |
667 | ||
668 | mrouter->expires = time_now() + MCAST_MROUTER_PORT_IDLE_TIME; | |
417e7e66 | 669 | ovs_list_push_back(&ms->mrouter_lru, &mrouter->mrouter_node); |
4a95091d FL |
670 | return ms->need_revalidate; |
671 | } | |
672 | ||
673 | static void | |
674 | mcast_snooping_flush_mrouter(struct mcast_mrouter_bundle *mrouter) | |
675 | { | |
417e7e66 | 676 | ovs_list_remove(&mrouter->mrouter_node); |
4a95091d FL |
677 | free(mrouter); |
678 | } | |
679 | \f | |
f4ae6e23 | 680 | /* Ports */ |
4a95091d | 681 | |
f4ae6e23 FL |
682 | static struct mcast_port_bundle * |
683 | mcast_port_from_list_node(struct ovs_list *list) | |
4a95091d | 684 | { |
f4ae6e23 | 685 | return CONTAINER_OF(list, struct mcast_port_bundle, node); |
4a95091d FL |
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 | |
f4ae6e23 FL |
691 | mcast_snooping_port_get(const struct ovs_list *list, |
692 | struct mcast_port_bundle **f) | |
4a95091d | 693 | { |
417e7e66 | 694 | if (!ovs_list_is_empty(list)) { |
f4ae6e23 | 695 | *f = mcast_port_from_list_node(list->next); |
4a95091d FL |
696 | return true; |
697 | } else { | |
698 | *f = NULL; | |
699 | return false; | |
700 | } | |
701 | } | |
702 | ||
f4ae6e23 FL |
703 | static struct mcast_port_bundle * |
704 | mcast_snooping_port_lookup(struct ovs_list *list, void *port) | |
4a95091d | 705 | { |
f4ae6e23 | 706 | struct mcast_port_bundle *pbundle; |
4a95091d | 707 | |
f4ae6e23 FL |
708 | LIST_FOR_EACH (pbundle, node, list) { |
709 | if (pbundle->port == port) { | |
710 | return pbundle; | |
4a95091d FL |
711 | } |
712 | } | |
713 | return NULL; | |
714 | } | |
715 | ||
716 | static void | |
f4ae6e23 | 717 | mcast_snooping_add_port(struct ovs_list *list, void *port) |
4a95091d | 718 | { |
f4ae6e23 | 719 | struct mcast_port_bundle *pbundle; |
4a95091d | 720 | |
f4ae6e23 FL |
721 | pbundle = xmalloc(sizeof *pbundle); |
722 | pbundle->port = port; | |
417e7e66 | 723 | ovs_list_insert(list, &pbundle->node); |
4a95091d FL |
724 | } |
725 | ||
726 | static void | |
f4ae6e23 | 727 | mcast_snooping_flush_port(struct mcast_port_bundle *pbundle) |
4a95091d | 728 | { |
417e7e66 | 729 | ovs_list_remove(&pbundle->node); |
f4ae6e23 | 730 | free(pbundle); |
4a95091d FL |
731 | } |
732 | ||
f4ae6e23 FL |
733 | \f |
734 | /* Flood ports. */ | |
4a95091d | 735 | void |
f4ae6e23 FL |
736 | mcast_snooping_set_port_flood(struct mcast_snooping *ms, void *port, |
737 | bool flood) | |
4a95091d FL |
738 | OVS_REQ_WRLOCK(ms->rwlock) |
739 | { | |
f4ae6e23 | 740 | struct mcast_port_bundle *fbundle; |
4a95091d | 741 | |
f4ae6e23 FL |
742 | fbundle = mcast_snooping_port_lookup(&ms->fport_list, port); |
743 | if (flood && !fbundle) { | |
744 | mcast_snooping_add_port(&ms->fport_list, port); | |
4a95091d | 745 | ms->need_revalidate = true; |
f4ae6e23 FL |
746 | } else if (!flood && fbundle) { |
747 | mcast_snooping_flush_port(fbundle); | |
4a95091d FL |
748 | ms->need_revalidate = true; |
749 | } | |
750 | } | |
751 | \f | |
8e04a33f FL |
752 | /* Flood Reports ports. */ |
753 | ||
754 | void | |
755 | 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 | pbundle = mcast_snooping_port_lookup(&ms->rport_list, port); | |
762 | if (flood && !pbundle) { | |
763 | mcast_snooping_add_port(&ms->rport_list, port); | |
764 | ms->need_revalidate = true; | |
765 | } else if (!flood && pbundle) { | |
766 | mcast_snooping_flush_port(pbundle); | |
767 | ms->need_revalidate = true; | |
768 | } | |
769 | } | |
770 | \f | |
4a95091d FL |
771 | /* Run and flush. */ |
772 | ||
773 | static void | |
774 | 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 | while (group_get_lru(ms, &grp)) { | |
781 | mcast_snooping_flush_group(ms, grp); | |
782 | } | |
783 | ||
784 | hmap_shrink(&ms->table); | |
785 | ||
786 | while (mrouter_get_lru(ms, &mrouter)) { | |
787 | mcast_snooping_flush_mrouter(mrouter); | |
788 | } | |
789 | } | |
790 | ||
791 | void | |
792 | mcast_snooping_mdb_flush(struct mcast_snooping *ms) | |
793 | { | |
794 | if (!mcast_snooping_enabled(ms)) { | |
795 | return; | |
796 | } | |
797 | ||
798 | ovs_rwlock_wrlock(&ms->rwlock); | |
799 | mcast_snooping_mdb_flush__(ms); | |
800 | ovs_rwlock_unlock(&ms->rwlock); | |
801 | } | |
802 | ||
803 | /* Flushes mdb and flood ports. */ | |
804 | static void | |
805 | 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; | |
f4ae6e23 | 810 | struct mcast_port_bundle *pbundle; |
4a95091d FL |
811 | |
812 | while (group_get_lru(ms, &grp)) { | |
813 | mcast_snooping_flush_group(ms, grp); | |
814 | } | |
815 | ||
816 | hmap_shrink(&ms->table); | |
817 | ||
8e04a33f | 818 | /* flush multicast routers */ |
4a95091d FL |
819 | while (mrouter_get_lru(ms, &mrouter)) { |
820 | mcast_snooping_flush_mrouter(mrouter); | |
821 | } | |
822 | ||
8e04a33f | 823 | /* flush flood ports */ |
f4ae6e23 FL |
824 | while (mcast_snooping_port_get(&ms->fport_list, &pbundle)) { |
825 | mcast_snooping_flush_port(pbundle); | |
4a95091d | 826 | } |
8e04a33f FL |
827 | |
828 | /* flush flood report ports */ | |
829 | while (mcast_snooping_port_get(&ms->rport_list, &pbundle)) { | |
830 | mcast_snooping_flush_port(pbundle); | |
831 | } | |
4a95091d FL |
832 | } |
833 | ||
834 | void | |
835 | mcast_snooping_flush(struct mcast_snooping *ms) | |
836 | { | |
837 | if (!mcast_snooping_enabled(ms)) { | |
838 | return; | |
839 | } | |
840 | ||
841 | ovs_rwlock_wrlock(&ms->rwlock); | |
842 | mcast_snooping_flush__(ms); | |
843 | ovs_rwlock_unlock(&ms->rwlock); | |
844 | } | |
845 | ||
846 | static bool | |
847 | 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 | while (group_get_lru(ms, &grp)) { | |
856 | if (hmap_count(&ms->table) > ms->max_entries) { | |
857 | mcast_snooping_flush_group(ms, grp); | |
858 | } else { | |
859 | if (!mcast_snooping_prune_expired(ms, grp)) { | |
860 | break; | |
861 | } | |
862 | } | |
863 | } | |
864 | ||
865 | hmap_shrink(&ms->table); | |
866 | ||
867 | mrouter_expired = 0; | |
868 | while (mrouter_get_lru(ms, &mrouter) | |
869 | && time_now() >= mrouter->expires) { | |
870 | mcast_snooping_flush_mrouter(mrouter); | |
871 | mrouter_expired++; | |
872 | } | |
873 | ||
874 | if (mrouter_expired) { | |
875 | ms->need_revalidate = true; | |
876 | COVERAGE_ADD(mcast_snooping_expired, mrouter_expired); | |
877 | } | |
878 | ||
879 | need_revalidate = ms->need_revalidate; | |
880 | ms->need_revalidate = false; | |
881 | 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 | mcast_snooping_run(struct mcast_snooping *ms) | |
888 | { | |
889 | bool need_revalidate; | |
890 | ||
891 | if (!mcast_snooping_enabled(ms)) { | |
892 | return false; | |
893 | } | |
894 | ||
895 | ovs_rwlock_wrlock(&ms->rwlock); | |
896 | need_revalidate = mcast_snooping_run__(ms); | |
897 | ovs_rwlock_unlock(&ms->rwlock); | |
898 | ||
899 | return need_revalidate; | |
900 | } | |
901 | ||
902 | static void | |
903 | mcast_snooping_wait__(struct mcast_snooping *ms) | |
904 | OVS_REQ_RDLOCK(ms->rwlock) | |
905 | { | |
906 | if (hmap_count(&ms->table) > ms->max_entries | |
907 | || ms->need_revalidate) { | |
908 | 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 | long long int msec = 0; | |
915 | ||
417e7e66 | 916 | if (!ovs_list_is_empty(&ms->group_lru)) { |
4a95091d FL |
917 | grp = mcast_group_from_lru_node(ms->group_lru.next); |
918 | bundle = mcast_group_bundle_from_lru_node(grp->bundle_lru.next); | |
919 | msec = bundle->expires * 1000LL; | |
920 | } | |
921 | ||
417e7e66 | 922 | if (!ovs_list_is_empty(&ms->mrouter_lru)) { |
4a95091d FL |
923 | mrouter = mcast_mrouter_from_lru_node(ms->mrouter_lru.next); |
924 | mrouter_msec = mrouter->expires * 1000LL; | |
925 | msec = msec ? MIN(msec, mrouter_msec) : mrouter_msec; | |
926 | } | |
927 | ||
928 | if (msec) { | |
929 | poll_timer_wait_until(msec); | |
930 | } | |
931 | } | |
932 | } | |
933 | ||
934 | void | |
935 | mcast_snooping_wait(struct mcast_snooping *ms) | |
936 | { | |
937 | if (!mcast_snooping_enabled(ms)) { | |
938 | return; | |
939 | } | |
940 | ||
941 | ovs_rwlock_rdlock(&ms->rwlock); | |
942 | mcast_snooping_wait__(ms); | |
943 | ovs_rwlock_unlock(&ms->rwlock); | |
944 | } |