IGMP plugin
[vpp.git] / src / plugins / igmp / input.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 <vlib/unix/unix.h>
23 #include <vnet/adj/adj_mcast.h>
24
25 #include <igmp/igmp.h>
26 #include <igmp/error.h>
27
28 #include <limits.h>
29
30 /* TODO: mld...
31 typedef enum
32 {
33   MLD_INPUT_NEXT_DROP,
34   ...
35 } mld_input_next_t;
36 */
37
38 typedef enum
39 {
40   IGMP_INPUT_NEXT_DROP,
41   IGMP_INPUT_NEXT_PARSE_QUERY,
42   IGMP_INPUT_NEXT_PARSE_REPORT,
43   IGMP_INPUT_N_NEXT,
44 } igmp_input_next_t;
45
46 typedef enum
47 {
48   IGMP_PARSE_QUERY_NEXT_DROP,
49   IGMP_PARSE_QUERY_N_NEXT,
50 } igmp_parse_query_next_t;
51
52 typedef enum
53 {
54   IGMP_PARSE_REPORT_NEXT_DROP,
55   IGMP_PARSE_REPORT_N_NEXT,
56 } igmp_parse_report_next_t;
57
58 char *igmp_error_strings[] = {
59 #define _(sym,string) string,
60   foreach_igmp_error
61 #undef _
62 };
63
64 typedef struct
65 {
66   u32 next_index;
67   u32 sw_if_index;
68
69   u8 packet_data[64];
70 } igmp_input_trace_t;
71
72 static u8 *
73 format_igmp_input_trace (u8 * s, va_list * va)
74 {
75   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *);
76   CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *);
77   igmp_input_trace_t *t = va_arg (*va, igmp_input_trace_t *);
78
79   s =
80     format (s, "sw_if_index %u next-index %u", t->sw_if_index, t->next_index);
81   s =
82     format (s, "\n%U", format_igmp_header, t->packet_data,
83             sizeof (t->packet_data));
84   return s;
85 }
86
87 static u8 *
88 format_igmp_parse_report_trace (u8 * s, va_list * va)
89 {
90   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *);
91   CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *);
92   igmp_input_trace_t *t = va_arg (*va, igmp_input_trace_t *);
93
94   s =
95     format (s, "sw_if_index %u next-index %u", t->sw_if_index, t->next_index);
96   s =
97     format (s, "\n%U", format_igmp_report_v3, t->packet_data,
98             sizeof (t->packet_data));
99   return s;
100 }
101
102 static u8 *
103 format_igmp_parse_query_trace (u8 * s, va_list * va)
104 {
105   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*va, vlib_main_t *);
106   CLIB_UNUSED (vlib_node_t * node) = va_arg (*va, vlib_node_t *);
107   igmp_input_trace_t *t = va_arg (*va, igmp_input_trace_t *);
108
109   s =
110     format (s, "sw_if_index %u next-input %u", t->sw_if_index, t->next_index);
111   s =
112     format (s, "\n%U", format_igmp_query_v3, t->packet_data,
113             sizeof (t->packet_data));
114   return s;
115 }
116
117 uword
118 igmp_input (vlib_main_t * vm, vlib_node_runtime_t * node,
119             vlib_frame_t * frame)
120 {
121   DBG ("IGMP_INPUT");
122   u32 n_left_from, *from, *to_next;
123   igmp_parse_query_next_t next_index;
124   vlib_node_runtime_t *error_node =
125     vlib_node_get_runtime (vm, igmp_input_node.index);
126   u8 error;
127   ip_csum_t sum;
128   u16 csum;
129
130   error = IGMP_ERROR_NONE;
131
132   from = vlib_frame_vector_args (frame);
133   n_left_from = frame->n_vectors;
134   next_index = node->cached_next_index;
135
136   while (n_left_from > 0)
137     {
138       u32 n_left_to_next;
139
140       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
141
142       while (n_left_from > 0 && n_left_to_next > 0)
143         {
144           vlib_buffer_t *b;
145           ip4_header_t *ip;
146           u32 bi, next;
147           next = IGMP_INPUT_NEXT_DROP;
148
149           bi = from[0];
150           to_next[0] = bi;
151           from++;
152           to_next++;
153           n_left_from--;
154           n_left_to_next--;
155
156           b = vlib_get_buffer (vm, bi);
157           ip = vlib_buffer_get_current (b);
158
159           if (ip->protocol != 2)
160             {
161               error = IGMP_ERROR_INVALID_PROTOCOL;
162               next = IGMP_INPUT_NEXT_DROP;
163               goto next_buffer;
164             }
165
166           vlib_buffer_advance (b, ip4_header_bytes (ip));
167
168           igmp_header_t *igmp = vlib_buffer_get_current (b);
169
170           u16 checksum = igmp->checksum;
171           igmp->checksum = 0;
172           sum = ip_incremental_checksum (0, igmp,
173                                          clib_net_to_host_u16 (ip->length) -
174                                          ip4_header_bytes (ip));
175           igmp->checksum = checksum;
176           csum = ~ip_csum_fold (sum);
177           if (checksum != csum)
178             {
179               error = IGMP_ERROR_BAD_CHECKSUM;
180               next = IGMP_INPUT_NEXT_DROP;
181               goto next_buffer;
182             }
183
184           /* TODO: IGMPv2 and IGMPv1 */
185           switch (igmp->type)
186             {
187             case IGMP_TYPE_membership_query:
188               next = IGMP_INPUT_NEXT_PARSE_QUERY;
189               break;
190             case IGMP_TYPE_membership_report_v3:
191               next = IGMP_INPUT_NEXT_PARSE_REPORT;
192               break;
193             default:
194               error = IGMP_ERROR_UNKNOWN_TYPE;
195               next = IGMP_INPUT_NEXT_DROP;
196               break;
197             }
198         next_buffer:
199           b->error = error_node->errors[error];
200
201           if (node->flags & VLIB_NODE_FLAG_TRACE)
202             {
203               igmp_input_trace_t *tr;
204               tr = vlib_add_trace (vm, node, b, sizeof (*tr));
205               tr->next_index = next;
206               tr->sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
207               clib_memcpy (tr->packet_data, vlib_buffer_get_current (b),
208                            sizeof (tr->packet_data));
209             }
210
211           vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
212                                            n_left_to_next, bi, next);
213         }
214       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
215     }
216
217   return frame->n_vectors;
218 }
219
220 /* *INDENT-OFF* */
221 VLIB_REGISTER_NODE (igmp_input_node) =
222 {
223   .function = igmp_input,
224   .name = "igmp-input",
225   .vector_size = sizeof (u32),
226
227   .format_buffer = format_igmp_header,
228   .format_trace = format_igmp_input_trace,
229
230   .n_errors = IGMP_N_ERROR,
231   .error_strings = igmp_error_strings,
232
233   .n_next_nodes = IGMP_INPUT_N_NEXT,
234   .next_nodes = {
235       [IGMP_INPUT_NEXT_DROP] = "error-drop",
236       [IGMP_INPUT_NEXT_PARSE_QUERY] = "igmp-parse-query",
237       [IGMP_INPUT_NEXT_PARSE_REPORT] = "igmp-parse-report",
238   }
239 };
240 /* *INDENT-ON* */
241
242 uword
243 igmp_parse_query (vlib_main_t * vm, vlib_node_runtime_t * node,
244                   vlib_frame_t * frame)
245 {
246   DBG ("IGMP_PARSE_QUERY");
247
248   u32 n_left_from, *from, *to_next;
249   igmp_parse_query_next_t next_index;
250   igmp_main_t *im = &igmp_main;
251   igmp_config_t *config;
252
253   from = vlib_frame_vector_args (frame);
254   n_left_from = frame->n_vectors;
255   next_index = node->cached_next_index;
256
257   while (n_left_from > 0)
258     {
259       u32 n_left_to_next;
260
261       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
262
263       while (n_left_from > 0 && n_left_to_next > 0)
264         {
265           vlib_buffer_t *b;
266           u32 sw_if_index, bi, next;
267           next = IGMP_PARSE_QUERY_NEXT_DROP;
268
269           bi = from[0];
270           to_next[0] = bi;
271           from++;
272           to_next++;
273           n_left_from--;
274           n_left_to_next--;
275
276           b = vlib_get_buffer (vm, bi);
277           sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
278
279           igmp_membership_query_v3_t *igmp = vlib_buffer_get_current (b);
280           ASSERT (igmp->header.type == IGMP_TYPE_membership_query);
281
282           /* if group address is zero, this is a general query */
283           if (igmp->dst.as_u32 == 0)
284             {
285               config = igmp_config_lookup (im, sw_if_index);
286               if (!config)
287                 {
288                   DBG ("No config on interface %u", sw_if_index);
289                 }
290               else
291                 {
292                   /* WIP
293                    *
294                    * TODO: divide to multipe reports in random time range [now, max resp time]
295                    */
296                   u32 seed = vlib_time_now (vm);
297                   f64 next_resp_time = random_f64 (&seed) *
298                     (f64) (igmp->header.code / 10) + vlib_time_now (vm);
299                   config->flags |= IGMP_CONFIG_FLAG_CAN_SEND_REPORT;
300                   igmp_create_int_timer (next_resp_time, sw_if_index,
301                                          igmp_send_report);
302                   vlib_process_signal_event (vm,
303                                              igmp_timer_process_node.index,
304                                              IGMP_PROCESS_EVENT_UPDATE_TIMER,
305                                              0);
306                 }
307             }
308
309           if (node->flags & VLIB_NODE_FLAG_TRACE)
310             {
311               igmp_input_trace_t *tr;
312               tr = vlib_add_trace (vm, node, b, sizeof (*tr));
313               tr->next_index = next;
314               tr->sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
315               clib_memcpy (tr->packet_data, vlib_buffer_get_current (b),
316                            sizeof (tr->packet_data));
317             }
318           vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
319                                            n_left_to_next, bi, next);
320         }
321       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
322     }
323
324   return frame->n_vectors;
325 }
326
327 /* *INDENT-OFF* */
328 VLIB_REGISTER_NODE (igmp_parse_query_node) =
329 {
330   .function = igmp_parse_query,
331   .name = "igmp-parse-query",
332   .vector_size = sizeof (u32),
333
334   .format_buffer = format_igmp_query_v3,
335   .format_trace = format_igmp_parse_query_trace,
336
337   .n_errors = IGMP_N_ERROR,
338   .error_strings = igmp_error_strings,
339
340   .n_next_nodes = IGMP_PARSE_QUERY_N_NEXT,
341   .next_nodes = {
342     [IGMP_PARSE_QUERY_NEXT_DROP] = "error-drop",
343   }
344 };
345 /* *INDENT-ON* */
346
347 uword
348 igmp_parse_report (vlib_main_t * vm, vlib_node_runtime_t * node,
349                    vlib_frame_t * frame)
350 {
351   DBG ("IGMP_PARSE_REPORT");
352
353   igmp_main_t *im = &igmp_main;
354   u32 n_left_from, *from, *to_next;
355   igmp_input_next_t next_index;
356   igmp_config_t *config;
357   igmp_sg_t *sg;
358   igmp_membership_group_v3_t *group;
359   ip4_address_t *src;
360   igmp_sg_key_t key;
361   memset (&key, 0, sizeof (igmp_sg_key_t));
362   ip46_address_t saddr;
363   memset (&saddr, 0, sizeof (ip46_address_t));
364   ip46_address_t gaddr;
365   memset (&gaddr, 0, sizeof (ip46_address_t));
366   u32 len;
367   vlib_node_runtime_t *error_node =
368     vlib_node_get_runtime (vm, igmp_input_node.index);
369   u8 error;
370
371   from = vlib_frame_vector_args (frame);
372   n_left_from = frame->n_vectors;
373   next_index = node->cached_next_index;
374
375   while (n_left_from > 0)
376     {
377       u32 n_left_to_next;
378
379       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
380
381       while (n_left_from > 0 && n_left_to_next > 0)
382         {
383           vlib_buffer_t *b;
384           u32 sw_if_index, bi, next;
385           next = IGMP_PARSE_REPORT_NEXT_DROP;
386
387           bi = from[0];
388           to_next[0] = bi;
389           from++;
390           to_next++;
391           n_left_from--;
392           n_left_to_next--;
393
394           b = vlib_get_buffer (vm, bi);
395
396           error = IGMP_ERROR_NONE;
397           b->error = error_node->errors[error];
398
399           sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
400
401           igmp_membership_report_v3_t *igmp = vlib_buffer_get_current (b);
402           ASSERT (igmp->header.type == IGMP_TYPE_membership_report_v3);
403           len = sizeof (igmp_membership_report_v3_t);
404
405           /* if interface (S,G)s were configured by CLI/API goto next frame */
406           config = igmp_config_lookup (im, sw_if_index);
407           if (config)
408             {
409               config->flags |= IGMP_CONFIG_FLAG_QUERY_RESP_RECVED;
410               if (config->cli_api_configured)
411                 {
412                   DBG ("Interface %u has (S,G)s configured by CLI/API",
413                        sw_if_index);
414                   error = IGMP_ERROR_CLI_API_CONFIG;
415                   b->error = error_node->errors[error];
416                   goto next_frame;
417                 }
418             }
419           DBG ("interface %u", sw_if_index);
420           int i, j = 0;
421           for (i = 0; i < clib_net_to_host_u16 (igmp->n_groups); i++)
422             {
423               group = group_ptr (igmp, len);
424               src = group->src_addresses;
425               if (group->type == IGMP_MEMBERSHIP_GROUP_mode_is_filter_include)
426                 {
427                   for (j = 0;
428                        j < clib_net_to_host_u16 (group->n_src_addresses); j++)
429                     {
430                       /* update (S,G) expiration timer */
431                       key.saddr.ip4 = *src;
432                       key.gaddr.ip4 = group->dst_address;
433                       sg = igmp_sg_lookup (config, &key);
434                       if (sg)
435                         sg->exp_time = vlib_time_now (vm) + IGMP_SG_TIMER;
436                       src++;
437                     }
438                 }
439               else if (group->type ==
440                        IGMP_MEMBERSHIP_GROUP_mode_is_filter_exclude)
441                 {
442                   for (j = 0;
443                        j < clib_net_to_host_u16 (group->n_src_addresses); j++)
444                     {
445                       /* nothing for now... */
446                       src++;
447                     }
448                 }
449               else if (group->type ==
450                        IGMP_MEMBERSHIP_GROUP_change_to_filter_include)
451                 {
452                   for (j = 0;
453                        j < clib_net_to_host_u16 (group->n_src_addresses); j++)
454                     {
455                       /* add new (S,G) to interface */
456                       saddr.ip4 = *src;
457                       gaddr.ip4 = group->dst_address;
458                       igmp_listen (vm, 1, sw_if_index, saddr, gaddr, 0);
459                       src++;
460                     }
461                 }
462               else if (group->type ==
463                        IGMP_MEMBERSHIP_GROUP_change_to_filter_exclude)
464                 {
465                   for (j = 0;
466                        j < clib_net_to_host_u16 (group->n_src_addresses); j++)
467                     {
468                       /* remove (S,G) from interface */
469                       saddr.ip4 = *src;
470                       gaddr.ip4 = group->dst_address;
471                       igmp_listen (vm, 0, sw_if_index, saddr, gaddr, 0);
472                       src++;
473                     }
474                 }
475               else if (group->type == IGMP_MEMBERSHIP_GROUP_allow_new_sources)
476                 {
477                   for (j = 0;
478                        j < clib_net_to_host_u16 (group->n_src_addresses); j++)
479                     {
480                       /* nothing for now... */
481                       src++;
482                     }
483                 }
484               else if (group->type == IGMP_MEMBERSHIP_GROUP_block_old_sources)
485                 {
486                   for (j = 0;
487                        j < clib_net_to_host_u16 (group->n_src_addresses); j++)
488                     {
489                       /* remove (S,G) from interface */
490                       saddr.ip4 = *src;
491                       gaddr.ip4 = group->dst_address;
492                       igmp_listen (vm, 0, sw_if_index, saddr, gaddr, 0);
493                       src++;
494                     }
495                 }
496               /*
497                * Unrecognized Record Type values MUST be silently ignored.
498                */
499
500               /* move ptr to next Group Record */
501               len +=
502                 sizeof (igmp_membership_group_v3_t) +
503                 (sizeof (ip4_address_t) * j);
504             }
505         next_frame:
506           if (node->flags & VLIB_NODE_FLAG_TRACE)
507             {
508               igmp_input_trace_t *tr;
509               tr = vlib_add_trace (vm, node, b, sizeof (*tr));
510               tr->next_index = next;
511               tr->sw_if_index = vnet_buffer (b)->sw_if_index[VLIB_RX];
512               clib_memcpy (tr->packet_data, vlib_buffer_get_current (b),
513                            sizeof (tr->packet_data));
514             }
515           vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
516                                            n_left_to_next, bi, next);
517         }
518       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
519     }
520
521   return frame->n_vectors;
522 }
523
524 /* *INDENT-OFF* */
525 VLIB_REGISTER_NODE (igmp_parse_report_node) =
526 {
527   .function = igmp_parse_report,
528   .name = "igmp-parse-report",
529   .vector_size = sizeof (u32),
530
531   .format_buffer = format_igmp_report_v3,
532   .format_trace = format_igmp_parse_report_trace,
533
534   .n_errors = IGMP_N_ERROR,
535   .error_strings = igmp_error_strings,
536
537   .n_next_nodes = IGMP_PARSE_REPORT_N_NEXT,
538   .next_nodes = {
539     [IGMP_PARSE_REPORT_NEXT_DROP] = "error-drop",
540   }
541 };
542 /* *INDENT-ON* */
543
544 /*
545  * fd.io coding-style-patch-verification: ON
546  *
547  * Local Variables:
548  * eval: (c-set-style "gnu")
549  * End:
550  */