VPP-1283: IPv4 PMTU missing MTU value in ICMP4 message.
[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
31 #include <limits.h>
32 #include <float.h>
33
34 igmp_main_t igmp_main;
35
36 void
37 igmp_clear_group (igmp_config_t * config, igmp_group_t * group)
38 {
39   igmp_src_t *src;
40
41   ASSERT (config);
42   ASSERT (group);
43
44   IGMP_DBG ("group_type %u, sw_if_index %d", group->type,
45             config->sw_if_index);
46
47   /* *INDENT-OFF* */
48   pool_foreach (src, group->srcs, (
49     {
50       clib_mem_free (src->key);
51     }));
52   /* *INDENT-ON* */
53   pool_free (group->srcs);
54   hash_free (group->igmp_src_by_key);
55
56   hash_unset_mem (config->igmp_group_by_key, group->key);
57   clib_mem_free (group->key);
58   pool_put (config->groups, group);
59 }
60
61 void
62 igmp_clear_config (igmp_config_t * config)
63 {
64   igmp_main_t *im = &igmp_main;
65   igmp_group_t *group;
66
67   ASSERT (config);
68   /* *INDENT-OFF* */
69   pool_foreach (group, config->groups, (
70     {
71       igmp_clear_group (config, group);
72     }));
73   /* *INDENT-ON* */
74   pool_free (config->groups);
75   hash_free (config->igmp_group_by_key);
76
77   hash_unset_mem (im->igmp_config_by_sw_if_index, &config->sw_if_index);
78   pool_put (im->configs, config);
79 }
80
81 /** \brief igmp timer compare
82     @param _a - igmp timer
83     @param _b - igmp timer
84
85     Compare function for igmp_timer_t sorting.
86 */
87 int
88 igmp_timer_compare (const void *_a, const void *_b)
89 {
90   const igmp_timer_t *a = _a;
91   const igmp_timer_t *b = _b;
92   f64 dt = b->exp_time - a->exp_time;
93   return dt < 0 ? -1 : (dt > 0 ? +1 : 0);
94 }
95
96 void
97 igmp_sort_timers (igmp_timer_t * timers)
98 {
99   vlib_main_t *vm = vlib_get_main ();
100
101   qsort (timers, vec_len (timers), sizeof (igmp_timer_t), igmp_timer_compare);
102
103   vlib_process_signal_event (vm, igmp_timer_process_node.index,
104                              IGMP_PROCESS_EVENT_UPDATE_TIMER, 0);
105 }
106
107 void
108 igmp_create_int_timer (f64 time, u32 sw_if_index,
109                        igmp_timer_function_t * func)
110 {
111   igmp_main_t *im = &igmp_main;
112   igmp_timer_t *timer;
113
114   pool_get (im->timers, timer);
115   memset (timer, 0, sizeof (igmp_timer_t));
116   timer->func = func;
117   timer->exp_time = time;
118   timer->sw_if_index = sw_if_index;
119
120   igmp_sort_timers (im->timers);
121 }
122
123 void
124 igmp_create_group_timer (f64 time, u32 sw_if_index, igmp_key_t * gkey,
125                          igmp_timer_function_t * func)
126 {
127   igmp_main_t *im = &igmp_main;
128   igmp_timer_t *timer;
129
130   pool_get (im->timers, timer);
131   memset (timer, 0, sizeof (igmp_timer_t));
132   timer->func = func;
133   timer->exp_time = time;
134   timer->sw_if_index = sw_if_index;
135
136
137   ASSERT (gkey);
138   /* duplicate keys, to prevent segmentation fault if (S,G) is removed */
139   timer->data = clib_mem_alloc (sizeof (igmp_key_t));
140   clib_memcpy (&((igmp_key_t *) timer->data)[0], gkey, sizeof (igmp_key_t));
141
142   igmp_sort_timers (im->timers);
143 }
144
145 void
146 igmp_create_src_timer (f64 time, u32 sw_if_index, igmp_key_t * gkey,
147                        igmp_key_t * skey, igmp_timer_function_t * func)
148 {
149   igmp_main_t *im = &igmp_main;
150   igmp_timer_t *timer;
151
152   pool_get (im->timers, timer);
153   memset (timer, 0, sizeof (igmp_timer_t));
154   timer->func = func;
155   timer->exp_time = time;
156   timer->sw_if_index = sw_if_index;
157
158   ASSERT (gkey);
159   ASSERT (skey);
160   /* duplicate keys, to prevent segmentation fault if (S,G) is removed */
161   timer->data = clib_mem_alloc (sizeof (igmp_key_t) * 2);
162   clib_memcpy (&((igmp_key_t *) timer->data)[0], gkey, sizeof (igmp_key_t));
163   clib_memcpy (&((igmp_key_t *) timer->data)[1], skey, sizeof (igmp_key_t));
164
165   igmp_sort_timers (im->timers);
166 }
167
168 /** \brief igmp get next timer
169     @param im - igmp main
170
171     Get next timer.
172 */
173 always_inline igmp_timer_t *
174 igmp_get_next_timer (igmp_main_t * im)
175 {
176   if (pool_elts (im->timers) > 0)
177     return vec_elt_at_index (im->timers, pool_elts (im->timers) - 1);
178   return NULL;
179 }
180
181 /*
182 static void
183 igmp_create_report_v2 (vlib_buffer_t * b, igmp_config_t * config)
184 {
185   ip_csum_t sum;
186   u16 csum;
187   igmp_main_t *im = &igmp_main;
188   igmp_sg_t *sg;
189
190   sg = vec_elt_at_index (config->sg, im->next_index.sg_index);
191
192   igmp_message_t *igmp = (igmp_message_t *) (vlib_buffer_get_current (b));
193   memset (igmp, 0, sizeof (igmp_message_t));
194
195   clib_memcpy (&igmp->dst, &sg->gaddr.ip4, sizeof (ip4_address_t));
196   igmp->header.type =
197     (sg->group_type == IGMP_MEMBERSHIP_GROUP_block_old_sources) ?
198     IGMP_TYPE_leave_group_v2 : IGMP_TYPE_membership_report_v2;
199   sum = ip_incremental_checksum (0, igmp, sizeof (igmp_message_t));
200   csum = ~ip_csum_fold (sum);
201   igmp->header.checksum = csum;
202
203   b->current_data += sizeof (igmp_message_t);
204   b->current_length += sizeof (igmp_message_t);
205 }
206 */
207
208 /* TODO: divide (S,G)s to multiple reports...
209  * - create report limited by <packet size|number of (S,G)s>?
210  * - save loop state
211  * - on next timer continue loop
212  * - case of new query -> reset loop
213  */
214
215 /** \brief igmp create report all (v3)
216     @param b - vlib buffer
217     @param config - igmp configuration
218     @param group - igmp group
219
220     Create IGMPv3 report. If group is NULL, send all groups on interface.
221 */
222 static void
223 igmp_create_report_v3 (vlib_buffer_t * b, igmp_config_t * config,
224                        igmp_group_t * group)
225 {
226   ip_csum_t sum;
227   u16 csum;
228   u32 len = 0;
229   int i;
230
231   igmp_src_t *src;
232
233   igmp_membership_group_v3_t *igmp_group;
234
235   len = sizeof (igmp_membership_report_v3_t);
236
237   igmp_membership_report_v3_t *igmp =
238     (igmp_membership_report_v3_t *) (vlib_buffer_get_current (b));
239   memset (igmp, 0, sizeof (igmp_membership_report_v3_t));
240
241   igmp->header.type = IGMP_TYPE_membership_report_v3;
242   igmp->n_groups =
243     clib_net_to_host_u16 ((group) ? 1 : pool_elts (config->groups));
244
245   /* get pointer to first group */
246   igmp_group = igmp->groups;
247
248   /* if group is not NULL, send the specified group */
249   if (group)
250     {
251       memset (igmp_group, 0, sizeof (igmp_membership_group_v3_t));
252       igmp_group->type = group->type;
253       igmp_group->n_src_addresses =
254         clib_host_to_net_u16 (pool_elts (group->srcs));
255       igmp_group->dst_address = group->addr.ip4;
256       i = 0;
257       /* *INDENT-OFF* */
258       pool_foreach (src, group->srcs, (
259         {
260           igmp_group->src_addresses[i++] = src->addr.ip4;
261         }));
262       /* *INDENT-ON* */
263       len += sizeof (ip4_address_t) * i;
264       len += sizeof (igmp_membership_group_v3_t);
265     }
266   else
267     {
268       /* *INDENT-OFF* */
269       pool_foreach (group, config->groups, (
270         {
271           memset (igmp_group, 0, sizeof (igmp_membership_group_v3_t));
272           igmp_group->type = group->type;
273           igmp_group->n_src_addresses =
274             clib_host_to_net_u16 (pool_elts (group->srcs));
275           igmp_group->dst_address = group->addr.ip4;
276           i = 0;
277           pool_foreach (src, group->srcs, (
278             {
279               igmp_group->src_addresses[i++] = src->addr.ip4;
280             }));
281           len += sizeof (ip4_address_t) * i;
282           len += sizeof (igmp_membership_group_v3_t);
283           igmp_group = group_ptr (igmp, len);
284         }));
285       /* *INDENT-ON* */
286     }
287
288   sum = ip_incremental_checksum (0, igmp, len);
289   csum = ~ip_csum_fold (sum);
290   igmp->header.checksum = csum;
291
292   b->current_data += len;
293   b->current_length += len;
294 }
295
296 /** \brief igmp create query (v3)
297     @param b - vlib buffer
298     @param config - configuration that sends the query
299     @param group - if not NULL, create Group-specific query
300
301     Create igmp v3 qeury inside vlib buffer b.
302     If group == NULL create general query,
303     else, create group specific query.
304 */
305 static void
306 igmp_create_query_v3 (vlib_buffer_t * b, igmp_config_t * config,
307                       igmp_group_t * group)
308 {
309   vlib_main_t *vm = vlib_get_main ();
310   ip_csum_t sum;
311   u16 csum;
312
313   igmp_membership_query_v3_t *igmp =
314     (igmp_membership_query_v3_t *) (vlib_buffer_get_current (b));
315   memset (igmp, 0, sizeof (igmp_membership_query_v3_t));
316
317   igmp->header.type = IGMP_TYPE_membership_query;
318   igmp->header.code = 100;
319
320   config->flags &= ~IGMP_CONFIG_FLAG_QUERY_RESP_RECVED;
321   igmp_create_int_timer (vlib_time_now (vm) + (f64) (igmp->header.code / 10),
322                          config->sw_if_index, igmp_query_resp_exp);
323
324   if (PREDICT_FALSE (group != NULL))
325     clib_memcpy (&igmp->dst, &group->addr.ip4, sizeof (ip4_address_t));
326
327   sum =
328     ip_incremental_checksum (0, igmp, sizeof (igmp_membership_query_v3_t));
329   csum = ~ip_csum_fold (sum);
330   igmp->header.checksum = csum;
331
332   b->current_data += sizeof (igmp_membership_query_v3_t);
333   b->current_length += sizeof (igmp_membership_query_v3_t);
334 }
335
336 /** \brief igmp create ip4
337     @param b - vlib buffer
338     @param config - igmp configuration
339     @param group - igmp membership group
340     @param is_report - if zero create query, else create report
341
342     Create ip4 header in vlib buffer b.
343 */
344 static void
345 igmp_create_ip4 (vlib_buffer_t * b, igmp_config_t * config,
346                  igmp_group_t * group, u8 is_report)
347 {
348   ip_lookup_main_t *lm = &ip4_main.lookup_main;
349
350   ip4_header_t *ip4 = (ip4_header_t *) (vlib_buffer_get_current (b));
351   memset (ip4, 0, sizeof (ip4_header_t));
352   ip4->ip_version_and_header_length = 0x45;
353   ip4->ttl = 1;
354   ip4->protocol = 2;
355   ip4->tos = 0xc0;
356
357   u32 if_add_index =
358     lm->if_address_pool_index_by_sw_if_index[config->sw_if_index];
359   if (PREDICT_TRUE (if_add_index != ~0))
360     {
361       ip_interface_address_t *if_add =
362         pool_elt_at_index (lm->if_address_pool, if_add_index);
363       ip4_address_t *if_ip = ip_interface_address_get_address (lm, if_add);
364       clib_memcpy (&ip4->src_address, if_ip, sizeof (ip4_address_t));
365     }
366
367   if (is_report)
368     ip4->dst_address.as_u32 =
369       clib_host_to_net_u32 (IGMP_MEMBERSHIP_REPORT_ADDRESS);
370   else
371     {
372       if ((group != NULL))
373         clib_memcpy (&ip4->dst_address, &group->addr.ip4,
374                      sizeof (ip4_address_t));
375       else
376         ip4->dst_address.as_u32 =
377           clib_host_to_net_u32 (IGMP_GENERAL_QUERY_ADDRESS);
378     }
379
380   b->current_data += ip4_header_bytes (ip4);
381   b->current_length += ip4_header_bytes (ip4);
382
383   config->next_create_msg (b, config, group);
384   ip4->length = clib_host_to_net_u16 (b->current_length);
385
386   ip4->checksum = ip4_header_checksum (ip4);
387 }
388
389
390 /** \brief igmp send message
391     @param vm - vlib main
392     @param node - vlib runtime node
393     @param im - igmp main
394     @param config - igmp configuration
395     @param group - igmp mebership group
396     @param is_report - 0 == qeury, else report
397
398     Send an igmp message. Get free vlib buffer fill it with igmp packet and transmit.
399 */
400 static void
401 igmp_send_msg (vlib_main_t * vm, vlib_node_runtime_t * node,
402                igmp_main_t * im, igmp_config_t * config, igmp_group_t * group,
403                u8 is_report)
404 {
405   u32 thread_index = vlib_get_thread_index ();
406   u32 *to_next;
407   u32 next_index = IGMP_NEXT_IP4_REWRITE_MCAST_NODE;
408
409   u32 n_free_bufs = vec_len (im->buffers[thread_index]);
410   if (PREDICT_FALSE (n_free_bufs < 1))
411     {
412       vec_validate (im->buffers[thread_index], 1 + n_free_bufs - 1);
413       n_free_bufs +=
414         vlib_buffer_alloc (vm, &im->buffers[thread_index][n_free_bufs], 1);
415       _vec_len (im->buffers[thread_index]) = n_free_bufs;
416     }
417
418   u32 n_left_to_next;
419   u32 next0 = next_index;
420   vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
421
422   if (n_left_to_next > 0)
423     {
424       vlib_buffer_t *b = 0;
425       u32 bi = 0;
426
427       if (n_free_bufs)
428         {
429           u32 last_buf = vec_len (im->buffers[thread_index]) - 1;
430           bi = im->buffers[thread_index][last_buf];
431           b = vlib_get_buffer (vm, bi);
432           _vec_len (im->buffers[thread_index]) = last_buf;
433           n_free_bufs--;
434           if (PREDICT_FALSE (n_free_bufs == 0))
435             {
436               n_free_bufs += vlib_buffer_alloc (vm,
437                                                 &im->buffers[thread_index]
438                                                 [n_free_bufs], 1);
439               _vec_len (im->buffers[thread_index]) = n_free_bufs;
440             }
441
442           b->current_data = 0;
443           b->current_length = 0;
444
445           igmp_create_ip4 (b, config, group, is_report);
446
447           b->current_data = 0;
448
449           b->total_length_not_including_first_buffer = 0;
450           b->flags = VLIB_BUFFER_TOTAL_LENGTH_VALID;
451           vnet_buffer (b)->sw_if_index[VLIB_RX] = (u32) ~ 0;
452           vnet_buffer (b)->ip.adj_index[VLIB_TX] = config->adj_index;
453           b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
454         }
455
456       to_next[0] = bi;
457       to_next += 1;
458       n_left_to_next -= 1;
459
460       vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
461                                        n_left_to_next, bi, next0);
462     }
463   vlib_put_next_frame (vm, node, next_index, n_left_to_next);
464 }
465
466 void
467 igmp_send_query (vlib_main_t * vm, vlib_node_runtime_t * rt, igmp_main_t * im,
468                  igmp_timer_t * timer)
469 {
470   igmp_config_t *config;
471 /* TODO: group-specific query: pass group key in timer */
472   igmp_group_t *group = NULL;
473
474   u32 sw_if_index = timer->sw_if_index;
475
476   pool_put (im->timers, timer);
477
478   config = igmp_config_lookup (im, sw_if_index);
479   if (!config)
480     return;
481
482   /* TODO: implement IGMPv2 */
483   config->next_create_msg = igmp_create_query_v3;
484   igmp_send_msg (vm, rt, im, config, group, /* is_report */ 0);
485
486   /* in case of group query we don't want to set up another qery timer */
487   if (PREDICT_TRUE (!group))
488     igmp_create_int_timer (vlib_time_now (vm) + IGMP_QUERY_TIMER, sw_if_index,
489                            igmp_send_query);
490 }
491
492 void
493 igmp_query_resp_exp (vlib_main_t * vm, vlib_node_runtime_t * rt,
494                      igmp_main_t * im, igmp_timer_t * timer)
495 {
496   igmp_config_t *config;
497 /* TODO: group-specific query: pass group key in timer */
498   igmp_group_t *group = NULL;
499
500   u32 sw_if_index = timer->sw_if_index;
501
502   pool_put (im->timers, timer);
503
504   config = igmp_config_lookup (im, sw_if_index);
505   if (!config)
506     return;
507
508   /* if group != NULL this is a group-specific qeury timer */
509   if (PREDICT_FALSE (group != NULL))
510     {
511       if ((group->flags & IGMP_GROUP_FLAG_QUERY_RESP_RECVED) == 0)
512         {
513           igmp_clear_group (config, group);
514           return;
515         }
516     }
517   /* if report not received in max resp time clear igmp on interface */
518   if ((config->flags & IGMP_CONFIG_FLAG_QUERY_RESP_RECVED) == 0)
519     {
520       igmp_clear_config (config);
521     }
522 }
523
524 void
525 igmp_send_report (vlib_main_t * vm, vlib_node_runtime_t * rt,
526                   igmp_main_t * im, igmp_timer_t * timer)
527 {
528   igmp_config_t *config;
529
530   u32 sw_if_index = timer->sw_if_index;
531
532   pool_put (im->timers, timer);
533
534   config = igmp_config_lookup (im, sw_if_index);
535   if (!config)
536     return;
537
538   if (config->flags & IGMP_CONFIG_FLAG_CAN_SEND_REPORT)
539     {
540       /* TODO: implement IGMPv2 and IGMPv1 */
541       config->next_create_msg = igmp_create_report_v3;
542       /* pass NULL as group to send all groups at once */
543       igmp_send_msg (vm, rt, im, config, NULL, /* is_report */ 1);
544       /* WIP: unset flag after all reports sent */
545       config->flags &= ~IGMP_CONFIG_FLAG_CAN_SEND_REPORT;
546     }
547 }
548
549 void
550 igmp_send_state_changed (vlib_main_t * vm, vlib_node_runtime_t * rt,
551                          igmp_main_t * im, igmp_timer_t * timer)
552 {
553   igmp_config_t *config;
554   igmp_group_t *group;
555   igmp_src_t *src;
556   igmp_key_t gkey;
557
558   u32 sw_if_index = timer->sw_if_index;
559   IGMP_DBG ("sw_if_index %d", sw_if_index);
560
561   ASSERT (timer->data);
562   clib_memcpy (&gkey, timer->data, sizeof (igmp_key_t));
563
564   pool_put (im->timers, timer);
565
566   config = igmp_config_lookup (im, sw_if_index);
567   if (!config)
568     return;
569
570   group = igmp_group_lookup (config, &gkey);
571   if (!group)
572     return;
573
574   config->next_create_msg = igmp_create_report_v3;
575   igmp_send_msg (vm, rt, im, config, group, /* is_report */ 1);
576
577   IGMP_DBG ("group_type %u", group->type);
578
579   if (group->type == IGMP_MEMBERSHIP_GROUP_change_to_filter_include)
580     {
581       igmp_key_t new_gkey;
582       igmp_group_t *new_group;
583       igmp_src_t *new_src;
584
585       clib_memcpy (&new_gkey.data, &group->addr, sizeof (ip46_address_t));
586       new_gkey.group_type = IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
587
588       new_group = igmp_group_lookup (config, &new_gkey);
589       if (!new_group)
590         {
591           IGMP_DBG ("creating new group...");
592           pool_get (config->groups, new_group);
593           /* get valid pointer to old group */
594           group = igmp_group_lookup (config, &gkey);
595
596           memset (new_group, 0, sizeof (igmp_group_t));
597
598           clib_memcpy (&new_group->addr, &group->addr,
599                        sizeof (ip46_address_t));
600           new_group->n_srcs = 0;
601           new_group->type = new_gkey.group_type;
602
603           new_group->key = clib_mem_alloc (sizeof (igmp_key_t));
604           clib_memcpy (new_group->key, &new_gkey, sizeof (igmp_key_t));
605           new_group->igmp_src_by_key =
606             hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
607           hash_set_mem (config->igmp_group_by_key, new_group->key,
608                         new_group - config->groups);
609         }
610       /* *INDENT-OFF* */
611       /* loop through old group sources */
612       pool_foreach (src, group->srcs, (
613         {
614           /* add sources to new group */
615           new_src = igmp_src_lookup (new_group, src->key);
616           if (!new_src)
617             {
618               pool_get (new_group->srcs, new_src);
619               memset (new_src, 0, sizeof (igmp_src_t));
620               new_group->n_srcs += 1;
621               new_src->key = clib_mem_alloc (sizeof (igmp_key_t));
622               clib_memcpy (new_src->key, src->key, sizeof (igmp_key_t));
623               clib_memcpy (&new_src->addr, &src->addr,
624                 sizeof (ip46_address_t));
625
626               hash_set_mem (new_group->igmp_src_by_key, new_src->key,
627                     new_src - new_group->srcs);
628             }
629         }));
630       /* *INDENT-ON* */
631     }
632
633   /* remove group */
634   IGMP_DBG ("remove group");
635   igmp_clear_group (config, group);
636   if (pool_elts (config->groups) == 0)
637     {
638       hash_unset_mem (im->igmp_config_by_sw_if_index, &config->sw_if_index);
639       pool_put (im->configs, config);
640     }
641 }
642
643 void
644 igmp_src_exp (vlib_main_t * vm, vlib_node_runtime_t * rt,
645               igmp_main_t * im, igmp_timer_t * timer)
646 {
647   igmp_config_t *config;
648   igmp_group_t *group;
649   igmp_src_t *src;
650
651   ASSERT (timer->data);
652
653   igmp_key_t *gkey = (igmp_key_t *) & ((igmp_key_t *) timer->data)[0];
654   igmp_key_t *skey = (igmp_key_t *) & ((igmp_key_t *) timer->data)[1];
655
656   config = igmp_config_lookup (im, timer->sw_if_index);
657   if (!config)
658     goto done;
659   group = igmp_group_lookup (config, gkey);
660   if (!group)
661     goto done;
662   src = igmp_src_lookup (group, skey);
663   if (!src)
664     goto done;
665   /* check if this timer is valid */
666   if (timer->exp_time != src->exp_time)
667     {
668       timer->exp_time = src->exp_time;
669       igmp_sort_timers (im->timers);
670       return;
671     }
672
673   ip46_address_t saddr;
674   ip46_address_t gaddr;
675   clib_memcpy (&saddr, skey->data, sizeof (ip46_address_t));
676   clib_memcpy (&gaddr, gkey->data, sizeof (ip46_address_t));
677
678   /* source timer expired, remove src */
679   igmp_listen (vm, 0, timer->sw_if_index, saddr, gaddr, 0);
680 done:
681   clib_mem_free (timer->data);
682   pool_put (im->timers, timer);
683 }
684
685 /** \brief igmp timer process
686     @param vm - vlib main
687     @param rt - vlib runtime node
688     @param f - vlib frame
689
690     Handle igmp timers.
691 */
692 static uword
693 igmp_timer_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
694                     vlib_frame_t * f)
695 {
696   igmp_main_t *im = &igmp_main;
697   uword *event_data = 0, event_type;
698   f64 time_start;
699   igmp_timer_t *timer = NULL;
700   while (1)
701     {
702       /* suspend util timer expires */
703       if (NULL != timer)
704         vlib_process_wait_for_event_or_clock (vm,
705                                               timer->exp_time - time_start);
706       else
707         vlib_process_wait_for_event (vm);
708       time_start = vlib_time_now (vm);
709       event_type = vlib_process_get_events (vm, &event_data);
710       vec_reset_length (event_data);
711       if (event_type == IGMP_PROCESS_EVENT_UPDATE_TIMER)
712         goto next_timer;
713       IGMP_DBG ("time: %f", vlib_time_now (vm));
714       /* timer expired */
715       if (NULL != timer && timer->func != NULL)
716         timer->func (vm, rt, im, timer);
717     next_timer:
718       timer = igmp_get_next_timer (im);
719     }
720   return 0;
721 }
722
723 /* *INDENT-OFF* */
724 VLIB_REGISTER_NODE (igmp_timer_process_node) =
725 {
726   .function = igmp_timer_process,
727   .type = VLIB_NODE_TYPE_PROCESS,
728   .name = "igmp-timer-process",
729   .n_next_nodes = IGMP_N_NEXT,
730   .next_nodes =  {
731     [IGMP_NEXT_IP4_REWRITE_MCAST_NODE] = "ip4-rewrite-mcast",
732     [IGMP_NEXT_IP6_REWRITE_MCAST_NODE] = "ip6-rewrite-mcast",
733   }
734 };
735 /* *INDENT-ON* */
736
737 int
738 igmp_listen (vlib_main_t * vm, u8 enable, u32 sw_if_index,
739              ip46_address_t saddr, ip46_address_t gaddr,
740              u8 cli_api_configured)
741 {
742   igmp_main_t *im = &igmp_main;
743   igmp_config_t *config;
744   igmp_group_t *group;
745   igmp_src_t *src;
746   igmp_key_t skey;
747   igmp_key_t gkey;
748
749   igmp_membership_group_v3_type_t group_type =
750     (cli_api_configured) ?
751     IGMP_MEMBERSHIP_GROUP_change_to_filter_include :
752     IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
753   int rv = 0;
754
755   /* set the lookup keys */
756   skey.group_type = 0;
757   gkey.group_type = group_type;
758   clib_memcpy (&skey.data, &saddr, sizeof (ip46_address_t));
759   clib_memcpy (&gkey.data, &gaddr, sizeof (ip46_address_t));
760
761   if (enable)
762     {
763       /* find configuration, if it dosn't exist, create new */
764       config = igmp_config_lookup (im, sw_if_index);
765       if (!config)
766         {
767           pool_get (im->configs, config);
768           memset (config, 0, sizeof (igmp_config_t));
769           config->sw_if_index = sw_if_index;
770           config->igmp_group_by_key =
771             hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
772           config->cli_api_configured = cli_api_configured;
773           /* use IGMPv3 by default */
774           config->igmp_ver = IGMP_V3;
775           config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
776           config->flags |= IGMP_CONFIG_FLAG_QUERY_RESP_RECVED;
777
778           if (!cli_api_configured)
779             {
780               /* create qery timer */
781               igmp_create_int_timer (vlib_time_now (vm) + IGMP_QUERY_TIMER,
782                                      sw_if_index, igmp_send_query);
783             }
784           config->adj_index =
785             adj_mcast_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4,
786                                    config->sw_if_index);
787           hash_set_mem (im->igmp_config_by_sw_if_index,
788                         &config->sw_if_index, config - im->configs);
789         }
790       else if (config->cli_api_configured != cli_api_configured)
791         {
792           rv = -2;
793           goto error;
794         }
795       /* find igmp group, if it dosn't exist, create new */
796       group = igmp_group_lookup (config, &gkey);
797       if (!group)
798         {
799           pool_get (config->groups, group);
800           memset (group, 0, sizeof (igmp_group_t));
801           group->key = clib_mem_alloc (sizeof (igmp_key_t));
802           clib_memcpy (group->key, &gkey, sizeof (igmp_key_t));
803           clib_memcpy (&group->addr, &gaddr, sizeof (ip46_address_t));
804           group->igmp_src_by_key =
805             hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
806           group->n_srcs = 0;
807           group->type = gkey.group_type;
808           if (cli_api_configured)
809             {
810               /* create state-changed report timer with zero timeout */
811               igmp_create_group_timer (0, sw_if_index, group->key,
812                                        igmp_send_state_changed);
813             }
814
815           hash_set_mem (config->igmp_group_by_key, group->key,
816                         group - config->groups);
817         }
818       /* find source, if it dosn't exist, create new */
819       src = igmp_src_lookup (group, &skey);
820       if (!src)
821         {
822           pool_get (group->srcs, src);
823           memset (src, 0, sizeof (igmp_src_t));
824           group->n_srcs += 1;
825           src->key = clib_mem_alloc (sizeof (igmp_key_t));
826           clib_memcpy (src->key, &skey, sizeof (igmp_key_t));
827           clib_memcpy (&src->addr, &saddr, sizeof (ip46_address_t));
828           if (!cli_api_configured)
829             {
830               /* arm source timer (after expiration remove (S,G)) */
831               igmp_event (im, config, group, src);
832               src->exp_time = vlib_time_now (vm) + IGMP_SRC_TIMER;
833               igmp_create_src_timer (src->exp_time, config->sw_if_index,
834                                      group->key, src->key, igmp_src_exp);
835             }
836
837           hash_set_mem (group->igmp_src_by_key, src->key, src - group->srcs);
838         }
839       else
840         {
841           rv = -1;
842           goto error;
843         }
844     }
845   else
846     {
847       config = igmp_config_lookup (im, sw_if_index);
848       if (config)
849         {
850           gkey.group_type = IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
851           group = igmp_group_lookup (config, &gkey);
852           if (group)
853             {
854               src = igmp_src_lookup (group, &skey);
855               if (src)
856                 {
857                   /* add source to block_all_sources group */
858                   igmp_key_t new_gkey;
859                   igmp_group_t *new_group;
860
861                   clib_memcpy (&new_gkey, &gkey, sizeof (igmp_key_t));
862                   new_gkey.group_type =
863                     IGMP_MEMBERSHIP_GROUP_block_old_sources;
864                   new_group = igmp_group_lookup (config, &new_gkey);
865                   if (!new_group)
866                     {
867                       pool_get (config->groups, new_group);
868
869                       group = igmp_group_lookup (config, &gkey);
870
871                       memset (new_group, 0, sizeof (igmp_group_t));
872                       new_group->key = clib_mem_alloc (sizeof (igmp_key_t));
873                       clib_memcpy (new_group->key, &new_gkey,
874                                    sizeof (igmp_key_t));
875                       clib_memcpy (&new_group->addr, &group->addr,
876                                    sizeof (ip46_address_t));
877                       new_group->igmp_src_by_key =
878                         hash_create_mem (0, sizeof (igmp_key_t),
879                                          sizeof (uword));
880                       new_group->n_srcs = 0;
881                       new_group->type = new_gkey.group_type;
882                       hash_set_mem (config->igmp_group_by_key, new_group->key,
883                                     new_group - config->groups);
884                     }
885                   igmp_src_t *new_src;
886                   new_src = igmp_src_lookup (new_group, &skey);
887                   if (!new_src)
888                     {
889                       pool_get (new_group->srcs, new_src);
890                       memset (new_src, 0, sizeof (igmp_src_t));
891                       new_group->n_srcs += 1;
892                       new_src->key = clib_mem_alloc (sizeof (igmp_key_t));
893                       clib_memcpy (new_src->key, src->key,
894                                    sizeof (igmp_key_t));
895                       clib_memcpy (&new_src->addr, &src->addr,
896                                    sizeof (ip46_address_t));
897                       hash_set_mem (new_group->igmp_src_by_key, new_src->key,
898                                     new_src - new_group->srcs);
899                     }
900
901                   /* notify all registered api clients */
902                   if (!cli_api_configured)
903                     igmp_event (im, config, new_group, new_src);
904                   else
905                     igmp_create_group_timer (0, sw_if_index, new_group->key,
906                                              igmp_send_state_changed);
907                   /* remove source form mode_is_filter_include group */
908                   hash_unset_mem (group->igmp_src_by_key, src->key);
909                   clib_mem_free (src->key);
910                   pool_put (group->srcs, src);
911                   group->n_srcs -= 1;
912                   if (group->n_srcs <= 0)
913                     igmp_clear_group (config, group);
914                   if (pool_elts (config->groups) <= 0)
915                     igmp_clear_config (config);
916                 }
917               else
918                 {
919                   rv = -1;
920                   goto error;
921                 }
922             }
923           else
924             {
925               rv = -1;
926               goto error;
927             }
928         }
929       else
930         {
931           rv = -1;
932           goto error;
933         }
934     }
935
936 error:
937   return rv;
938 }
939
940 /** \brief igmp hardware interface link up down
941     @param vnm - vnet main
942     @param hw_if_index - interface hw_if_index
943     @param flags - hw interface flags
944
945     If an interface goes down, remove its (S,G)s.
946 */
947 static clib_error_t *
948 igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
949 {
950   igmp_main_t *im = &igmp_main;
951   igmp_config_t *config;
952   clib_error_t *error = NULL;
953   /* remove igmp from a down interface to prevent crashes... */
954   config =
955     igmp_config_lookup (im,
956                         vnet_get_hw_interface (vnm,
957                                                hw_if_index)->sw_if_index);
958   if (config)
959     {
960       if ((flags & VNET_HW_INTERFACE_FLAG_LINK_UP) == 0)
961         igmp_clear_config (config);
962     }
963   return error;
964 }
965
966 VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (igmp_hw_interface_link_up_down);
967
968 /** \brief igmp initialization
969     @param vm - vlib main
970
971     initialize igmp plugin. Initialize igmp_main, set mfib to allow igmp traffic.
972 */
973 static clib_error_t *
974 igmp_init (vlib_main_t * vm)
975 {
976   clib_error_t *error;
977   igmp_main_t *im = &igmp_main;
978   vlib_thread_main_t *tm = vlib_get_thread_main ();
979   int i;
980   if ((error = vlib_call_init_function (vm, ip4_lookup_init)))
981     return error;
982   im->igmp_config_by_sw_if_index =
983     hash_create_mem (0, sizeof (u32), sizeof (uword));
984   im->igmp_api_client_by_client_index =
985     hash_create_mem (0, sizeof (u32), sizeof (uword));
986   vec_validate_aligned (im->buffers, tm->n_vlib_mains - 1,
987                         CLIB_CACHE_LINE_BYTES);
988   ip4_register_protocol (IP_PROTOCOL_IGMP, igmp_input_node.index);
989   igmp_type_info_t *ti;
990   igmp_report_type_info_t *rti;
991 #define igmp_type(n,s)                          \
992 do {                                            \
993   vec_add2 (im->type_infos, ti, 1);             \
994   ti->type = n;                                 \
995   ti->name = (u8 *) #s;                         \
996 } while (0);
997 #define igmp_report_type(n,s)                           \
998 do {                                            \
999   vec_add2 (im->report_type_infos, rti, 1);             \
1000   rti->type = n;                                        \
1001   rti->name = (u8 *) #s;                                \
1002 } while (0);
1003 #include "igmp.def"
1004 #undef igmp_type
1005 #undef igmp_report_type
1006   for (i = 0; i < vec_len (im->type_infos); i++)
1007     {
1008       ti = im->type_infos + i;
1009       hash_set (im->type_info_by_type, ti->type, i);
1010     }
1011
1012   for (i = 0; i < vec_len (im->report_type_infos); i++)
1013     {
1014       rti = im->report_type_infos + i;
1015       hash_set (im->report_type_info_by_report_type, rti->type, i);
1016     }
1017
1018   /* General Query address */
1019   ip46_address_t addr0 = {
1020     .as_u64[0] = 0,
1021     .as_u64[1] = 0
1022   };
1023   addr0.ip4.as_u32 = clib_host_to_net_u32 (IGMP_GENERAL_QUERY_ADDRESS);
1024
1025   /* Report address */
1026   ip46_address_t addr1 = {
1027     .as_u64[0] = 0,
1028     .as_u64[1] = 0
1029   };
1030   addr1.ip4.as_u32 = clib_host_to_net_u32 (IGMP_MEMBERSHIP_REPORT_ADDRESS);
1031
1032   fib_route_path_t path = {
1033     .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
1034     .frp_addr = zero_addr,
1035     .frp_sw_if_index = 0xffffffff,
1036     .frp_fib_index = 0,
1037     .frp_weight = 0,
1038     .frp_flags = FIB_ROUTE_PATH_LOCAL,
1039   };
1040
1041   const mfib_prefix_t mpfx0 = {
1042     .fp_proto = FIB_PROTOCOL_IP4,
1043     .fp_len = 32,
1044     .fp_grp_addr = addr0,
1045   };
1046
1047   const mfib_prefix_t mpfx1 = {
1048     .fp_proto = FIB_PROTOCOL_IP4,
1049     .fp_len = 32,
1050     .fp_grp_addr = addr1,
1051   };
1052
1053   /* configure MFIB to accept IGMPv3 general query
1054    * and reports from all interfaces
1055    */
1056   mfib_table_entry_path_update (0, &mpfx0,
1057                                 MFIB_SOURCE_DEFAULT_ROUTE, &path,
1058                                 MFIB_ITF_FLAG_FORWARD);
1059   mfib_table_entry_path_update (0, &mpfx1,
1060                                 MFIB_SOURCE_DEFAULT_ROUTE, &path,
1061                                 MFIB_ITF_FLAG_FORWARD);
1062   mfib_table_entry_update (0, &mpfx0, MFIB_SOURCE_DEFAULT_ROUTE,
1063                            0, MFIB_ENTRY_FLAG_ACCEPT_ALL_ITF);
1064   mfib_table_entry_update (0, &mpfx1, MFIB_SOURCE_DEFAULT_ROUTE,
1065                            0, MFIB_ENTRY_FLAG_ACCEPT_ALL_ITF);
1066   return (error);
1067 }
1068
1069 VLIB_INIT_FUNCTION (igmp_init);
1070 /* *INDENT-OFF* */
1071 VLIB_PLUGIN_REGISTER () = {
1072     .version = VPP_BUILD_VER,
1073     .description = "IGMP messaging",
1074 };
1075 /* *INDENT-ON* */
1076
1077 /*
1078  * fd.io coding-style-patch-verification: ON
1079  *
1080  * Local Variables:
1081  * eval: (c-set-style "gnu")
1082  * End:
1083  */