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;
38 /* General Query address */
39 const static mfib_prefix_t mpfx_general_query = {
40 .fp_proto = FIB_PROTOCOL_IP4,
44 .as_u32 = IGMP_GENERAL_QUERY_ADDRESS,
50 const static mfib_prefix_t mpfx_report = {
51 .fp_proto = FIB_PROTOCOL_IP4,
55 .as_u32 = IGMP_MEMBERSHIP_REPORT_ADDRESS,
61 * @brief igmp send query (igmp_timer_function_t)
64 * If the timer holds group key, send Group-Specific query,
65 * else send General query.
68 igmp_send_general_query (u32 obj, void *dat)
70 igmp_pkt_build_query_t bq;
71 igmp_config_t *config;
73 config = igmp_config_get (obj);
75 IGMP_DBG ("send-general-query: %U",
76 format_vnet_sw_if_index_name, vnet_get_main (),
79 igmp_timer_retire (&config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY]);
81 igmp_pkt_build_query_init (&bq, config->sw_if_index);
82 igmp_pkt_query_v3_add_group (&bq, NULL, NULL);
83 igmp_pkt_query_v3_send (&bq);
88 config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] =
89 igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY),
90 igmp_config_index (config),
91 igmp_send_general_query, NULL);
95 igmp_send_state_change_group_report_v3 (u32 sw_if_index,
96 const igmp_group_t * group)
98 igmp_pkt_build_report_t br;
100 IGMP_DBG ("state-change-group: %U", format_igmp_key, group->key);
102 igmp_pkt_build_report_init (&br, sw_if_index);
103 igmp_pkt_report_v3_add_group (&br,
105 IGMP_MEMBERSHIP_GROUP_allow_new_sources);
106 igmp_pkt_report_v3_send (&br);
110 igmp_resend_state_change_group_report_v3 (u32 gi, void *data)
112 igmp_config_t *config;
115 group = igmp_group_get (gi);
116 config = igmp_config_get (group->config);
118 igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
119 igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
121 if (++group->n_reports_sent < config->robustness_var)
123 group->timers[IGMP_GROUP_TIMER_RESEND_REPORT] =
124 igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_REPORT_INTERVAL),
125 igmp_group_index (group),
126 igmp_resend_state_change_group_report_v3, NULL);
131 igmp_listen (vlib_main_t * vm,
132 igmp_filter_mode_t mode,
134 const ip46_address_t * saddrs, const ip46_address_t * gaddr)
136 const ip46_address_t *saddr;
137 igmp_config_t *config;
142 " For a given combination of socket, interface, and multicast address,
143 * only a single filter mode and source list can be in effect at any one
144 * time. However, either the filter mode or the source list, or both,
145 * may be changed by subsequent IPMulticastListen requests that specify
146 * the same socket, interface, and multicast address. Each subsequent
147 * request completely replaces any earlier request for the given socket,
148 * interface and multicast address."
151 IGMP_DBG ("listen: (%U, %U) %U %U",
152 format_igmp_src_addr_list, saddrs,
153 format_igmp_key, gaddr,
154 format_vnet_sw_if_index_name, vnet_get_main (),
155 sw_if_index, format_igmp_filter_mode, mode);
157 * find configuration, if it doesn't exist, then this interface is
160 config = igmp_config_lookup (sw_if_index);
164 rv = VNET_API_ERROR_INVALID_INTERFACE;
167 if (config->mode != IGMP_MODE_HOST)
169 rv = VNET_API_ERROR_INVALID_INTERFACE;
173 /* find igmp group, if it doesn't exist, create new */
174 group = igmp_group_lookup (config, gaddr);
178 group = igmp_group_alloc (config, gaddr, mode);
180 /* new group implies create all sources */
181 vec_foreach (saddr, saddrs)
183 igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
187 * Send state changed event report for the group.
189 * RFC3376 Section 5.1
190 * "To cover the possibility of the State-Change Report being missed by
191 * one or more multicast routers, it is retransmitted [Robustness
192 * Variable] - 1 more times, at intervals chosen at random from the
193 * range (0, [Unsolicited Report Interval])."
195 igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
197 igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
199 group->n_reports_sent = 1;
200 group->timers[IGMP_GROUP_TIMER_RESEND_REPORT] =
201 igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_REPORT_INTERVAL),
202 igmp_group_index (group),
203 igmp_resend_state_change_group_report_v3, NULL);
207 IGMP_DBG ("... update (%U, %U) %U %U",
208 format_igmp_src_addr_list, saddrs,
209 format_igmp_key, gaddr,
210 format_vnet_sw_if_index_name, vnet_get_main (),
211 sw_if_index, format_igmp_filter_mode, mode);
214 * RFC 3367 Section 5.1
216 * Old State New State State-Change Record Sent
217 * --------- --------- ------------------------
219 * 1) INCLUDE (A) INCLUDE (B) ALLOW (B-A), BLOCK (A-B)
220 * 2) EXCLUDE (A) EXCLUDE (B) ALLOW (A-B), BLOCK (B-A)
221 * 3) INCLUDE (A) EXCLUDE (B) TO_EX (B)
222 * 4) EXCLUDE (A) INCLUDE (B) TO_IN (B)
224 * N.B. We do not split state-change records for pending transfer
225 * hence there is no merge logic required.
228 if (IGMP_FILTER_MODE_INCLUDE == mode)
230 ip46_address_t *added, *removed;
231 igmp_pkt_build_report_t br;
234 * find the list of sources that have been added and removed from
238 igmp_group_present_minus_new (group, IGMP_FILTER_MODE_INCLUDE,
241 igmp_group_new_minus_present (group, IGMP_FILTER_MODE_INCLUDE,
244 if (!(vec_len (added) || vec_len (removed)))
245 /* no change => done */
248 igmp_pkt_build_report_init (&br, config->sw_if_index);
252 igmp_pkt_report_v3_add_report (&br,
255 IGMP_MEMBERSHIP_GROUP_allow_new_sources);
258 if (vec_len (removed))
260 igmp_pkt_report_v3_add_report (&br,
263 IGMP_MEMBERSHIP_GROUP_block_old_sources);
266 IGMP_DBG ("... added %U", format_igmp_src_addr_list, added);
267 IGMP_DBG ("... removed %U", format_igmp_src_addr_list, removed);
269 igmp_pkt_report_v3_send (&br);
272 * clear the group of the old sources and populate it with the new
275 igmp_group_free_all_srcs (group);
277 vec_foreach (saddr, saddrs)
279 igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
282 if (0 == igmp_group_n_srcs (group, mode))
283 igmp_group_clear (&group);
291 * The control plane is excluding some sources.
292 * - First; check for those that are present in the include list
293 * - Second; check add them to the exclude list
305 igmp_sw_if_down (vnet_main_t * vnm, u32 sw_if_index, void *ctx)
307 igmp_config_t *config;
308 config = igmp_config_lookup (sw_if_index);
309 IGMP_DBG ("down: %U",
310 format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index);
313 igmp_clear_config (config);
316 return (WALK_CONTINUE);
319 /** \brief igmp hardware interface link up down
320 @param vnm - vnet main
321 @param hw_if_index - interface hw_if_index
322 @param flags - hw interface flags
324 If an interface goes down, remove its (S,G)s.
326 static clib_error_t *
327 igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
329 clib_error_t *error = NULL;
330 /* remove igmp state from down interfaces */
331 if (!(flags & VNET_HW_INTERFACE_FLAG_LINK_UP))
332 vnet_hw_interface_walk_sw (vnm, hw_if_index, igmp_sw_if_down, NULL);
336 VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (igmp_hw_interface_link_up_down);
338 igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode)
340 igmp_config_t *config;
341 igmp_main_t *im = &igmp_main;
343 IGMP_DBG ("%s: %U", (enable ? "Enabled" : "Disabled"),
344 format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index);
346 fib_route_path_t via_itf_path =
348 .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
349 .frp_addr = zero_addr,
350 .frp_sw_if_index = sw_if_index,
353 .frp_mitf_flags = MFIB_ITF_FLAG_ACCEPT,
355 fib_route_path_t for_us_path = {
356 .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
357 .frp_addr = zero_addr,
358 .frp_sw_if_index = 0xffffffff,
361 .frp_flags = FIB_ROUTE_PATH_LOCAL,
362 .frp_mitf_flags = MFIB_ITF_FLAG_FORWARD,
365 /* find configuration, if it doesn't exist, create new */
366 config = igmp_config_lookup (sw_if_index);
367 mfib_index = mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
369 if (!config && enable)
373 vec_validate_init_empty (im->igmp_config_by_sw_if_index,
375 pool_get (im->configs, config);
376 clib_memset (config, 0, sizeof (igmp_config_t));
377 config->sw_if_index = sw_if_index;
378 config->igmp_group_by_key =
379 hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
380 config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
382 config->proxy_device_id = ~0;
384 for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++)
385 config->timers[ii] = IGMP_TIMER_ID_INVALID;
387 if (IGMP_MODE_ROUTER == mode)
389 config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] =
390 igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY),
391 igmp_config_index (config),
392 igmp_send_general_query, NULL);
396 adj_mcast_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4,
397 config->sw_if_index);
398 im->igmp_config_by_sw_if_index[config->sw_if_index] =
399 (config - im->configs);
401 vec_validate (im->n_configs_per_mfib_index, mfib_index);
402 im->n_configs_per_mfib_index[mfib_index]++;
403 if (1 == im->n_configs_per_mfib_index[mfib_index])
405 /* first config in this FIB */
406 mfib_table_lock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
407 mfib_table_entry_path_update (mfib_index, &mpfx_general_query,
409 MFIB_ENTRY_FLAG_NONE, &for_us_path);
410 mfib_table_entry_path_update (mfib_index, &mpfx_report,
412 MFIB_ENTRY_FLAG_NONE, &for_us_path);
414 mfib_table_entry_path_update (mfib_index, &mpfx_general_query,
415 MFIB_SOURCE_IGMP, MFIB_ENTRY_FLAG_NONE,
417 mfib_table_entry_path_update (mfib_index, &mpfx_report,
418 MFIB_SOURCE_IGMP, MFIB_ENTRY_FLAG_NONE,
422 else if (config && !enable)
424 vec_validate (im->n_configs_per_mfib_index, mfib_index);
425 im->n_configs_per_mfib_index[mfib_index]--;
426 if (0 == im->n_configs_per_mfib_index[mfib_index])
428 /* last config in this FIB */
429 mfib_table_entry_path_remove (mfib_index,
431 MFIB_SOURCE_IGMP, &for_us_path);
432 mfib_table_entry_path_remove (mfib_index,
434 MFIB_SOURCE_IGMP, &for_us_path);
435 mfib_table_unlock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
438 mfib_table_entry_path_remove (mfib_index,
440 MFIB_SOURCE_IGMP, &via_itf_path);
441 mfib_table_entry_path_remove (mfib_index,
443 MFIB_SOURCE_IGMP, &via_itf_path);
446 * remove interface from proxy device
447 * if this device is upstream, delete proxy device
449 if (config->mode == IGMP_MODE_ROUTER)
450 igmp_proxy_device_add_del_interface (config->proxy_device_id,
451 config->sw_if_index, 0);
452 else if (config->mode == IGMP_MODE_HOST)
453 igmp_proxy_device_add_del (config->proxy_device_id,
454 config->sw_if_index, 0);
456 igmp_clear_config (config);
457 im->igmp_config_by_sw_if_index[config->sw_if_index] = ~0;
458 hash_free (config->igmp_group_by_key);
459 pool_put (im->configs, config);
469 /** \brief igmp initialization
470 @param vm - vlib main
472 initialize igmp plugin. Initialize igmp_main, set mfib to allow igmp traffic.
474 static clib_error_t *
475 igmp_init (vlib_main_t * vm)
477 igmp_main_t *im = &igmp_main;
479 im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32));
480 im->logger = vlib_log_register_class ("igmp", 0);
482 IGMP_DBG ("initialized");
487 VLIB_INIT_FUNCTION (igmp_init) =
489 .runs_after = VLIB_INITS("ip4_lookup_init"),
491 VLIB_PLUGIN_REGISTER () =
493 .version = VPP_BUILD_VER,
494 .description = "Internet Group Management Protocol (IGMP)",
498 * fd.io coding-style-patch-verification: ON
501 * eval: (c-set-style "gnu")