]>
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" | |
29 | #include "list.h" | |
30 | #include "poll-loop.h" | |
31 | #include "timeval.h" | |
32 | #include "entropy.h" | |
33 | #include "unaligned.h" | |
34 | #include "util.h" | |
35 | #include "vlan-bitmap.h" | |
36 | #include "vlog.h" | |
37 | ||
38 | COVERAGE_DEFINE(mcast_snooping_learned); | |
39 | COVERAGE_DEFINE(mcast_snooping_expired); | |
40 | ||
41 | static struct mcast_mrouter_bundle * | |
42 | mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan, | |
43 | void *port) | |
44 | OVS_REQ_RDLOCK(ms->rwlock); | |
45 | ||
46 | bool | |
47 | mcast_snooping_enabled(const struct mcast_snooping *ms) | |
48 | { | |
49 | return !!ms; | |
50 | } | |
51 | ||
52 | bool | |
53 | mcast_snooping_flood_unreg(const struct mcast_snooping *ms) | |
54 | { | |
55 | return ms->flood_unreg; | |
56 | } | |
57 | ||
58 | bool | |
59 | mcast_snooping_is_query(ovs_be16 igmp_type) | |
60 | { | |
61 | return igmp_type == htons(IGMP_HOST_MEMBERSHIP_QUERY); | |
62 | } | |
63 | ||
64 | bool | |
65 | mcast_snooping_is_membership(ovs_be16 igmp_type) | |
66 | { | |
67 | switch (ntohs(igmp_type)) { | |
68 | case IGMP_HOST_MEMBERSHIP_REPORT: | |
69 | case IGMPV2_HOST_MEMBERSHIP_REPORT: | |
70 | case IGMP_HOST_LEAVE_MESSAGE: | |
71 | return true; | |
72 | } | |
73 | return false; | |
74 | } | |
75 | ||
76 | /* Returns the number of seconds since multicast group 'b' was learned in a | |
77 | * port on 'ms'. */ | |
78 | int | |
79 | mcast_bundle_age(const struct mcast_snooping *ms, | |
80 | const struct mcast_group_bundle *b) | |
81 | { | |
82 | time_t remaining = b->expires - time_now(); | |
83 | return ms->idle_time - remaining; | |
84 | } | |
85 | ||
86 | static uint32_t | |
87 | mcast_table_hash(const struct mcast_snooping *ms, ovs_be32 grp_ip4, | |
88 | uint16_t vlan) | |
89 | { | |
90 | return hash_3words((OVS_FORCE uint32_t) grp_ip4, vlan, ms->secret); | |
91 | } | |
92 | ||
93 | static struct mcast_group_bundle * | |
94 | mcast_group_bundle_from_lru_node(struct list *list) | |
95 | { | |
96 | return CONTAINER_OF(list, struct mcast_group_bundle, bundle_node); | |
97 | } | |
98 | ||
99 | static struct mcast_group * | |
100 | mcast_group_from_lru_node(struct list *list) | |
101 | { | |
102 | return CONTAINER_OF(list, struct mcast_group, group_node); | |
103 | } | |
104 | ||
105 | /* Searches 'ms' for and returns an mcast group for destination address | |
106 | * 'dip' in 'vlan'. */ | |
107 | struct mcast_group * | |
108 | mcast_snooping_lookup(const struct mcast_snooping *ms, ovs_be32 dip, | |
109 | uint16_t vlan) | |
110 | OVS_REQ_RDLOCK(ms->rwlock) | |
111 | { | |
112 | struct mcast_group *grp; | |
113 | uint32_t hash; | |
114 | ||
115 | hash = mcast_table_hash(ms, dip, vlan); | |
116 | HMAP_FOR_EACH_WITH_HASH (grp, hmap_node, hash, &ms->table) { | |
117 | if (grp->vlan == vlan && grp->ip4 == dip) { | |
118 | return grp; | |
119 | } | |
120 | } | |
121 | return NULL; | |
122 | } | |
123 | ||
124 | /* If the LRU list is not empty, stores the least-recently-used entry | |
125 | * in '*e' and returns true. Otherwise, if the LRU list is empty, | |
126 | * stores NULL in '*e' and return false. */ | |
127 | static bool | |
128 | group_get_lru(const struct mcast_snooping *ms, struct mcast_group **grp) | |
129 | OVS_REQ_RDLOCK(ms->rwlock) | |
130 | { | |
131 | if (!list_is_empty(&ms->group_lru)) { | |
132 | *grp = mcast_group_from_lru_node(ms->group_lru.next); | |
133 | return true; | |
134 | } else { | |
135 | *grp = NULL; | |
136 | return false; | |
137 | } | |
138 | } | |
139 | ||
140 | static unsigned int | |
141 | normalize_idle_time(unsigned int idle_time) | |
142 | { | |
143 | return (idle_time < 15 ? 15 | |
144 | : idle_time > 3600 ? 3600 | |
145 | : idle_time); | |
146 | } | |
147 | ||
148 | /* Creates and returns a new mcast table with an initial mcast aging | |
149 | * timeout of MCAST_ENTRY_DEFAULT_IDLE_TIME seconds and an initial maximum of | |
150 | * MCAST_DEFAULT_MAX entries. */ | |
151 | struct mcast_snooping * | |
152 | mcast_snooping_create(void) | |
153 | { | |
154 | struct mcast_snooping *ms; | |
155 | ||
156 | ms = xmalloc(sizeof *ms); | |
157 | hmap_init(&ms->table); | |
158 | list_init(&ms->group_lru); | |
159 | list_init(&ms->mrouter_lru); | |
160 | list_init(&ms->fport_list); | |
161 | ms->secret = random_uint32(); | |
162 | ms->idle_time = MCAST_ENTRY_DEFAULT_IDLE_TIME; | |
163 | ms->max_entries = MCAST_DEFAULT_MAX_ENTRIES; | |
164 | ms->need_revalidate = false; | |
165 | ms->flood_unreg = true; | |
166 | ovs_refcount_init(&ms->ref_cnt); | |
167 | ovs_rwlock_init(&ms->rwlock); | |
168 | return ms; | |
169 | } | |
170 | ||
171 | struct mcast_snooping * | |
172 | mcast_snooping_ref(const struct mcast_snooping *ms_) | |
173 | { | |
174 | struct mcast_snooping *ms = CONST_CAST(struct mcast_snooping *, ms_); | |
175 | if (ms) { | |
176 | ovs_refcount_ref(&ms->ref_cnt); | |
177 | } | |
178 | return ms; | |
179 | } | |
180 | ||
181 | /* Unreferences (and possibly destroys) mcast snooping table 'ms'. */ | |
182 | void | |
183 | mcast_snooping_unref(struct mcast_snooping *ms) | |
184 | { | |
185 | if (!mcast_snooping_enabled(ms)) { | |
186 | return; | |
187 | } | |
188 | ||
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. */ | |
198 | void | |
199 | mcast_snooping_set_idle_time(struct mcast_snooping *ms, unsigned int idle_time) | |
200 | OVS_REQ_WRLOCK(ms->rwlock) | |
201 | { | |
202 | struct mcast_group *grp; | |
203 | struct mcast_group_bundle *b; | |
204 | int delta; | |
205 | ||
206 | idle_time = normalize_idle_time(idle_time); | |
207 | if (idle_time != ms->idle_time) { | |
208 | delta = (int) idle_time - (int) ms->idle_time; | |
209 | LIST_FOR_EACH (grp, group_node, &ms->group_lru) { | |
210 | LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) { | |
211 | b->expires += delta; | |
212 | } | |
213 | } | |
214 | ms->idle_time = idle_time; | |
215 | } | |
216 | } | |
217 | ||
218 | /* Sets the maximum number of entries in 'ms' to 'max_entries', adjusting it | |
219 | * to be within a reasonable range. */ | |
220 | void | |
221 | mcast_snooping_set_max_entries(struct mcast_snooping *ms, | |
222 | size_t max_entries) | |
223 | OVS_REQ_WRLOCK(ms->rwlock) | |
224 | { | |
225 | ms->max_entries = (max_entries < 10 ? 10 | |
226 | : max_entries > 1000 * 1000 ? 1000 * 1000 | |
227 | : max_entries); | |
228 | } | |
229 | ||
230 | /* Sets if unregistered multicast packets should be flooded to | |
231 | * all ports or only to ports connected to multicast routers | |
232 | * | |
233 | * Returns true if previous state differs from current state, | |
234 | * false otherwise. */ | |
235 | bool | |
236 | mcast_snooping_set_flood_unreg(struct mcast_snooping *ms, bool enable) | |
237 | OVS_REQ_WRLOCK(ms->rwlock) | |
238 | { | |
239 | bool prev = ms->flood_unreg; | |
240 | ms->flood_unreg = enable; | |
241 | return prev != enable; | |
242 | } | |
243 | ||
244 | static struct mcast_group_bundle * | |
245 | mcast_group_bundle_lookup(struct mcast_snooping *ms OVS_UNUSED, | |
246 | struct mcast_group *grp, void *port) | |
247 | OVS_REQ_RDLOCK(ms->rwlock) | |
248 | { | |
249 | struct mcast_group_bundle *b; | |
250 | ||
251 | LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) { | |
252 | if (b->port == port) { | |
253 | return b; | |
254 | } | |
255 | } | |
256 | return NULL; | |
257 | } | |
258 | ||
259 | /* Insert a new bundle to the mcast group or update its | |
260 | * position and expiration if it is already there. */ | |
261 | static struct mcast_group_bundle * | |
262 | mcast_group_insert_bundle(struct mcast_snooping *ms OVS_UNUSED, | |
263 | struct mcast_group *grp, void *port, int idle_time) | |
264 | OVS_REQ_WRLOCK(ms->rwlock) | |
265 | { | |
266 | struct mcast_group_bundle *b; | |
267 | ||
268 | b = mcast_group_bundle_lookup(ms, grp, port); | |
269 | if (b) { | |
270 | list_remove(&b->bundle_node); | |
271 | } else { | |
272 | b = xmalloc(sizeof *b); | |
273 | list_init(&b->bundle_node); | |
274 | b->port = port; | |
275 | } | |
276 | ||
277 | b->expires = time_now() + idle_time; | |
278 | list_push_back(&grp->bundle_lru, &b->bundle_node); | |
279 | return b; | |
280 | } | |
281 | ||
282 | /* Return true if multicast still has bundles associated. | |
283 | * Return false if there is no bundles. */ | |
284 | static bool | |
285 | mcast_group_has_bundles(struct mcast_group *grp) | |
286 | { | |
287 | return !list_is_empty(&grp->bundle_lru); | |
288 | } | |
289 | ||
290 | /* Delete 'grp' from the 'ms' hash table. | |
291 | * Caller is responsible to clean bundle lru first. */ | |
292 | static void | |
293 | mcast_snooping_flush_group__(struct mcast_snooping *ms, | |
294 | struct mcast_group *grp) | |
295 | { | |
296 | ovs_assert(list_is_empty(&grp->bundle_lru)); | |
297 | hmap_remove(&ms->table, &grp->hmap_node); | |
298 | list_remove(&grp->group_node); | |
299 | free(grp); | |
300 | } | |
301 | ||
302 | /* Flush out mcast group and its bundles */ | |
303 | static void | |
304 | mcast_snooping_flush_group(struct mcast_snooping *ms, struct mcast_group *grp) | |
305 | OVS_REQ_WRLOCK(ms->rwlock) | |
306 | { | |
307 | struct mcast_group_bundle *b, *next_b; | |
308 | ||
309 | LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) { | |
310 | list_remove(&b->bundle_node); | |
311 | free(b); | |
312 | } | |
313 | mcast_snooping_flush_group__(ms, grp); | |
314 | ms->need_revalidate = true; | |
315 | } | |
316 | ||
317 | ||
318 | /* Delete bundle returning true if it succeeds, | |
319 | * false if it didn't find the group. */ | |
320 | static bool | |
321 | mcast_group_delete_bundle(struct mcast_snooping *ms OVS_UNUSED, | |
322 | struct mcast_group *grp, void *port) | |
323 | OVS_REQ_WRLOCK(ms->rwlock) | |
324 | { | |
325 | struct mcast_group_bundle *b; | |
326 | ||
327 | LIST_FOR_EACH (b, bundle_node, &grp->bundle_lru) { | |
328 | if (b->port == port) { | |
329 | list_remove(&b->bundle_node); | |
330 | free(b); | |
331 | return true; | |
332 | } | |
333 | } | |
334 | return false; | |
335 | } | |
336 | ||
337 | /* If any bundle has expired, delete it. Returns the number of deleted | |
338 | * bundles. */ | |
339 | static int | |
340 | mcast_snooping_prune_expired(struct mcast_snooping *ms, | |
341 | struct mcast_group *grp) | |
342 | OVS_REQ_WRLOCK(ms->rwlock) | |
343 | { | |
344 | int expired; | |
345 | struct mcast_group_bundle *b, *next_b; | |
346 | time_t timenow = time_now(); | |
347 | ||
348 | expired = 0; | |
349 | LIST_FOR_EACH_SAFE (b, next_b, bundle_node, &grp->bundle_lru) { | |
350 | /* This list is sorted on expiration time. */ | |
351 | if (b->expires > timenow) { | |
352 | break; | |
353 | } | |
354 | list_remove(&b->bundle_node); | |
355 | free(b); | |
356 | expired++; | |
357 | } | |
358 | ||
359 | if (!mcast_group_has_bundles(grp)) { | |
360 | mcast_snooping_flush_group__(ms, grp); | |
361 | expired++; | |
362 | } | |
363 | ||
364 | if (expired) { | |
365 | ms->need_revalidate = true; | |
366 | COVERAGE_ADD(mcast_snooping_expired, expired); | |
367 | } | |
368 | ||
369 | return expired; | |
370 | } | |
371 | ||
372 | /* Add a multicast group to the mdb. If it exists, then | |
373 | * move to the last position in the LRU list. | |
374 | */ | |
375 | bool | |
376 | mcast_snooping_add_group(struct mcast_snooping *ms, ovs_be32 ip4, | |
377 | uint16_t vlan, void *port) | |
378 | OVS_REQ_WRLOCK(ms->rwlock) | |
379 | { | |
380 | bool learned; | |
381 | struct mcast_group *grp; | |
382 | ||
383 | /* Avoid duplicate packets. */ | |
384 | if (mcast_snooping_mrouter_lookup(ms, vlan, port) | |
385 | || mcast_snooping_fport_lookup(ms, vlan, port)) { | |
386 | return false; | |
387 | } | |
388 | ||
389 | learned = false; | |
390 | grp = mcast_snooping_lookup(ms, ip4, vlan); | |
391 | if (!grp) { | |
392 | uint32_t hash = mcast_table_hash(ms, ip4, vlan); | |
393 | ||
394 | if (hmap_count(&ms->table) >= ms->max_entries) { | |
395 | group_get_lru(ms, &grp); | |
396 | mcast_snooping_flush_group(ms, grp); | |
397 | } | |
398 | ||
399 | grp = xmalloc(sizeof *grp); | |
400 | hmap_insert(&ms->table, &grp->hmap_node, hash); | |
401 | grp->ip4 = ip4; | |
402 | grp->vlan = vlan; | |
403 | list_init(&grp->bundle_lru); | |
404 | learned = true; | |
405 | ms->need_revalidate = true; | |
406 | COVERAGE_INC(mcast_snooping_learned); | |
407 | } else { | |
408 | list_remove(&grp->group_node); | |
409 | } | |
410 | mcast_group_insert_bundle(ms, grp, port, ms->idle_time); | |
411 | ||
412 | /* Mark 'grp' as recently used. */ | |
413 | list_push_back(&ms->group_lru, &grp->group_node); | |
414 | return learned; | |
415 | } | |
416 | ||
417 | bool | |
418 | mcast_snooping_leave_group(struct mcast_snooping *ms, ovs_be32 ip4, | |
419 | uint16_t vlan, void *port) | |
420 | OVS_REQ_WRLOCK(ms->rwlock) | |
421 | { | |
422 | struct mcast_group *grp; | |
423 | ||
424 | grp = mcast_snooping_lookup(ms, ip4, vlan); | |
425 | if (grp && mcast_group_delete_bundle(ms, grp, port)) { | |
426 | ms->need_revalidate = true; | |
427 | return true; | |
428 | } | |
429 | return false; | |
430 | } | |
431 | ||
432 | \f | |
433 | /* Router ports. */ | |
434 | ||
435 | /* Returns the number of seconds since the multicast router | |
436 | * was learned in a port. */ | |
437 | int | |
438 | mcast_mrouter_age(const struct mcast_snooping *ms OVS_UNUSED, | |
439 | const struct mcast_mrouter_bundle *mrouter) | |
440 | { | |
441 | time_t remaining = mrouter->expires - time_now(); | |
442 | return MCAST_MROUTER_PORT_IDLE_TIME - remaining; | |
443 | } | |
444 | ||
445 | static struct mcast_mrouter_bundle * | |
446 | mcast_mrouter_from_lru_node(struct list *list) | |
447 | { | |
448 | return CONTAINER_OF(list, struct mcast_mrouter_bundle, mrouter_node); | |
449 | } | |
450 | ||
451 | /* If the LRU list is not empty, stores the least-recently-used mrouter | |
452 | * in '*m' and returns true. Otherwise, if the LRU list is empty, | |
453 | * stores NULL in '*m' and return false. */ | |
454 | static bool | |
455 | mrouter_get_lru(const struct mcast_snooping *ms, | |
456 | struct mcast_mrouter_bundle **m) | |
457 | OVS_REQ_RDLOCK(ms->rwlock) | |
458 | { | |
459 | if (!list_is_empty(&ms->mrouter_lru)) { | |
460 | *m = mcast_mrouter_from_lru_node(ms->mrouter_lru.next); | |
461 | return true; | |
462 | } else { | |
463 | *m = NULL; | |
464 | return false; | |
465 | } | |
466 | } | |
467 | ||
468 | static struct mcast_mrouter_bundle * | |
469 | mcast_snooping_mrouter_lookup(struct mcast_snooping *ms, uint16_t vlan, | |
470 | void *port) | |
471 | OVS_REQ_RDLOCK(ms->rwlock) | |
472 | { | |
473 | struct mcast_mrouter_bundle *mrouter; | |
474 | ||
475 | LIST_FOR_EACH (mrouter, mrouter_node, &ms->mrouter_lru) { | |
476 | if (mrouter->vlan == vlan && mrouter->port == port) { | |
477 | return mrouter; | |
478 | } | |
479 | } | |
480 | return NULL; | |
481 | } | |
482 | ||
483 | bool | |
484 | mcast_snooping_add_mrouter(struct mcast_snooping *ms, uint16_t vlan, | |
485 | void *port) | |
486 | OVS_REQ_WRLOCK(ms->rwlock) | |
487 | { | |
488 | struct mcast_mrouter_bundle *mrouter; | |
489 | ||
490 | /* Avoid duplicate packets. */ | |
491 | if (mcast_snooping_fport_lookup(ms, vlan, port)) { | |
492 | return false; | |
493 | } | |
494 | ||
495 | mrouter = mcast_snooping_mrouter_lookup(ms, vlan, port); | |
496 | if (mrouter) { | |
497 | list_remove(&mrouter->mrouter_node); | |
498 | } else { | |
499 | mrouter = xmalloc(sizeof *mrouter); | |
500 | mrouter->vlan = vlan; | |
501 | mrouter->port = port; | |
502 | COVERAGE_INC(mcast_snooping_learned); | |
503 | ms->need_revalidate = true; | |
504 | } | |
505 | ||
506 | mrouter->expires = time_now() + MCAST_MROUTER_PORT_IDLE_TIME; | |
507 | list_push_back(&ms->mrouter_lru, &mrouter->mrouter_node); | |
508 | return ms->need_revalidate; | |
509 | } | |
510 | ||
511 | static void | |
512 | mcast_snooping_flush_mrouter(struct mcast_mrouter_bundle *mrouter) | |
513 | { | |
514 | list_remove(&mrouter->mrouter_node); | |
515 | free(mrouter); | |
516 | } | |
517 | \f | |
518 | /* Flood ports. */ | |
519 | ||
520 | static struct mcast_fport_bundle * | |
521 | mcast_fport_from_list_node(struct list *list) | |
522 | { | |
523 | return CONTAINER_OF(list, struct mcast_fport_bundle, fport_node); | |
524 | } | |
525 | ||
526 | /* If the list is not empty, stores the fport in '*f' and returns true. | |
527 | * Otherwise, if the list is empty, stores NULL in '*f' and return false. */ | |
528 | static bool | |
529 | fport_get(const struct mcast_snooping *ms, struct mcast_fport_bundle **f) | |
530 | OVS_REQ_RDLOCK(ms->rwlock) | |
531 | { | |
532 | if (!list_is_empty(&ms->fport_list)) { | |
533 | *f = mcast_fport_from_list_node(ms->fport_list.next); | |
534 | return true; | |
535 | } else { | |
536 | *f = NULL; | |
537 | return false; | |
538 | } | |
539 | } | |
540 | ||
541 | struct mcast_fport_bundle * | |
542 | mcast_snooping_fport_lookup(struct mcast_snooping *ms, uint16_t vlan, | |
543 | void *port) | |
544 | OVS_REQ_RDLOCK(ms->rwlock) | |
545 | { | |
546 | struct mcast_fport_bundle *fport; | |
547 | ||
548 | LIST_FOR_EACH (fport, fport_node, &ms->fport_list) { | |
549 | if (fport->vlan == vlan && fport->port == port) { | |
550 | return fport; | |
551 | } | |
552 | } | |
553 | return NULL; | |
554 | } | |
555 | ||
556 | static void | |
557 | mcast_snooping_add_fport(struct mcast_snooping *ms, uint16_t vlan, void *port) | |
558 | OVS_REQ_WRLOCK(ms->rwlock) | |
559 | { | |
560 | struct mcast_fport_bundle *fport; | |
561 | ||
562 | fport = xmalloc(sizeof *fport); | |
563 | fport->vlan = vlan; | |
564 | fport->port = port; | |
565 | list_insert(&ms->fport_list, &fport->fport_node); | |
566 | } | |
567 | ||
568 | static void | |
569 | mcast_snooping_flush_fport(struct mcast_fport_bundle *fport) | |
570 | { | |
571 | list_remove(&fport->fport_node); | |
572 | free(fport); | |
573 | } | |
574 | ||
575 | void | |
576 | mcast_snooping_set_port_flood(struct mcast_snooping *ms, uint16_t vlan, | |
577 | void *port, bool flood) | |
578 | OVS_REQ_WRLOCK(ms->rwlock) | |
579 | { | |
580 | struct mcast_fport_bundle *fport; | |
581 | ||
582 | fport = mcast_snooping_fport_lookup(ms, vlan, port); | |
583 | if (flood && !fport) { | |
584 | mcast_snooping_add_fport(ms, vlan, port); | |
585 | ms->need_revalidate = true; | |
586 | } else if (!flood && fport) { | |
587 | mcast_snooping_flush_fport(fport); | |
588 | ms->need_revalidate = true; | |
589 | } | |
590 | } | |
591 | \f | |
592 | /* Run and flush. */ | |
593 | ||
594 | static void | |
595 | mcast_snooping_mdb_flush__(struct mcast_snooping *ms) | |
596 | OVS_REQ_WRLOCK(ms->rwlock) | |
597 | { | |
598 | struct mcast_group *grp; | |
599 | struct mcast_mrouter_bundle *mrouter; | |
600 | ||
601 | while (group_get_lru(ms, &grp)) { | |
602 | mcast_snooping_flush_group(ms, grp); | |
603 | } | |
604 | ||
605 | hmap_shrink(&ms->table); | |
606 | ||
607 | while (mrouter_get_lru(ms, &mrouter)) { | |
608 | mcast_snooping_flush_mrouter(mrouter); | |
609 | } | |
610 | } | |
611 | ||
612 | void | |
613 | mcast_snooping_mdb_flush(struct mcast_snooping *ms) | |
614 | { | |
615 | if (!mcast_snooping_enabled(ms)) { | |
616 | return; | |
617 | } | |
618 | ||
619 | ovs_rwlock_wrlock(&ms->rwlock); | |
620 | mcast_snooping_mdb_flush__(ms); | |
621 | ovs_rwlock_unlock(&ms->rwlock); | |
622 | } | |
623 | ||
624 | /* Flushes mdb and flood ports. */ | |
625 | static void | |
626 | mcast_snooping_flush__(struct mcast_snooping *ms) | |
627 | OVS_REQ_WRLOCK(ms->rwlock) | |
628 | { | |
629 | struct mcast_group *grp; | |
630 | struct mcast_mrouter_bundle *mrouter; | |
631 | struct mcast_fport_bundle *fport; | |
632 | ||
633 | while (group_get_lru(ms, &grp)) { | |
634 | mcast_snooping_flush_group(ms, grp); | |
635 | } | |
636 | ||
637 | hmap_shrink(&ms->table); | |
638 | ||
639 | while (mrouter_get_lru(ms, &mrouter)) { | |
640 | mcast_snooping_flush_mrouter(mrouter); | |
641 | } | |
642 | ||
643 | while (fport_get(ms, &fport)) { | |
644 | mcast_snooping_flush_fport(fport); | |
645 | } | |
646 | } | |
647 | ||
648 | void | |
649 | mcast_snooping_flush(struct mcast_snooping *ms) | |
650 | { | |
651 | if (!mcast_snooping_enabled(ms)) { | |
652 | return; | |
653 | } | |
654 | ||
655 | ovs_rwlock_wrlock(&ms->rwlock); | |
656 | mcast_snooping_flush__(ms); | |
657 | ovs_rwlock_unlock(&ms->rwlock); | |
658 | } | |
659 | ||
660 | static bool | |
661 | mcast_snooping_run__(struct mcast_snooping *ms) | |
662 | OVS_REQ_WRLOCK(ms->rwlock) | |
663 | { | |
664 | bool need_revalidate; | |
665 | struct mcast_group *grp; | |
666 | struct mcast_mrouter_bundle *mrouter; | |
667 | int mrouter_expired; | |
668 | ||
669 | while (group_get_lru(ms, &grp)) { | |
670 | if (hmap_count(&ms->table) > ms->max_entries) { | |
671 | mcast_snooping_flush_group(ms, grp); | |
672 | } else { | |
673 | if (!mcast_snooping_prune_expired(ms, grp)) { | |
674 | break; | |
675 | } | |
676 | } | |
677 | } | |
678 | ||
679 | hmap_shrink(&ms->table); | |
680 | ||
681 | mrouter_expired = 0; | |
682 | while (mrouter_get_lru(ms, &mrouter) | |
683 | && time_now() >= mrouter->expires) { | |
684 | mcast_snooping_flush_mrouter(mrouter); | |
685 | mrouter_expired++; | |
686 | } | |
687 | ||
688 | if (mrouter_expired) { | |
689 | ms->need_revalidate = true; | |
690 | COVERAGE_ADD(mcast_snooping_expired, mrouter_expired); | |
691 | } | |
692 | ||
693 | need_revalidate = ms->need_revalidate; | |
694 | ms->need_revalidate = false; | |
695 | return need_revalidate; | |
696 | } | |
697 | ||
698 | /* Does periodic work required by 'ms'. Returns true if something changed | |
699 | * that may require flow revalidation. */ | |
700 | bool | |
701 | mcast_snooping_run(struct mcast_snooping *ms) | |
702 | { | |
703 | bool need_revalidate; | |
704 | ||
705 | if (!mcast_snooping_enabled(ms)) { | |
706 | return false; | |
707 | } | |
708 | ||
709 | ovs_rwlock_wrlock(&ms->rwlock); | |
710 | need_revalidate = mcast_snooping_run__(ms); | |
711 | ovs_rwlock_unlock(&ms->rwlock); | |
712 | ||
713 | return need_revalidate; | |
714 | } | |
715 | ||
716 | static void | |
717 | mcast_snooping_wait__(struct mcast_snooping *ms) | |
718 | OVS_REQ_RDLOCK(ms->rwlock) | |
719 | { | |
720 | if (hmap_count(&ms->table) > ms->max_entries | |
721 | || ms->need_revalidate) { | |
722 | poll_immediate_wake(); | |
723 | } else { | |
724 | struct mcast_group *grp; | |
725 | struct mcast_group_bundle *bundle; | |
726 | struct mcast_mrouter_bundle *mrouter; | |
727 | long long int mrouter_msec; | |
728 | long long int msec = 0; | |
729 | ||
730 | if (!list_is_empty(&ms->group_lru)) { | |
731 | grp = mcast_group_from_lru_node(ms->group_lru.next); | |
732 | bundle = mcast_group_bundle_from_lru_node(grp->bundle_lru.next); | |
733 | msec = bundle->expires * 1000LL; | |
734 | } | |
735 | ||
736 | if (!list_is_empty(&ms->mrouter_lru)) { | |
737 | mrouter = mcast_mrouter_from_lru_node(ms->mrouter_lru.next); | |
738 | mrouter_msec = mrouter->expires * 1000LL; | |
739 | msec = msec ? MIN(msec, mrouter_msec) : mrouter_msec; | |
740 | } | |
741 | ||
742 | if (msec) { | |
743 | poll_timer_wait_until(msec); | |
744 | } | |
745 | } | |
746 | } | |
747 | ||
748 | void | |
749 | mcast_snooping_wait(struct mcast_snooping *ms) | |
750 | { | |
751 | if (!mcast_snooping_enabled(ms)) { | |
752 | return; | |
753 | } | |
754 | ||
755 | ovs_rwlock_rdlock(&ms->rwlock); | |
756 | mcast_snooping_wait__(ms); | |
757 | ovs_rwlock_unlock(&ms->rwlock); | |
758 | } |