IGMP: proxy device
[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 for_us_path =
350     {
351       .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
352       .frp_addr = zero_addr,
353       .frp_sw_if_index = 0xffffffff,
354       .frp_fib_index = 0,
355       .frp_weight = 1,
356       .frp_flags = FIB_ROUTE_PATH_LOCAL,
357     };
358   fib_route_path_t via_itf_path =
359     {
360       .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
361       .frp_addr = zero_addr,
362       .frp_sw_if_index = sw_if_index,
363       .frp_fib_index = 0,
364       .frp_weight = 1,
365     };
366   /* *INDENT-ON* */
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,
370                                                      sw_if_index);
371   if (!config && enable)
372     {
373       u32 ii;
374
375       vec_validate_init_empty (im->igmp_config_by_sw_if_index,
376                                sw_if_index, ~0);
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;
383       config->mode = mode;
384       config->proxy_device_id = ~0;
385
386       for (ii = 0; ii < IGMP_CONFIG_N_TIMERS; ii++)
387         config->timers[ii] = IGMP_TIMER_ID_INVALID;
388
389       if (IGMP_MODE_ROUTER == mode)
390         {
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);
395         }
396
397       config->adj_index =
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);
402       {
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])
406           {
407             /* first config in this FIB */
408             mfib_table_entry_path_update (mfib_index,
409                                           &mpfx_general_query,
410                                           MFIB_SOURCE_IGMP,
411                                           &for_us_path,
412                                           MFIB_ITF_FLAG_FORWARD);
413             mfib_table_entry_path_update (mfib_index,
414                                           &mpfx_report,
415                                           MFIB_SOURCE_IGMP,
416                                           &for_us_path,
417                                           MFIB_ITF_FLAG_FORWARD);
418           }
419         mfib_table_entry_path_update (mfib_index,
420                                       &mpfx_general_query,
421                                       MFIB_SOURCE_IGMP,
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);
426       }
427     }
428   else if (config && !enable)
429     {
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])
433         {
434           /* last config in this FIB */
435           mfib_table_entry_path_remove (mfib_index,
436                                         &mpfx_general_query,
437                                         MFIB_SOURCE_IGMP, &for_us_path);
438           mfib_table_entry_path_remove (mfib_index,
439                                         &mpfx_report,
440                                         MFIB_SOURCE_IGMP, &for_us_path);
441         }
442
443       mfib_table_entry_path_remove (mfib_index,
444                                     &mpfx_general_query,
445                                     MFIB_SOURCE_IGMP, &via_itf_path);
446       mfib_table_entry_path_remove (mfib_index,
447                                     &mpfx_report,
448                                     MFIB_SOURCE_IGMP, &via_itf_path);
449
450       /*
451        * remove interface from proxy device
452        * if this device is upstream, delete proxy device
453        */
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);
460
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);
465     }
466   else
467     {
468       return -1;
469     }
470
471   return (0);
472 }
473
474 /** \brief igmp initialization
475     @param vm - vlib main
476
477     initialize igmp plugin. Initialize igmp_main, set mfib to allow igmp traffic.
478 */
479 static clib_error_t *
480 igmp_init (vlib_main_t * vm)
481 {
482   clib_error_t *error;
483   igmp_main_t *im = &igmp_main;
484
485   if ((error = vlib_call_init_function (vm, ip4_lookup_init)))
486     return error;
487
488   im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32));
489
490   im->logger = vlib_log_register_class ("igmp", 0);
491
492   IGMP_DBG ("initialized");
493
494   return (error);
495 }
496
497 VLIB_INIT_FUNCTION (igmp_init);
498 /* *INDENT-OFF* */
499 VLIB_PLUGIN_REGISTER () = {
500   .version = VPP_BUILD_VER,
501   .description = "IGMP messaging",
502 };
503 /* *INDENT-ON* */
504
505 /*
506  * fd.io coding-style-patch-verification: ON
507  *
508  * Local Variables:
509  * eval: (c-set-style "gnu")
510  * End:
511  */