vlib: fix use of RTLD_DEEPBIND for musl
[vpp.git] / src / plugins / igmp / igmp.c
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 /* General Query address */
39 const static mfib_prefix_t mpfx_general_query = {
40   .fp_proto = FIB_PROTOCOL_IP4,
41   .fp_len = 32,
42   .fp_grp_addr = {
43     .ip4 = {
44       .as_u32 = IGMP_GENERAL_QUERY_ADDRESS,
45     },
46   },
47 };
48
49 /* Report address */
50 const static mfib_prefix_t mpfx_report = {
51   .fp_proto = FIB_PROTOCOL_IP4,
52   .fp_len = 32,
53   .fp_grp_addr = {
54     .ip4 = {
55       .as_u32 = IGMP_MEMBERSHIP_REPORT_ADDRESS,
56     },
57   },
58 };
59
60 /**
61  * @brief igmp send query (igmp_timer_function_t)
62  *
63  *   Send an igmp query.
64  *   If the timer holds group key, send Group-Specific query,
65  *   else send General query.
66  */
67 static void
68 igmp_send_general_query (u32 obj, void *dat)
69 {
70   igmp_pkt_build_query_t bq;
71   igmp_config_t *config;
72
73   config = igmp_config_get (obj);
74
75   IGMP_DBG ("send-general-query: %U",
76             format_vnet_sw_if_index_name, vnet_get_main (),
77             config->sw_if_index);
78
79   igmp_timer_retire (&config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY]);
80
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);
84
85   /*
86    * re-schedule
87    */
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);
92 }
93
94 static void
95 igmp_send_state_change_group_report_v3 (u32 sw_if_index,
96                                         const igmp_group_t * group)
97 {
98   igmp_pkt_build_report_t br;
99
100   IGMP_DBG ("state-change-group: %U", format_igmp_key, group->key);
101
102   igmp_pkt_build_report_init (&br, sw_if_index);
103   igmp_pkt_report_v3_add_group (&br,
104                                 group,
105                                 IGMP_MEMBERSHIP_GROUP_allow_new_sources);
106   igmp_pkt_report_v3_send (&br);
107 }
108
109 static void
110 igmp_resend_state_change_group_report_v3 (u32 gi, void *data)
111 {
112   igmp_config_t *config;
113   igmp_group_t *group;
114
115   group = igmp_group_get (gi);
116   config = igmp_config_get (group->config);
117
118   igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
119   igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
120
121   if (++group->n_reports_sent < config->robustness_var)
122     {
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);
127     }
128 }
129
130 int
131 igmp_listen (vlib_main_t * vm,
132              igmp_filter_mode_t mode,
133              u32 sw_if_index,
134              const ip46_address_t * saddrs, const ip46_address_t * gaddr)
135 {
136   const ip46_address_t *saddr;
137   igmp_config_t *config;
138   igmp_group_t *group;
139
140   /*
141    * RFC 3376 Section 2
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."
149    */
150   int rv = 0;
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);
156   /*
157    * find configuration, if it doesn't exist, then this interface is
158    * not IGMP enabled
159    */
160   config = igmp_config_lookup (sw_if_index);
161
162   if (!config)
163     {
164       rv = VNET_API_ERROR_INVALID_INTERFACE;
165       goto error;
166     }
167   if (config->mode != IGMP_MODE_HOST)
168     {
169       rv = VNET_API_ERROR_INVALID_INTERFACE;
170       goto error;
171     }
172
173   /* find igmp group, if it doesn't exist, create new */
174   group = igmp_group_lookup (config, gaddr);
175
176   if (!group)
177     {
178       group = igmp_group_alloc (config, gaddr, mode);
179
180       /* new group implies create all sources */
181       vec_foreach (saddr, saddrs)
182       {
183         igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
184       }
185
186       /*
187        * Send state changed event report for the group.
188        *
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])."
194        */
195       igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
196
197       igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
198
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);
204     }
205   else
206     {
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);
212
213       /*
214        * RFC 3367 Section 5.1
215        *
216        *   Old State         New State         State-Change Record Sent
217        *   ---------         ---------         ------------------------
218        *
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)
223        *
224        * N.B. We do not split state-change records for pending transfer
225        * hence there is no merge logic required.
226        */
227
228       if (IGMP_FILTER_MODE_INCLUDE == mode)
229         {
230           ip46_address_t *added, *removed;
231           igmp_pkt_build_report_t br;
232
233           /*
234            * find the list of sources that have been added and removed from
235            * the include set
236            */
237           removed =
238             igmp_group_present_minus_new (group, IGMP_FILTER_MODE_INCLUDE,
239                                           saddrs);
240           added =
241             igmp_group_new_minus_present (group, IGMP_FILTER_MODE_INCLUDE,
242                                           saddrs);
243
244           if (!(vec_len (added) || vec_len (removed)))
245             /* no change => done */
246             goto error;
247
248           igmp_pkt_build_report_init (&br, config->sw_if_index);
249
250           if (vec_len (added))
251             {
252               igmp_pkt_report_v3_add_report (&br,
253                                              group->key,
254                                              added,
255                                              IGMP_MEMBERSHIP_GROUP_allow_new_sources);
256             }
257
258           if (vec_len (removed))
259             {
260               igmp_pkt_report_v3_add_report (&br,
261                                              group->key,
262                                              removed,
263                                              IGMP_MEMBERSHIP_GROUP_block_old_sources);
264             }
265
266           IGMP_DBG ("... added %U", format_igmp_src_addr_list, added);
267           IGMP_DBG ("... removed %U", format_igmp_src_addr_list, removed);
268
269           igmp_pkt_report_v3_send (&br);
270
271           /*
272            * clear the group of the old sources and populate it with the new
273            * set requested
274            */
275           igmp_group_free_all_srcs (group);
276
277           vec_foreach (saddr, saddrs)
278           {
279             igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
280           }
281
282           if (0 == igmp_group_n_srcs (group, mode))
283             igmp_group_clear (&group);
284
285           vec_free (added);
286           vec_free (removed);
287         }
288       else
289         {
290           /*
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
294            *
295            * TODO
296            */
297         }
298     }
299
300 error:
301   return (rv);
302 }
303
304 static walk_rc_t
305 igmp_sw_if_down (vnet_main_t * vnm, u32 sw_if_index, void *ctx)
306 {
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);
311   if (NULL != config)
312     {
313       igmp_clear_config (config);
314     }
315
316   return (WALK_CONTINUE);
317 }
318
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
323
324     If an interface goes down, remove its (S,G)s.
325 */
326 static clib_error_t *
327 igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
328 {
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);
333   return error;
334 }
335
336 VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (igmp_hw_interface_link_up_down);
337 int
338 igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode)
339 {
340   igmp_config_t *config;
341   igmp_main_t *im = &igmp_main;
342   u32 mfib_index;
343   IGMP_DBG ("%s:  %U", (enable ? "Enabled" : "Disabled"),
344             format_vnet_sw_if_index_name, vnet_get_main (), sw_if_index);
345
346   fib_route_path_t via_itf_path =
347     {
348       .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
349       .frp_addr = zero_addr,
350       .frp_sw_if_index = sw_if_index,
351       .frp_fib_index = 0,
352       .frp_weight = 1,
353     .frp_mitf_flags = MFIB_ITF_FLAG_ACCEPT,
354     };
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,
359     .frp_fib_index = 1,
360     .frp_weight = 0,
361     .frp_flags = FIB_ROUTE_PATH_LOCAL,
362     .frp_mitf_flags = MFIB_ITF_FLAG_FORWARD,
363   };
364
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,
368                                                      sw_if_index);
369   if (!config && enable)
370     {
371       u32 ii;
372
373       vec_validate_init_empty (im->igmp_config_by_sw_if_index,
374                                sw_if_index, ~0);
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;
381       config->mode = mode;
382       config->proxy_device_id = ~0;
383
384       for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++)
385         config->timers[ii] = IGMP_TIMER_ID_INVALID;
386
387       if (IGMP_MODE_ROUTER == mode)
388         {
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);
393         }
394
395       config->adj_index =
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);
400       {
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])
404           {
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,
408                                           MFIB_SOURCE_IGMP,
409                                           MFIB_ENTRY_FLAG_NONE, &for_us_path);
410             mfib_table_entry_path_update (mfib_index, &mpfx_report,
411                                           MFIB_SOURCE_IGMP,
412                                           MFIB_ENTRY_FLAG_NONE, &for_us_path);
413           }
414         mfib_table_entry_path_update (mfib_index, &mpfx_general_query,
415                                       MFIB_SOURCE_IGMP, MFIB_ENTRY_FLAG_NONE,
416                                       &via_itf_path);
417         mfib_table_entry_path_update (mfib_index, &mpfx_report,
418                                       MFIB_SOURCE_IGMP, MFIB_ENTRY_FLAG_NONE,
419                                       &via_itf_path);
420       }
421     }
422   else if (config && !enable)
423     {
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])
427         {
428           /* last config in this FIB */
429           mfib_table_entry_path_remove (mfib_index,
430                                         &mpfx_general_query,
431                                         MFIB_SOURCE_IGMP, &for_us_path);
432           mfib_table_entry_path_remove (mfib_index,
433                                         &mpfx_report,
434                                         MFIB_SOURCE_IGMP, &for_us_path);
435           mfib_table_unlock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
436         }
437
438       mfib_table_entry_path_remove (mfib_index,
439                                     &mpfx_general_query,
440                                     MFIB_SOURCE_IGMP, &via_itf_path);
441       mfib_table_entry_path_remove (mfib_index,
442                                     &mpfx_report,
443                                     MFIB_SOURCE_IGMP, &via_itf_path);
444
445       /*
446        * remove interface from proxy device
447        * if this device is upstream, delete proxy device
448        */
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);
455
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);
460     }
461   else
462     {
463       return -1;
464     }
465
466   return (0);
467 }
468
469 /** \brief igmp initialization
470     @param vm - vlib main
471
472     initialize igmp plugin. Initialize igmp_main, set mfib to allow igmp traffic.
473 */
474 static clib_error_t *
475 igmp_init (vlib_main_t * vm)
476 {
477   igmp_main_t *im = &igmp_main;
478
479   im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32));
480   im->logger = vlib_log_register_class ("igmp", 0);
481
482   IGMP_DBG ("initialized");
483
484   return (0);
485 }
486
487 VLIB_INIT_FUNCTION (igmp_init) =
488 {
489   .runs_after = VLIB_INITS("ip4_lookup_init"),
490 };
491 VLIB_PLUGIN_REGISTER () =
492 {
493   .version = VPP_BUILD_VER,
494   .description = "Internet Group Management Protocol (IGMP)",
495 };
496
497 /*
498  * fd.io coding-style-patch-verification: ON
499  *
500  * Local Variables:
501  * eval: (c-set-style "gnu")
502  * End:
503  */