fib: fib api updates
[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 /* *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 igmp_send_general_query (u32 obj, void *dat)
71 {
72   igmp_pkt_build_query_t bq;
73   igmp_config_t *config;
74
75   config = igmp_config_get (obj);
76
77   IGMP_DBG ("send-general-query: %U",
78             format_vnet_sw_if_index_name, vnet_get_main (),
79             config->sw_if_index);
80
81   igmp_timer_retire (&config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY]);
82
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);
86
87   /*
88    * re-schedule
89    */
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);
94 }
95
96 static void
97 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   IGMP_DBG ("state-change-group: %U", format_igmp_key, group->key);
103
104   igmp_pkt_build_report_init (&br, sw_if_index);
105   igmp_pkt_report_v3_add_group (&br,
106                                 group,
107                                 IGMP_MEMBERSHIP_GROUP_allow_new_sources);
108   igmp_pkt_report_v3_send (&br);
109 }
110
111 static void
112 igmp_resend_state_change_group_report_v3 (u32 gi, void *data)
113 {
114   igmp_config_t *config;
115   igmp_group_t *group;
116
117   group = igmp_group_get (gi);
118   config = igmp_config_get (group->config);
119
120   igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
121   igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
122
123   if (++group->n_reports_sent < config->robustness_var)
124     {
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);
129     }
130 }
131
132 int
133 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   int rv = 0;
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);
158   /*
159    * find configuration, if it doesn't exist, then this interface is
160    * not IGMP enabled
161    */
162   config = igmp_config_lookup (sw_if_index);
163
164   if (!config)
165     {
166       rv = VNET_API_ERROR_INVALID_INTERFACE;
167       goto error;
168     }
169   if (config->mode != IGMP_MODE_HOST)
170     {
171       rv = VNET_API_ERROR_INVALID_INTERFACE;
172       goto error;
173     }
174
175   /* find igmp group, if it doesn't exist, create new */
176   group = igmp_group_lookup (config, gaddr);
177
178   if (!group)
179     {
180       group = igmp_group_alloc (config, gaddr, mode);
181
182       /* new group implies create all sources */
183       vec_foreach (saddr, saddrs)
184       {
185         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       igmp_send_state_change_group_report_v3 (config->sw_if_index, group);
198
199       igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_RESEND_REPORT]);
200
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);
206     }
207   else
208     {
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);
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       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           removed =
240             igmp_group_present_minus_new (group, IGMP_FILTER_MODE_INCLUDE,
241                                           saddrs);
242           added =
243             igmp_group_new_minus_present (group, IGMP_FILTER_MODE_INCLUDE,
244                                           saddrs);
245
246           if (!(vec_len (added) || vec_len (removed)))
247             /* no change => done */
248             goto error;
249
250           igmp_pkt_build_report_init (&br, config->sw_if_index);
251
252           if (vec_len (added))
253             {
254               igmp_pkt_report_v3_add_report (&br,
255                                              group->key,
256                                              added,
257                                              IGMP_MEMBERSHIP_GROUP_allow_new_sources);
258             }
259
260           if (vec_len (removed))
261             {
262               igmp_pkt_report_v3_add_report (&br,
263                                              group->key,
264                                              removed,
265                                              IGMP_MEMBERSHIP_GROUP_block_old_sources);
266             }
267
268           IGMP_DBG ("... added %U", format_igmp_src_addr_list, added);
269           IGMP_DBG ("... removed %U", format_igmp_src_addr_list, removed);
270
271           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           igmp_group_free_all_srcs (group);
278
279           vec_foreach (saddr, saddrs)
280           {
281             igmp_group_src_update (group, saddr, IGMP_MODE_HOST);
282           }
283
284           if (0 == igmp_group_n_srcs (group, mode))
285             igmp_group_clear (group);
286
287           vec_free (added);
288           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 error:
303   return (rv);
304 }
305
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
310
311     If an interface goes down, remove its (S,G)s.
312 */
313 static walk_rc_t
314 igmp_sw_if_down (vnet_main_t * vnm, u32 sw_if_index, void *ctx)
315 {
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);
320   if (NULL != config)
321     {
322       igmp_clear_config (config);
323     }
324
325   return (WALK_CONTINUE);
326 }
327
328 static clib_error_t *
329 igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
330 {
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);
335   return error;
336 }
337
338 VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (igmp_hw_interface_link_up_down);
339 int
340 igmp_enable_disable (u32 sw_if_index, u8 enable, igmp_mode_t mode)
341 {
342   igmp_config_t *config;
343   igmp_main_t *im = &igmp_main;
344   u32 mfib_index;
345   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   fib_route_path_t via_itf_path =
350     {
351       .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   fib_route_path_t for_us_path = {
359     .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   config = igmp_config_lookup (sw_if_index);
371   mfib_index = mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
372                                                      sw_if_index);
373   if (!config && enable)
374     {
375       u32 ii;
376
377       vec_validate_init_empty (im->igmp_config_by_sw_if_index,
378                                sw_if_index, ~0);
379       pool_get (im->configs, config);
380       clib_memset (config, 0, sizeof (igmp_config_t));
381       config->sw_if_index = sw_if_index;
382       config->igmp_group_by_key =
383         hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
384       config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
385       config->mode = mode;
386       config->proxy_device_id = ~0;
387
388       for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++)
389         config->timers[ii] = IGMP_TIMER_ID_INVALID;
390
391       if (IGMP_MODE_ROUTER == mode)
392         {
393           config->timers[IGMP_CONFIG_TIMER_GENERAL_QUERY] =
394             igmp_timer_schedule (igmp_timer_type_get (IGMP_TIMER_QUERY),
395                                  igmp_config_index (config),
396                                  igmp_send_general_query, NULL);
397         }
398
399       config->adj_index =
400         adj_mcast_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4,
401                                config->sw_if_index);
402       im->igmp_config_by_sw_if_index[config->sw_if_index] =
403         (config - im->configs);
404       {
405         vec_validate (im->n_configs_per_mfib_index, mfib_index);
406         im->n_configs_per_mfib_index[mfib_index]++;
407         if (1 == im->n_configs_per_mfib_index[mfib_index])
408           {
409             /* first config in this FIB */
410             mfib_table_lock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
411             mfib_table_entry_path_update (mfib_index,
412                                           &mpfx_general_query,
413                                           MFIB_SOURCE_IGMP, &for_us_path);
414             mfib_table_entry_path_update (mfib_index,
415                                           &mpfx_report,
416                                           MFIB_SOURCE_IGMP, &for_us_path);
417           }
418         mfib_table_entry_path_update (mfib_index,
419                                       &mpfx_general_query,
420                                       MFIB_SOURCE_IGMP, &via_itf_path);
421         mfib_table_entry_path_update (mfib_index, &mpfx_report,
422                                       MFIB_SOURCE_IGMP, &via_itf_path);
423       }
424     }
425   else if (config && !enable)
426     {
427       vec_validate (im->n_configs_per_mfib_index, mfib_index);
428       im->n_configs_per_mfib_index[mfib_index]--;
429       if (0 == im->n_configs_per_mfib_index[mfib_index])
430         {
431           /* last config in this FIB */
432           mfib_table_entry_path_remove (mfib_index,
433                                         &mpfx_general_query,
434                                         MFIB_SOURCE_IGMP, &for_us_path);
435           mfib_table_entry_path_remove (mfib_index,
436                                         &mpfx_report,
437                                         MFIB_SOURCE_IGMP, &for_us_path);
438           mfib_table_unlock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
439         }
440
441       mfib_table_entry_path_remove (mfib_index,
442                                     &mpfx_general_query,
443                                     MFIB_SOURCE_IGMP, &via_itf_path);
444       mfib_table_entry_path_remove (mfib_index,
445                                     &mpfx_report,
446                                     MFIB_SOURCE_IGMP, &via_itf_path);
447
448       /*
449        * remove interface from proxy device
450        * if this device is upstream, delete proxy device
451        */
452       if (config->mode == IGMP_MODE_ROUTER)
453         igmp_proxy_device_add_del_interface (config->proxy_device_id,
454                                              config->sw_if_index, 0);
455       else if (config->mode == IGMP_MODE_HOST)
456         igmp_proxy_device_add_del (config->proxy_device_id,
457                                    config->sw_if_index, 0);
458
459       igmp_clear_config (config);
460       im->igmp_config_by_sw_if_index[config->sw_if_index] = ~0;
461       hash_free (config->igmp_group_by_key);
462       pool_put (im->configs, config);
463     }
464   else
465     {
466       return -1;
467     }
468
469   return (0);
470 }
471
472 /** \brief igmp initialization
473     @param vm - vlib main
474
475     initialize igmp plugin. Initialize igmp_main, set mfib to allow igmp traffic.
476 */
477 static clib_error_t *
478 igmp_init (vlib_main_t * vm)
479 {
480   igmp_main_t *im = &igmp_main;
481
482   im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32));
483   im->logger = vlib_log_register_class ("igmp", 0);
484
485   IGMP_DBG ("initialized");
486
487   return (0);
488 }
489
490 /* *INDENT-OFF* */
491 VLIB_INIT_FUNCTION (igmp_init) =
492 {
493   .runs_after = VLIB_INITS("ip4_lookup_init"),
494 };
495 VLIB_PLUGIN_REGISTER () =
496 {
497   .version = VPP_BUILD_VER,
498   .description = "Internet Group Management Protocol (IGMP)",
499 };
500 /* *INDENT-ON* */
501
502 /*
503  * fd.io coding-style-patch-verification: ON
504  *
505  * Local Variables:
506  * eval: (c-set-style "gnu")
507  * End:
508  */