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 dosn'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 dosn'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 exlude 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 dosn'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 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;
385 for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++)
386 config->timers[ii] = IGMP_TIMER_ID_INVALID;
388 if (IGMP_MODE_ROUTER == mode)
390 config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] =
391 igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY),
392 igmp_config_index (config),
393 igmp_send_general_query, NULL);
397 adj_mcast_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4,
398 config->sw_if_index);
399 im->igmp_config_by_sw_if_index[config->sw_if_index] =
400 (config - im->configs);
402 vec_validate (im->n_configs_per_mfib_index, mfib_index);
403 im->n_configs_per_mfib_index[mfib_index]++;
404 if (1 == im->n_configs_per_mfib_index[mfib_index])
406 /* first config in this FIB */
407 mfib_table_entry_path_update (mfib_index,
411 MFIB_ITF_FLAG_FORWARD);
412 mfib_table_entry_path_update (mfib_index,
416 MFIB_ITF_FLAG_FORWARD);
418 mfib_table_entry_path_update (mfib_index,
421 &via_itf_path, MFIB_ITF_FLAG_ACCEPT);
422 mfib_table_entry_path_update (mfib_index, &mpfx_report,
423 MFIB_SOURCE_IGMP, &via_itf_path,
424 MFIB_ITF_FLAG_ACCEPT);
427 else if (config && !enable)
429 vec_validate (im->n_configs_per_mfib_index, mfib_index);
430 im->n_configs_per_mfib_index[mfib_index]--;
431 if (0 == im->n_configs_per_mfib_index[mfib_index])
433 /* last config in this FIB */
434 mfib_table_entry_path_remove (mfib_index,
436 MFIB_SOURCE_IGMP, &for_us_path);
437 mfib_table_entry_path_remove (mfib_index,
439 MFIB_SOURCE_IGMP, &for_us_path);
442 mfib_table_entry_path_remove (mfib_index,
444 MFIB_SOURCE_IGMP, &via_itf_path);
445 mfib_table_entry_path_remove (mfib_index,
447 MFIB_SOURCE_IGMP, &via_itf_path);
448 igmp_clear_config (config);
449 im->igmp_config_by_sw_if_index[config->sw_if_index] = ~0;
450 hash_free (config->igmp_group_by_key);
451 pool_put (im->configs, config);
457 /** \brief igmp initialization
458 @param vm - vlib main
460 initialize igmp plugin. Initialize igmp_main, set mfib to allow igmp traffic.
462 static clib_error_t *
463 igmp_init (vlib_main_t * vm)
466 igmp_main_t *im = &igmp_main;
468 if ((error = vlib_call_init_function (vm, ip4_lookup_init)))
471 im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32));
473 im->logger = vlib_log_register_class ("igmp", 0);
475 IGMP_DBG ("initialized");
480 VLIB_INIT_FUNCTION (igmp_init);
482 VLIB_PLUGIN_REGISTER () = {
483 .version = VPP_BUILD_VER,
484 .description = "IGMP messaging",
489 * fd.io coding-style-patch-verification: ON
492 * eval: (c-set-style "gnu")