Line data Source code
1 : /*
2 : *------------------------------------------------------------------
3 : * Copyright (c) 2017 Cisco and/or its affiliates.
4 : * Licensed under the Apache License, Version 2.0 (the "License");
5 : * you may not use this file except in compliance with the License.
6 : * You may obtain a copy of the License at:
7 : *
8 : * http://www.apache.org/licenses/LICENSE-2.0
9 : *
10 : * Unless required by applicable law or agreed to in writing, software
11 : * distributed under the License is distributed on an "AS IS" BASIS,
12 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 : * See the License for the specific language governing permissions and
14 : * limitations under the License.
15 : *------------------------------------------------------------------
16 : */
17 :
18 : #include <vlib/vlib.h>
19 : #include <vnet/plugin/plugin.h>
20 : #include <vpp/app/version.h>
21 : #include <vnet/ip/ip.h>
22 : #include <vnet/mfib/mfib_entry.h>
23 : #include <vlib/unix/unix.h>
24 : #include <vnet/adj/adj_mcast.h>
25 : #include <vnet/fib/fib_entry.h>
26 : #include <vnet/fib/fib_table.h>
27 : #include <vnet/mfib/mfib_table.h>
28 :
29 : #include <igmp/igmp.h>
30 : #include <igmp/igmp_format.h>
31 : #include <igmp/igmp_pkt.h>
32 :
33 : #include <limits.h>
34 : #include <float.h>
35 :
36 : igmp_main_t igmp_main;
37 :
38 : /* *INDENT-OFF* */
39 : /* General Query address */
40 : const static mfib_prefix_t mpfx_general_query = {
41 : .fp_proto = FIB_PROTOCOL_IP4,
42 : .fp_len = 32,
43 : .fp_grp_addr = {
44 : .ip4 = {
45 : .as_u32 = IGMP_GENERAL_QUERY_ADDRESS,
46 : },
47 : },
48 : };
49 :
50 : /* Report address */
51 : const static mfib_prefix_t mpfx_report = {
52 : .fp_proto = FIB_PROTOCOL_IP4,
53 : .fp_len = 32,
54 : .fp_grp_addr = {
55 : .ip4 = {
56 : .as_u32 = IGMP_MEMBERSHIP_REPORT_ADDRESS,
57 : },
58 : },
59 : };
60 : /* *INDENT-ON* */
61 :
62 : /**
63 : * @brief igmp send query (igmp_timer_function_t)
64 : *
65 : * Send an igmp query.
66 : * If the timer holds group key, send Group-Specific query,
67 : * else send General query.
68 : */
69 : static void
70 14 : igmp_send_general_query (u32 obj, void *dat)
71 : {
72 : igmp_pkt_build_query_t bq;
73 : igmp_config_t *config;
74 :
75 14 : config = igmp_config_get (obj);
76 :
77 14 : IGMP_DBG ("send-general-query: %U",
78 : format_vnet_sw_if_index_name, vnet_get_main (),
79 : config->sw_if_index);
80 :
81 14 : igmp_timer_retire (&config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY]);
82 :
83 14 : igmp_pkt_build_query_init (&bq, config->sw_if_index);
84 14 : igmp_pkt_query_v3_add_group (&bq, NULL, NULL);
85 14 : igmp_pkt_query_v3_send (&bq);
86 :
87 : /*
88 : * re-schedule
89 : */
90 14 : config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] =
91 14 : igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY),
92 : igmp_config_index (config),
93 : igmp_send_general_query, NULL);
94 14 : }
95 :
96 : static void
97 20 : igmp_send_state_change_group_report_v3 (u32 sw_if_index,
98 : const igmp_group_t * group)
99 : {
100 : igmp_pkt_build_report_t br;
101 :
102 20 : IGMP_DBG ("state-change-group: %U", format_igmp_key, group->key);
103 :
104 20 : igmp_pkt_build_report_init (&br, sw_if_index);
105 20 : igmp_pkt_report_v3_add_group (&br,
106 : group,
107 : IGMP_MEMBERSHIP_GROUP_allow_new_sources);
108 20 : igmp_pkt_report_v3_send (&br);
109 20 : }
110 :
111 : static void
112 10 : igmp_resend_state_change_group_report_v3 (u32 gi, void *data)
113 : {
114 : igmp_config_t *config;
115 : igmp_group_t *group;
116 :
117 10 : group = igmp_group_get (gi);
118 10 : config = igmp_config_get (group->config);
119 :
120 10 : igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
121 10 : igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
122 :
123 10 : if (++group->n_reports_sent < config->robustness_var)
124 : {
125 0 : group->timers[IGMP_GROUP_TIMER_RESEND_REPORT] =
126 0 : igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_REPORT_INTERVAL),
127 : igmp_group_index (group),
128 : igmp_resend_state_change_group_report_v3, NULL);
129 : }
130 10 : }
131 :
132 : int
133 21 : igmp_listen (vlib_main_t * vm,
134 : igmp_filter_mode_t mode,
135 : u32 sw_if_index,
136 : const ip46_address_t * saddrs, const ip46_address_t * gaddr)
137 : {
138 : const ip46_address_t *saddr;
139 : igmp_config_t *config;
140 : igmp_group_t *group;
141 :
142 : /*
143 : * RFC 3376 Section 2
144 : " For a given combination of socket, interface, and multicast address,
145 : * only a single filter mode and source list can be in effect at any one
146 : * time. However, either the filter mode or the source list, or both,
147 : * may be changed by subsequent IPMulticastListen requests that specify
148 : * the same socket, interface, and multicast address. Each subsequent
149 : * request completely replaces any earlier request for the given socket,
150 : * interface and multicast address."
151 : */
152 21 : int rv = 0;
153 21 : IGMP_DBG ("listen: (%U, %U) %U %U",
154 : format_igmp_src_addr_list, saddrs,
155 : format_igmp_key, gaddr,
156 : format_vnet_sw_if_index_name, vnet_get_main (),
157 : sw_if_index, format_igmp_filter_mode, mode);
158 : /*
159 : * find configuration, if it doesn't exist, then this interface is
160 : * not IGMP enabled
161 : */
162 21 : config = igmp_config_lookup (sw_if_index);
163 :
164 21 : if (!config)
165 : {
166 0 : rv = VNET_API_ERROR_INVALID_INTERFACE;
167 0 : goto error;
168 : }
169 21 : if (config->mode != IGMP_MODE_HOST)
170 : {
171 0 : rv = VNET_API_ERROR_INVALID_INTERFACE;
172 0 : goto error;
173 : }
174 :
175 : /* find igmp group, if it doesn't exist, create new */
176 21 : group = igmp_group_lookup (config, gaddr);
177 :
178 21 : if (!group)
179 : {
180 10 : group = igmp_group_alloc (config, gaddr, mode);
181 :
182 : /* new group implies create all sources */
183 426 : vec_foreach (saddr, saddrs)
184 : {
185 416 : igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
186 : }
187 :
188 : /*
189 : * Send state changed event report for the group.
190 : *
191 : * RFC3376 Section 5.1
192 : * "To cover the possibility of the State-Change Report being missed by
193 : * one or more multicast routers, it is retransmitted [Robustness
194 : * Variable] - 1 more times, at intervals chosen at random from the
195 : * range (0, [Unsolicited Report Interval])."
196 : */
197 10 : igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
198 :
199 10 : igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
200 :
201 10 : group->n_reports_sent = 1;
202 10 : group->timers[IGMP_GROUP_TIMER_RESEND_REPORT] =
203 10 : igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_REPORT_INTERVAL),
204 : igmp_group_index (group),
205 : igmp_resend_state_change_group_report_v3, NULL);
206 : }
207 : else
208 : {
209 11 : IGMP_DBG ("... update (%U, %U) %U %U",
210 : format_igmp_src_addr_list, saddrs,
211 : format_igmp_key, gaddr,
212 : format_vnet_sw_if_index_name, vnet_get_main (),
213 : sw_if_index, format_igmp_filter_mode, mode);
214 :
215 : /*
216 : * RFC 3367 Section 5.1
217 : *
218 : * Old State New State State-Change Record Sent
219 : * --------- --------- ------------------------
220 : *
221 : * 1) INCLUDE (A) INCLUDE (B) ALLOW (B-A), BLOCK (A-B)
222 : * 2) EXCLUDE (A) EXCLUDE (B) ALLOW (A-B), BLOCK (B-A)
223 : * 3) INCLUDE (A) EXCLUDE (B) TO_EX (B)
224 : * 4) EXCLUDE (A) INCLUDE (B) TO_IN (B)
225 : *
226 : * N.B. We do not split state-change records for pending transfer
227 : * hence there is no merge logic required.
228 : */
229 :
230 11 : if (IGMP_FILTER_MODE_INCLUDE == mode)
231 : {
232 : ip46_address_t *added, *removed;
233 : igmp_pkt_build_report_t br;
234 :
235 : /*
236 : * find the list of sources that have been added and removed from
237 : * the include set
238 : */
239 11 : removed =
240 11 : igmp_group_present_minus_new (group, IGMP_FILTER_MODE_INCLUDE,
241 : saddrs);
242 11 : added =
243 11 : igmp_group_new_minus_present (group, IGMP_FILTER_MODE_INCLUDE,
244 : saddrs);
245 :
246 11 : if (!(vec_len (added) || vec_len (removed)))
247 : /* no change => done */
248 0 : goto error;
249 :
250 11 : igmp_pkt_build_report_init (&br, config->sw_if_index);
251 :
252 11 : if (vec_len (added))
253 : {
254 1 : igmp_pkt_report_v3_add_report (&br,
255 1 : group->key,
256 : added,
257 : IGMP_MEMBERSHIP_GROUP_allow_new_sources);
258 : }
259 :
260 11 : if (vec_len (removed))
261 : {
262 11 : igmp_pkt_report_v3_add_report (&br,
263 11 : group->key,
264 : removed,
265 : IGMP_MEMBERSHIP_GROUP_block_old_sources);
266 : }
267 :
268 11 : IGMP_DBG ("... added %U", format_igmp_src_addr_list, added);
269 11 : IGMP_DBG ("... removed %U", format_igmp_src_addr_list, removed);
270 :
271 11 : igmp_pkt_report_v3_send (&br);
272 :
273 : /*
274 : * clear the group of the old sources and populate it with the new
275 : * set requested
276 : */
277 11 : igmp_group_free_all_srcs (group);
278 :
279 27 : vec_foreach (saddr, saddrs)
280 : {
281 16 : igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
282 : }
283 :
284 11 : if (0 == igmp_group_n_srcs (group, mode))
285 10 : igmp_group_clear (&group);
286 :
287 11 : vec_free (added);
288 11 : vec_free (removed);
289 : }
290 : else
291 : {
292 : /*
293 : * The control plane is excluding some sources.
294 : * - First; check for those that are present in the include list
295 : * - Second; check add them to the exclude list
296 : *
297 : * TODO
298 : */
299 : }
300 : }
301 :
302 0 : error:
303 21 : return (rv);
304 : }
305 :
306 : static walk_rc_t
307 6347 : igmp_sw_if_down (vnet_main_t * vnm, u32 sw_if_index, void *ctx)
308 : {
309 : igmp_config_t *config;
310 6347 : config = igmp_config_lookup (sw_if_index);
311 6347 : IGMP_DBG ("down: %U",
312 : format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index);
313 6347 : if (NULL != config)
314 : {
315 0 : igmp_clear_config (config);
316 : }
317 :
318 6347 : return (WALK_CONTINUE);
319 : }
320 :
321 : /** \brief igmp hardware interface link up down
322 : @param vnm - vnet main
323 : @param hw_if_index - interface hw_if_index
324 : @param flags - hw interface flags
325 :
326 : If an interface goes down, remove its (S,G)s.
327 : */
328 : static clib_error_t *
329 13092 : igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
330 : {
331 13092 : clib_error_t *error = NULL;
332 : /* remove igmp state from down interfaces */
333 13092 : if (!(flags & VNET_HW_INTERFACE_FLAG_LINK_UP))
334 6315 : vnet_hw_interface_walk_sw (vnm, hw_if_index, igmp_sw_if_down, NULL);
335 13092 : return error;
336 : }
337 :
338 2241 : VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (igmp_hw_interface_link_up_down);
339 : int
340 18 : igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode)
341 : {
342 : igmp_config_t *config;
343 18 : igmp_main_t *im = &igmp_main;
344 : u32 mfib_index;
345 18 : IGMP_DBG ("%s: %U", (enable ? "Enabled" : "Disabled"),
346 : format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index);
347 :
348 : /* *INDENT-OFF* */
349 36 : fib_route_path_t via_itf_path =
350 : {
351 18 : .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
352 : .frp_addr = zero_addr,
353 : .frp_sw_if_index = sw_if_index,
354 : .frp_fib_index = 0,
355 : .frp_weight = 1,
356 : .frp_mitf_flags = MFIB_ITF_FLAG_ACCEPT,
357 : };
358 36 : fib_route_path_t for_us_path = {
359 18 : .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
360 : .frp_addr = zero_addr,
361 : .frp_sw_if_index = 0xffffffff,
362 : .frp_fib_index = 1,
363 : .frp_weight = 0,
364 : .frp_flags = FIB_ROUTE_PATH_LOCAL,
365 : .frp_mitf_flags = MFIB_ITF_FLAG_FORWARD,
366 : };
367 :
368 : /* *INDENT-ON* */
369 : /* find configuration, if it doesn't exist, create new */
370 18 : config = igmp_config_lookup (sw_if_index);
371 18 : mfib_index = mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
372 : sw_if_index);
373 18 : if (!config && enable)
374 9 : {
375 : u32 ii;
376 :
377 14 : vec_validate_init_empty (im->igmp_config_by_sw_if_index,
378 : sw_if_index, ~0);
379 9 : pool_get (im->configs, config);
380 9 : clib_memset (config, 0, sizeof (igmp_config_t));
381 9 : config->sw_if_index = sw_if_index;
382 9 : config->igmp_group_by_key =
383 9 : hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
384 9 : config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
385 9 : config->mode = mode;
386 9 : config->proxy_device_id = ~0;
387 :
388 27 : for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++)
389 18 : config->timers[ii] = IGMP_TIMER_ID_INVALID;
390 :
391 9 : if (IGMP_MODE_ROUTER == mode)
392 : {
393 3 : config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] =
394 3 : igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY),
395 : igmp_config_index (config),
396 : igmp_send_general_query, NULL);
397 : }
398 :
399 18 : config->adj_index =
400 9 : adj_mcast_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4,
401 9 : config->sw_if_index);
402 9 : im->igmp_config_by_sw_if_index[config->sw_if_index] =
403 9 : (config - im->configs);
404 : {
405 9 : vec_validate (im->n_configs_per_mfib_index, mfib_index);
406 9 : im->n_configs_per_mfib_index[mfib_index]++;
407 9 : if (1 == im->n_configs_per_mfib_index[mfib_index])
408 : {
409 : /* first config in this FIB */
410 5 : mfib_table_lock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
411 5 : mfib_table_entry_path_update (mfib_index, &mpfx_general_query,
412 : MFIB_SOURCE_IGMP,
413 : MFIB_ENTRY_FLAG_NONE, &for_us_path);
414 5 : mfib_table_entry_path_update (mfib_index, &mpfx_report,
415 : MFIB_SOURCE_IGMP,
416 : MFIB_ENTRY_FLAG_NONE, &for_us_path);
417 : }
418 9 : mfib_table_entry_path_update (mfib_index, &mpfx_general_query,
419 : MFIB_SOURCE_IGMP, MFIB_ENTRY_FLAG_NONE,
420 : &via_itf_path);
421 9 : mfib_table_entry_path_update (mfib_index, &mpfx_report,
422 : MFIB_SOURCE_IGMP, MFIB_ENTRY_FLAG_NONE,
423 : &via_itf_path);
424 : }
425 : }
426 9 : else if (config && !enable)
427 : {
428 9 : vec_validate (im->n_configs_per_mfib_index, mfib_index);
429 9 : im->n_configs_per_mfib_index[mfib_index]--;
430 9 : if (0 == im->n_configs_per_mfib_index[mfib_index])
431 : {
432 : /* last config in this FIB */
433 5 : mfib_table_entry_path_remove (mfib_index,
434 : &mpfx_general_query,
435 : MFIB_SOURCE_IGMP, &for_us_path);
436 5 : mfib_table_entry_path_remove (mfib_index,
437 : &mpfx_report,
438 : MFIB_SOURCE_IGMP, &for_us_path);
439 5 : mfib_table_unlock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
440 : }
441 :
442 9 : mfib_table_entry_path_remove (mfib_index,
443 : &mpfx_general_query,
444 : MFIB_SOURCE_IGMP, &via_itf_path);
445 9 : mfib_table_entry_path_remove (mfib_index,
446 : &mpfx_report,
447 : MFIB_SOURCE_IGMP, &via_itf_path);
448 :
449 : /*
450 : * remove interface from proxy device
451 : * if this device is upstream, delete proxy device
452 : */
453 9 : if (config->mode == IGMP_MODE_ROUTER)
454 3 : igmp_proxy_device_add_del_interface (config->proxy_device_id,
455 3 : config->sw_if_index, 0);
456 6 : else if (config->mode == IGMP_MODE_HOST)
457 6 : igmp_proxy_device_add_del (config->proxy_device_id,
458 6 : config->sw_if_index, 0);
459 :
460 9 : igmp_clear_config (config);
461 9 : im->igmp_config_by_sw_if_index[config->sw_if_index] = ~0;
462 9 : hash_free (config->igmp_group_by_key);
463 9 : pool_put (im->configs, config);
464 : }
465 : else
466 : {
467 0 : return -1;
468 : }
469 :
470 18 : return (0);
471 : }
472 :
473 : /** \brief igmp initialization
474 : @param vm - vlib main
475 :
476 : initialize igmp plugin. Initialize igmp_main, set mfib to allow igmp traffic.
477 : */
478 : static clib_error_t *
479 559 : igmp_init (vlib_main_t * vm)
480 : {
481 559 : igmp_main_t *im = &igmp_main;
482 :
483 559 : im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32));
484 559 : im->logger = vlib_log_register_class ("igmp", 0);
485 :
486 559 : IGMP_DBG ("initialized");
487 :
488 559 : return (0);
489 : }
490 :
491 : /* *INDENT-OFF* */
492 1119 : VLIB_INIT_FUNCTION (igmp_init) =
493 : {
494 : .runs_after = VLIB_INITS("ip4_lookup_init"),
495 : };
496 : VLIB_PLUGIN_REGISTER () =
497 : {
498 : .version = VPP_BUILD_VER,
499 : .description = "Internet Group Management Protocol (IGMP)",
500 : };
501 : /* *INDENT-ON* */
502 :
503 : /*
504 : * fd.io coding-style-patch-verification: ON
505 : *
506 : * Local Variables:
507 : * eval: (c-set-style "gnu")
508 : * End:
509 : */
|