igmp: bugfix and minor improvements
[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 (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 *to_next = 0;
406   u32 next_index = ip4_rewrite_node.index;
407
408   u32 bi = 0;
409   vlib_buffer_alloc (vm, &bi, 1);
410
411   vlib_buffer_t *b = vlib_get_buffer (vm, bi);
412   vlib_buffer_free_list_t *fl = vlib_buffer_get_free_list (vm,
413                                                            VLIB_BUFFER_DEFAULT_FREE_LIST_INDEX);
414   vlib_buffer_init_for_free_list (b, fl);
415
416   b->current_data = 0;
417   b->current_length = 0;
418
419   igmp_create_ip4 (b, config, group, is_report);
420
421   b->current_data = 0;
422
423   b->total_length_not_including_first_buffer = 0;
424   b->flags = VLIB_BUFFER_TOTAL_LENGTH_VALID;
425   vnet_buffer (b)->sw_if_index[VLIB_RX] = (u32) ~ 0;
426   vnet_buffer (b)->ip.adj_index[VLIB_TX] = config->adj_index;
427
428   b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED;
429
430
431   vlib_frame_t *f = vlib_get_frame_to_node (vm, next_index);
432   to_next = vlib_frame_vector_args (f);
433   to_next[0] = bi;
434
435   f->n_vectors = 1;
436
437   vlib_buffer_t *c = vlib_buffer_copy (vm, b);
438   to_next += 1;
439   to_next[0] = vlib_get_buffer_index (vm, c);
440
441   vlib_put_frame_to_node (vm, next_index, f);
442 }
443
444 void
445 igmp_send_query (vlib_main_t * vm, vlib_node_runtime_t * rt, igmp_main_t * im,
446                  igmp_timer_t * timer)
447 {
448   igmp_config_t *config;
449 /* TODO: group-specific query: pass group key in timer */
450   igmp_group_t *group = NULL;
451
452   u32 sw_if_index = timer->sw_if_index;
453
454   pool_put (im->timers, timer);
455
456   config = igmp_config_lookup (im, sw_if_index);
457   if (!config)
458     return;
459
460   /* TODO: implement IGMPv2 */
461   config->next_create_msg = igmp_create_query_v3;
462   igmp_send_msg (vm, rt, im, config, group, /* is_report */ 0);
463
464   /* in case of group query we don't want to set up another qery timer */
465   if (PREDICT_TRUE (!group))
466     igmp_create_int_timer (vlib_time_now (vm) + IGMP_QUERY_TIMER, sw_if_index,
467                            igmp_send_query);
468 }
469
470 void
471 igmp_query_resp_exp (vlib_main_t * vm, vlib_node_runtime_t * rt,
472                      igmp_main_t * im, igmp_timer_t * timer)
473 {
474   igmp_config_t *config;
475 /* TODO: group-specific query: pass group key in timer */
476   igmp_group_t *group = NULL;
477
478   u32 sw_if_index = timer->sw_if_index;
479
480   pool_put (im->timers, timer);
481
482   config = igmp_config_lookup (im, sw_if_index);
483   if (!config)
484     return;
485
486   /* if group != NULL this is a group-specific qeury timer */
487   if (PREDICT_FALSE (group != NULL))
488     {
489       if ((group->flags & IGMP_GROUP_FLAG_QUERY_RESP_RECVED) == 0)
490         {
491           igmp_clear_group (config, group);
492           return;
493         }
494     }
495   /* if report not received in max resp time clear igmp on interface */
496   if ((config->flags & IGMP_CONFIG_FLAG_QUERY_RESP_RECVED) == 0)
497     {
498       igmp_clear_config (config);
499     }
500 }
501
502 void
503 igmp_send_report (vlib_main_t * vm, vlib_node_runtime_t * rt,
504                   igmp_main_t * im, igmp_timer_t * timer)
505 {
506   igmp_config_t *config;
507
508   u32 sw_if_index = timer->sw_if_index;
509
510   pool_put (im->timers, timer);
511
512   config = igmp_config_lookup (im, sw_if_index);
513   if (!config)
514     return;
515
516   if (config->flags & IGMP_CONFIG_FLAG_CAN_SEND_REPORT)
517     {
518       /* TODO: implement IGMPv2 and IGMPv1 */
519       config->next_create_msg = igmp_create_report_v3;
520       /* pass NULL as group to send all groups at once */
521       igmp_send_msg (vm, rt, im, config, NULL, /* is_report */ 1);
522       /* WIP: unset flag after all reports sent */
523       config->flags &= ~IGMP_CONFIG_FLAG_CAN_SEND_REPORT;
524     }
525 }
526
527 void
528 igmp_send_state_changed (vlib_main_t * vm, vlib_node_runtime_t * rt,
529                          igmp_main_t * im, igmp_timer_t * timer)
530 {
531   igmp_config_t *config;
532   igmp_group_t *group;
533   igmp_src_t *src;
534   igmp_key_t gkey;
535
536   u32 sw_if_index = timer->sw_if_index;
537   IGMP_DBG ("sw_if_index %d", sw_if_index);
538
539   ASSERT (timer->data);
540   clib_memcpy (&gkey, timer->data, sizeof (igmp_key_t));
541
542   pool_put (im->timers, timer);
543
544   config = igmp_config_lookup (im, sw_if_index);
545   if (!config)
546     return;
547
548   group = igmp_group_lookup (config, &gkey);
549   if (!group)
550     return;
551
552   config->next_create_msg = igmp_create_report_v3;
553   igmp_send_msg (vm, rt, im, config, group, /* is_report */ 1);
554
555   IGMP_DBG ("group_type %u", group->type);
556
557   if (group->type == IGMP_MEMBERSHIP_GROUP_change_to_filter_include)
558     {
559       igmp_key_t new_gkey;
560       igmp_group_t *new_group;
561       igmp_src_t *new_src;
562
563       clib_memcpy (&new_gkey.data, &group->addr, sizeof (ip46_address_t));
564       new_gkey.group_type = IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
565
566       new_group = igmp_group_lookup (config, &new_gkey);
567       if (!new_group)
568         {
569           IGMP_DBG ("creating new group...");
570           pool_get (config->groups, new_group);
571           /* get valid pointer to old group */
572           group = igmp_group_lookup (config, &gkey);
573
574           memset (new_group, 0, sizeof (igmp_group_t));
575
576           clib_memcpy (&new_group->addr, &group->addr,
577                        sizeof (ip46_address_t));
578           new_group->n_srcs = 0;
579           new_group->type = new_gkey.group_type;
580
581           new_group->key = clib_mem_alloc (sizeof (igmp_key_t));
582           clib_memcpy (new_group->key, &new_gkey, sizeof (igmp_key_t));
583           new_group->igmp_src_by_key =
584             hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
585           hash_set_mem (config->igmp_group_by_key, new_group->key,
586                         new_group - config->groups);
587         }
588       /* *INDENT-OFF* */
589       /* loop through old group sources */
590       pool_foreach (src, group->srcs, (
591         {
592           /* add sources to new group */
593           new_src = igmp_src_lookup (new_group, src->key);
594           if (!new_src)
595             {
596               pool_get (new_group->srcs, new_src);
597               memset (new_src, 0, sizeof (igmp_src_t));
598               new_group->n_srcs += 1;
599               new_src->key = clib_mem_alloc (sizeof (igmp_key_t));
600               clib_memcpy (new_src->key, src->key, sizeof (igmp_key_t));
601               clib_memcpy (&new_src->addr, &src->addr,
602                 sizeof (ip46_address_t));
603
604               hash_set_mem (new_group->igmp_src_by_key, new_src->key,
605                     new_src - new_group->srcs);
606             }
607         }));
608       /* *INDENT-ON* */
609     }
610
611   /* remove group */
612   IGMP_DBG ("remove group");
613   igmp_clear_group (config, group);
614   if (pool_elts (config->groups) == 0)
615     {
616       hash_unset (im->igmp_config_by_sw_if_index, config->sw_if_index);
617       pool_put (im->configs, config);
618     }
619 }
620
621 void
622 igmp_src_exp (vlib_main_t * vm, vlib_node_runtime_t * rt,
623               igmp_main_t * im, igmp_timer_t * timer)
624 {
625   igmp_config_t *config;
626   igmp_group_t *group;
627   igmp_src_t *src;
628
629   ASSERT (timer->data);
630
631   igmp_key_t *gkey = (igmp_key_t *) & ((igmp_key_t *) timer->data)[0];
632   igmp_key_t *skey = (igmp_key_t *) & ((igmp_key_t *) timer->data)[1];
633
634   config = igmp_config_lookup (im, timer->sw_if_index);
635   if (!config)
636     goto done;
637   group = igmp_group_lookup (config, gkey);
638   if (!group)
639     goto done;
640   src = igmp_src_lookup (group, skey);
641   if (!src)
642     goto done;
643   /* check if this timer is valid */
644   if (timer->exp_time != src->exp_time)
645     {
646       timer->exp_time = src->exp_time;
647       igmp_sort_timers (im->timers);
648       return;
649     }
650
651   ip46_address_t saddr;
652   ip46_address_t gaddr;
653   clib_memcpy (&saddr, skey->data, sizeof (ip46_address_t));
654   clib_memcpy (&gaddr, gkey->data, sizeof (ip46_address_t));
655
656   /* source timer expired, remove src */
657   igmp_listen (vm, 0, timer->sw_if_index, saddr, gaddr, 0);
658 done:
659   clib_mem_free (timer->data);
660   pool_put (im->timers, timer);
661 }
662
663 /** \brief igmp timer process
664     @param vm - vlib main
665     @param rt - vlib runtime node
666     @param f - vlib frame
667
668     Handle igmp timers.
669 */
670 static uword
671 igmp_timer_process (vlib_main_t * vm, vlib_node_runtime_t * rt,
672                     vlib_frame_t * f)
673 {
674   igmp_main_t *im = &igmp_main;
675   uword *event_data = 0, event_type;
676   f64 time_start;
677   igmp_timer_t *timer = NULL;
678   while (1)
679     {
680       /* suspend util timer expires */
681       if (NULL != timer)
682         vlib_process_wait_for_event_or_clock (vm,
683                                               timer->exp_time - time_start);
684       else
685         vlib_process_wait_for_event (vm);
686       time_start = vlib_time_now (vm);
687       event_type = vlib_process_get_events (vm, &event_data);
688       vec_reset_length (event_data);
689       if (event_type == IGMP_PROCESS_EVENT_UPDATE_TIMER)
690         goto next_timer;
691       IGMP_DBG ("time: %f", vlib_time_now (vm));
692       /* timer expired */
693       if (NULL != timer && timer->func != NULL)
694         timer->func (vm, rt, im, timer);
695     next_timer:
696       timer = igmp_get_next_timer (im);
697     }
698   return 0;
699 }
700
701 /* *INDENT-OFF* */
702 VLIB_REGISTER_NODE (igmp_timer_process_node) =
703 {
704   .function = igmp_timer_process,
705   .type = VLIB_NODE_TYPE_PROCESS,
706   .name = "igmp-timer-process",
707   .n_next_nodes = IGMP_N_NEXT,
708   .next_nodes =  {
709     [IGMP_NEXT_IP4_REWRITE_MCAST_NODE] = "ip4-rewrite-mcast",
710     [IGMP_NEXT_IP6_REWRITE_MCAST_NODE] = "ip6-rewrite-mcast",
711   }
712 };
713 /* *INDENT-ON* */
714
715 int
716 igmp_listen (vlib_main_t * vm, u8 enable, u32 sw_if_index,
717              ip46_address_t saddr, ip46_address_t gaddr, u8 flags)
718 {
719   igmp_main_t *im = &igmp_main;
720   igmp_config_t *config;
721   igmp_group_t *group;
722   igmp_src_t *src;
723   igmp_key_t skey;
724   igmp_key_t gkey;
725
726   igmp_membership_group_v3_type_t group_type =
727     (flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) ?
728     IGMP_MEMBERSHIP_GROUP_change_to_filter_include :
729     IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
730   int rv = 0;
731
732   /* set the lookup keys */
733   skey.group_type = 0;
734   gkey.group_type = group_type;
735   clib_memcpy (&skey.data, &saddr, sizeof (ip46_address_t));
736   clib_memcpy (&gkey.data, &gaddr, sizeof (ip46_address_t));
737
738   if (enable)
739     {
740       /* find configuration, if it dosn't exist, create new */
741       config = igmp_config_lookup (im, sw_if_index);
742       if (!config)
743         {
744           pool_get (im->configs, config);
745           memset (config, 0, sizeof (igmp_config_t));
746           config->sw_if_index = sw_if_index;
747           config->igmp_group_by_key =
748             hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
749           /* use IGMPv3 by default */
750           config->igmp_ver = IGMP_V3;
751           config->robustness_var = IGMP_DEFAULT_ROBUSTNESS_VARIABLE;
752           config->flags |= IGMP_CONFIG_FLAG_QUERY_RESP_RECVED | flags;
753
754           if ((flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) == 0)
755             {
756               /* create qery timer */
757               igmp_create_int_timer (vlib_time_now (vm) + IGMP_QUERY_TIMER,
758                                      sw_if_index, igmp_send_query);
759             }
760           config->adj_index =
761             adj_mcast_add_or_lock (FIB_PROTOCOL_IP4, VNET_LINK_IP4,
762                                    config->sw_if_index);
763           hash_set (im->igmp_config_by_sw_if_index,
764                     config->sw_if_index, config - im->configs);
765         }
766       else if ((config->flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED & flags)
767                == 0)
768         {
769           rv = -2;
770           goto error;
771         }
772       /* find igmp group, if it dosn't exist, create new */
773       group = igmp_group_lookup (config, &gkey);
774       if (!group)
775         {
776           pool_get (config->groups, group);
777           memset (group, 0, sizeof (igmp_group_t));
778           group->key = clib_mem_alloc (sizeof (igmp_key_t));
779           clib_memcpy (group->key, &gkey, sizeof (igmp_key_t));
780           clib_memcpy (&group->addr, &gaddr, sizeof (ip46_address_t));
781           group->igmp_src_by_key =
782             hash_create_mem (0, sizeof (igmp_key_t), sizeof (uword));
783           group->n_srcs = 0;
784           group->type = gkey.group_type;
785           if (flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED)
786             {
787               /* create state-changed report timer with zero timeout */
788               igmp_create_group_timer (0, sw_if_index, group->key,
789                                        igmp_send_state_changed);
790             }
791
792           hash_set_mem (config->igmp_group_by_key, group->key,
793                         group - config->groups);
794         }
795       /* find source, if it dosn't exist, create new */
796       src = igmp_src_lookup (group, &skey);
797       if (!src)
798         {
799           pool_get (group->srcs, src);
800           memset (src, 0, sizeof (igmp_src_t));
801           group->n_srcs += 1;
802           src->key = clib_mem_alloc (sizeof (igmp_key_t));
803           clib_memcpy (src->key, &skey, sizeof (igmp_key_t));
804           clib_memcpy (&src->addr, &saddr, sizeof (ip46_address_t));
805           if ((flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) == 0)
806             {
807               /* arm source timer (after expiration remove (S,G)) */
808               igmp_event (im, config, group, src);
809               src->exp_time = vlib_time_now (vm) + IGMP_SRC_TIMER;
810               igmp_create_src_timer (src->exp_time, config->sw_if_index,
811                                      group->key, src->key, igmp_src_exp);
812             }
813
814           hash_set_mem (group->igmp_src_by_key, src->key, src - group->srcs);
815         }
816       else
817         {
818           rv = -1;
819           goto error;
820         }
821     }
822   else
823     {
824       config = igmp_config_lookup (im, sw_if_index);
825       if (config)
826         {
827           gkey.group_type = IGMP_MEMBERSHIP_GROUP_mode_is_filter_include;
828           group = igmp_group_lookup (config, &gkey);
829           if (group)
830             {
831               src = igmp_src_lookup (group, &skey);
832               if (src)
833                 {
834                   /* add source to block_all_sources group */
835                   igmp_key_t new_gkey;
836                   igmp_group_t *new_group;
837
838                   clib_memcpy (&new_gkey, &gkey, sizeof (igmp_key_t));
839                   new_gkey.group_type =
840                     IGMP_MEMBERSHIP_GROUP_block_old_sources;
841                   new_group = igmp_group_lookup (config, &new_gkey);
842                   if (!new_group)
843                     {
844                       pool_get (config->groups, new_group);
845
846                       group = igmp_group_lookup (config, &gkey);
847
848                       memset (new_group, 0, sizeof (igmp_group_t));
849                       new_group->key = clib_mem_alloc (sizeof (igmp_key_t));
850                       clib_memcpy (new_group->key, &new_gkey,
851                                    sizeof (igmp_key_t));
852                       clib_memcpy (&new_group->addr, &group->addr,
853                                    sizeof (ip46_address_t));
854                       new_group->igmp_src_by_key =
855                         hash_create_mem (0, sizeof (igmp_key_t),
856                                          sizeof (uword));
857                       new_group->n_srcs = 0;
858                       new_group->type = new_gkey.group_type;
859                       hash_set_mem (config->igmp_group_by_key, new_group->key,
860                                     new_group - config->groups);
861                     }
862                   igmp_src_t *new_src;
863                   new_src = igmp_src_lookup (new_group, &skey);
864                   if (!new_src)
865                     {
866                       pool_get (new_group->srcs, new_src);
867                       memset (new_src, 0, sizeof (igmp_src_t));
868                       new_group->n_srcs += 1;
869                       new_src->key = clib_mem_alloc (sizeof (igmp_key_t));
870                       clib_memcpy (new_src->key, src->key,
871                                    sizeof (igmp_key_t));
872                       clib_memcpy (&new_src->addr, &src->addr,
873                                    sizeof (ip46_address_t));
874                       hash_set_mem (new_group->igmp_src_by_key, new_src->key,
875                                     new_src - new_group->srcs);
876                     }
877
878                   /* notify all registered api clients */
879                   if ((flags & IGMP_CONFIG_FLAG_CLI_API_CONFIGURED) == 0)
880                     igmp_event (im, config, new_group, new_src);
881                   else
882                     igmp_create_group_timer (0, sw_if_index, new_group->key,
883                                              igmp_send_state_changed);
884                   /* remove source form mode_is_filter_include group */
885                   hash_unset_mem (group->igmp_src_by_key, src->key);
886                   clib_mem_free (src->key);
887                   pool_put (group->srcs, src);
888                   group->n_srcs -= 1;
889                   if (group->n_srcs <= 0)
890                     igmp_clear_group (config, group);
891                   if (pool_elts (config->groups) <= 0)
892                     igmp_clear_config (config);
893                 }
894               else
895                 {
896                   rv = -1;
897                   goto error;
898                 }
899             }
900           else
901             {
902               rv = -1;
903               goto error;
904             }
905         }
906       else
907         {
908           rv = -1;
909           goto error;
910         }
911     }
912
913 error:
914   return rv;
915 }
916
917 /** \brief igmp hardware interface link up down
918     @param vnm - vnet main
919     @param hw_if_index - interface hw_if_index
920     @param flags - hw interface flags
921
922     If an interface goes down, remove its (S,G)s.
923 */
924 static clib_error_t *
925 igmp_hw_interface_link_up_down (vnet_main_t * vnm, u32 hw_if_index, u32 flags)
926 {
927   igmp_main_t *im = &igmp_main;
928   igmp_config_t *config;
929   clib_error_t *error = NULL;
930   /* remove igmp from a down interface to prevent crashes... */
931   config =
932     igmp_config_lookup (im,
933                         vnet_get_hw_interface (vnm,
934                                                hw_if_index)->sw_if_index);
935   if (config)
936     {
937       if ((flags & VNET_HW_INTERFACE_FLAG_LINK_UP) == 0)
938         igmp_clear_config (config);
939     }
940   return error;
941 }
942
943 VNET_HW_INTERFACE_LINK_UP_DOWN_FUNCTION (igmp_hw_interface_link_up_down);
944
945 /** \brief igmp initialization
946     @param vm - vlib main
947
948     initialize igmp plugin. Initialize igmp_main, set mfib to allow igmp traffic.
949 */
950 static clib_error_t *
951 igmp_init (vlib_main_t * vm)
952 {
953   clib_error_t *error;
954   igmp_main_t *im = &igmp_main;
955   int i;
956   if ((error = vlib_call_init_function (vm, ip4_lookup_init)))
957     return error;
958   im->igmp_config_by_sw_if_index = hash_create (0, sizeof (u32));
959   im->igmp_api_client_by_client_index = hash_create (0, sizeof (u32));
960   ip4_register_protocol (IP_PROTOCOL_IGMP, igmp_input_node.index);
961   igmp_type_info_t *ti;
962   igmp_report_type_info_t *rti;
963 #define igmp_type(n,s)                          \
964 do {                                            \
965   vec_add2 (im->type_infos, ti, 1);             \
966   ti->type = n;                                 \
967   ti->name = (u8 *) #s;                         \
968 } while (0);
969 #define igmp_report_type(n,s)                           \
970 do {                                            \
971   vec_add2 (im->report_type_infos, rti, 1);             \
972   rti->type = n;                                        \
973   rti->name = (u8 *) #s;                                \
974 } while (0);
975 #include "igmp.def"
976 #undef igmp_type
977 #undef igmp_report_type
978   for (i = 0; i < vec_len (im->type_infos); i++)
979     {
980       ti = im->type_infos + i;
981       hash_set (im->type_info_by_type, ti->type, i);
982     }
983
984   for (i = 0; i < vec_len (im->report_type_infos); i++)
985     {
986       rti = im->report_type_infos + i;
987       hash_set (im->report_type_info_by_report_type, rti->type, i);
988     }
989
990   /* General Query address */
991   ip46_address_t addr0 = {
992     .as_u64[0] = 0,
993     .as_u64[1] = 0
994   };
995   addr0.ip4.as_u32 = clib_host_to_net_u32 (IGMP_GENERAL_QUERY_ADDRESS);
996
997   /* Report address */
998   ip46_address_t addr1 = {
999     .as_u64[0] = 0,
1000     .as_u64[1] = 0
1001   };
1002   addr1.ip4.as_u32 = clib_host_to_net_u32 (IGMP_MEMBERSHIP_REPORT_ADDRESS);
1003
1004   fib_route_path_t path = {
1005     .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
1006     .frp_addr = zero_addr,
1007     .frp_sw_if_index = 0xffffffff,
1008     .frp_fib_index = 0,
1009     .frp_weight = 0,
1010     .frp_flags = FIB_ROUTE_PATH_LOCAL,
1011   };
1012
1013   const mfib_prefix_t mpfx0 = {
1014     .fp_proto = FIB_PROTOCOL_IP4,
1015     .fp_len = 32,
1016     .fp_grp_addr = addr0,
1017   };
1018
1019   const mfib_prefix_t mpfx1 = {
1020     .fp_proto = FIB_PROTOCOL_IP4,
1021     .fp_len = 32,
1022     .fp_grp_addr = addr1,
1023   };
1024
1025   /* configure MFIB to accept IGMPv3 general query
1026    * and reports from all interfaces
1027    */
1028   mfib_table_entry_path_update (0, &mpfx0,
1029                                 MFIB_SOURCE_DEFAULT_ROUTE, &path,
1030                                 MFIB_ITF_FLAG_FORWARD);
1031   mfib_table_entry_path_update (0, &mpfx1,
1032                                 MFIB_SOURCE_DEFAULT_ROUTE, &path,
1033                                 MFIB_ITF_FLAG_FORWARD);
1034   mfib_table_entry_update (0, &mpfx0, MFIB_SOURCE_DEFAULT_ROUTE,
1035                            0, MFIB_ENTRY_FLAG_ACCEPT_ALL_ITF);
1036   mfib_table_entry_update (0, &mpfx1, MFIB_SOURCE_DEFAULT_ROUTE,
1037                            0, MFIB_ENTRY_FLAG_ACCEPT_ALL_ITF);
1038   return (error);
1039 }
1040
1041 VLIB_INIT_FUNCTION (igmp_init);
1042 /* *INDENT-OFF* */
1043 VLIB_PLUGIN_REGISTER () = {
1044     .version = VPP_BUILD_VER,
1045     .description = "IGMP messaging",
1046 };
1047 /* *INDENT-ON* */
1048
1049 /*
1050  * fd.io coding-style-patch-verification: ON
1051  *
1052  * Local Variables:
1053  * eval: (c-set-style "gnu")
1054  * End:
1055  */