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:
8 * http://www.apache.org/licenses/LICENSE-2.0
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 *------------------------------------------------------------------
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>
29 #include <igmp/igmp.h>
30 #include <igmp/igmp_format.h>
31 #include <igmp/igmp_pkt.h>
36 igmp_main_t igmp_main;
39 /* General Query address */
40 const static mfib_prefix_t mpfx_general_query = {
41 .fp_proto = FIB_PROTOCOL_IP4,
45 .as_u32 = IGMP_GENERAL_QUERY_ADDRESS,
51 const static mfib_prefix_t mpfx_report = {
52 .fp_proto = FIB_PROTOCOL_IP4,
56 .as_u32 = IGMP_MEMBERSHIP_REPORT_ADDRESS,
63 * @brief igmp send query (igmp_timer_function_t)
66 * If the timer holds group key, send Group-Specific query,
67 * else send General query.
70 igmp_send_general_query (u32 obj, void *dat)
72 igmp_pkt_build_query_t bq;
73 igmp_config_t *config;
75 config = igmp_config_get (obj);
77 IGMP_DBG ("send-general-query: %U",
78 format_vnet_sw_if_index_name, vnet_get_main (),
81 igmp_timer_retire (&config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY]);
83 igmp_pkt_build_query_init (&bq, config->sw_if_index);
84 igmp_pkt_query_v3_add_group (&bq, NULL, NULL);
85 igmp_pkt_query_v3_send (&bq);
90 config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] =
91 igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY),
92 igmp_config_index (config),
93 igmp_send_general_query, NULL);
97 igmp_send_state_change_group_report_v3 (u32 sw_if_index,
98 const igmp_group_t * group)
100 igmp_pkt_build_report_t br;
102 IGMP_DBG ("state-change-group: %U", format_igmp_key, group->key);
104 igmp_pkt_build_report_init (&br, sw_if_index);
105 igmp_pkt_report_v3_add_group (&br,
107 IGMP_MEMBERSHIP_GROUP_allow_new_sources);
108 igmp_pkt_report_v3_send (&br);
112 igmp_resend_state_change_group_report_v3 (u32 gi, void *data)
114 igmp_config_t *config;
117 group = igmp_group_get (gi);
118 config = igmp_config_get (group->config);
120 igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
121 igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
123 if (++group->n_reports_sent < config->robustness_var)
125 group->timers[IGMP_GROUP_TIMER_RESEND_REPORT] =
126 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);
133 igmp_listen (vlib_main_t * vm,
134 igmp_filter_mode_t mode,
136 const ip46_address_t * saddrs, const ip46_address_t * gaddr)
138 const ip46_address_t *saddr;
139 igmp_config_t *config;
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."
153 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);
159 * find configuration, if it doesn't exist, then this interface is
162 config = igmp_config_lookup (sw_if_index);
166 rv = VNET_API_ERROR_INVALID_INTERFACE;
169 if (config->mode != IGMP_MODE_HOST)
171 rv = VNET_API_ERROR_INVALID_INTERFACE;
175 /* find igmp group, if it doesn't exist, create new */
176 group = igmp_group_lookup (config, gaddr);
180 group = igmp_group_alloc (config, gaddr, mode);
182 /* new group implies create all sources */
183 vec_foreach (saddr, saddrs)
185 igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
189 * Send state changed event report for the group.
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])."
197 igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
199 igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
201 group->n_reports_sent = 1;
202 group->timers[IGMP_GROUP_TIMER_RESEND_REPORT] =
203 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);
209 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);
216 * RFC 3367 Section 5.1
218 * Old State New State State-Change Record Sent
219 * --------- --------- ------------------------
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)
226 * N.B. We do not split state-change records for pending transfer
227 * hence there is no merge logic required.
230 if (IGMP_FILTER_MODE_INCLUDE == mode)
232 ip46_address_t *added, *removed;
233 igmp_pkt_build_report_t br;
236 * find the list of sources that have been added and removed from
240 igmp_group_present_minus_new (group, IGMP_FILTER_MODE_INCLUDE,
243 igmp_group_new_minus_present (group, IGMP_FILTER_MODE_INCLUDE,
246 if (!(vec_len (added) || vec_len (removed)))
247 /* no change => done */
250 igmp_pkt_build_report_init (&br, config->sw_if_index);
254 igmp_pkt_report_v3_add_report (&br,
257 IGMP_MEMBERSHIP_GROUP_allow_new_sources);
260 if (vec_len (removed))
262 igmp_pkt_report_v3_add_report (&br,
265 IGMP_MEMBERSHIP_GROUP_block_old_sources);
268 IGMP_DBG ("... added %U", format_igmp_src_addr_list, added);
269 IGMP_DBG ("... removed %U", format_igmp_src_addr_list, removed);
271 igmp_pkt_report_v3_send (&br);
274 * clear the group of the old sources and populate it with the new
277 igmp_group_free_all_srcs (group);
279 vec_foreach (saddr, saddrs)
281 igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
284 if (0 == igmp_group_n_srcs (group, mode))
285 igmp_group_clear (group);
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
306 /** \brief igmp hardware interface link up down
307 @param vnm - vnet main
308 @param hw_if_index - interface hw_if_index
309 @param flags - hw interface flags
311 If an interface goes down, remove its (S,G)s.
314 igmp_sw_if_down (vnet_main_t * vnm, u32 sw_if_index, void *ctx)
316 igmp_config_t *config;
317 config = igmp_config_lookup (sw_if_index);
318 IGMP_DBG ("down: %U",
319 format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index);
322 igmp_clear_config (config);
325 return (WALK_CONTINUE);
328 static clib_error_t *
329 igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
331 clib_error_t *error = NULL;
332 /* remove igmp state from down interfaces */
333 if (!(flags & VNET_HW_INTERFACE_FLAG_LINK_UP))
334 vnet_hw_interface_walk_sw (vnm, hw_if_index, igmp_sw_if_down, NULL);
338 VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (igmp_hw_interface_link_up_down);
340 igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode)
342 igmp_config_t *config;
343 igmp_main_t *im = &igmp_main;
345 IGMP_DBG ("%s: %U", (enable ? "Enabled" : "Disabled"),
346 format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index);
349 fib_route_path_t for_us_path =
351 .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
352 .frp_addr = zero_addr,
353 .frp_sw_if_index = 0xffffffff,
356 .frp_flags = FIB_ROUTE_PATH_LOCAL,
358 fib_route_path_t via_itf_path =
360 .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
361 .frp_addr = zero_addr,
362 .frp_sw_if_index = sw_if_index,
367 /* find configuration, if it doesn't exist, create new */
368 config = igmp_config_lookup (sw_if_index);
369 mfib_index = mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
371 if (!config && enable)
375 vec_validate_init_empty (im->igmp_config_by_sw_if_index,
377 pool_get (im->configs, config);
378 clib_memset (config, 0, sizeof (igmp_config_t));
379 config->sw_if_index = sw_if_index;
380 config->igmp_group_by_key =
381 hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
382 config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
384 config->proxy_device_id = ~0;
386 for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++)
387 config->timers[ii] = IGMP_TIMER_ID_INVALID;
389 if (IGMP_MODE_ROUTER == mode)
391 config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] =
392 igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY),
393 igmp_config_index (config),
394 igmp_send_general_query, NULL);
398 adj_mcast_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4,
399 config->sw_if_index);
400 im->igmp_config_by_sw_if_index[config->sw_if_index] =
401 (config - im->configs);
403 vec_validate (im->n_configs_per_mfib_index, mfib_index);
404 im->n_configs_per_mfib_index[mfib_index]++;
405 if (1 == im->n_configs_per_mfib_index[mfib_index])
407 /* first config in this FIB */
408 mfib_table_entry_path_update (mfib_index,
412 MFIB_ITF_FLAG_FORWARD);
413 mfib_table_entry_path_update (mfib_index,
417 MFIB_ITF_FLAG_FORWARD);
419 mfib_table_entry_path_update (mfib_index,
422 &via_itf_path, MFIB_ITF_FLAG_ACCEPT);
423 mfib_table_entry_path_update (mfib_index, &mpfx_report,
424 MFIB_SOURCE_IGMP, &via_itf_path,
425 MFIB_ITF_FLAG_ACCEPT);
428 else if (config && !enable)
430 vec_validate (im->n_configs_per_mfib_index, mfib_index);
431 im->n_configs_per_mfib_index[mfib_index]--;
432 if (0 == im->n_configs_per_mfib_index[mfib_index])
434 /* last config in this FIB */
435 mfib_table_entry_path_remove (mfib_index,
437 MFIB_SOURCE_IGMP, &for_us_path);
438 mfib_table_entry_path_remove (mfib_index,
440 MFIB_SOURCE_IGMP, &for_us_path);
443 mfib_table_entry_path_remove (mfib_index,
445 MFIB_SOURCE_IGMP, &via_itf_path);
446 mfib_table_entry_path_remove (mfib_index,
448 MFIB_SOURCE_IGMP, &via_itf_path);
451 * remove interface from proxy device
452 * if this device is upstream, delete proxy device
454 if (config->mode == IGMP_MODE_ROUTER)
455 igmp_proxy_device_add_del_interface (config->proxy_device_id,
456 config->sw_if_index, 0);
457 else if (config->mode == IGMP_MODE_HOST)
458 igmp_proxy_device_add_del (config->proxy_device_id,
459 config->sw_if_index, 0);
461 igmp_clear_config (config);
462 im->igmp_config_by_sw_if_index[config->sw_if_index] = ~0;
463 hash_free (config->igmp_group_by_key);
464 pool_put (im->configs, config);
474 /** \brief igmp initialization
475 @param vm - vlib main
477 initialize igmp plugin. Initialize igmp_main, set mfib to allow igmp traffic.
479 static clib_error_t *
480 igmp_init (vlib_main_t * vm)
483 igmp_main_t *im = &igmp_main;
485 if ((error = vlib_call_init_function (vm, ip4_lookup_init)))
488 im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32));
490 im->logger = vlib_log_register_class ("igmp", 0);
492 IGMP_DBG ("initialized");
497 VLIB_INIT_FUNCTION (igmp_init);
499 VLIB_PLUGIN_REGISTER () = {
500 .version = VPP_BUILD_VER,
501 .description = "IGMP messaging",
506 * fd.io coding-style-patch-verification: ON
509 * eval: (c-set-style "gnu")