IGMP improvements
[vpp.git] / src / plugins / igmp / igmp_query.c
1 /*
2  *------------------------------------------------------------------
3  * Copyright (c) 2018 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 <igmp/igmp_query.h>
19 #include <igmp/igmp_pkt.h>
20
21 static f64
22 igmp_get_random_resp_delay (const igmp_header_t * header)
23 {
24   u32 seed;
25
26   seed = vlib_time_now (vlib_get_main ());
27
28   return ((random_f64 (&seed) * igmp_header_get_max_resp_time (header)));
29
30 }
31
32 static ip46_address_t *
33 igmp_query_mk_source_list (const igmp_membership_query_v3_t * q)
34 {
35   ip46_address_t *srcs = NULL;
36   const ip4_address_t *s;
37   u16 ii, n;
38
39   n = clib_net_to_host_u16 (q->n_src_addresses);
40
41   if (0 == n)
42     return (NULL);
43
44   vec_validate (srcs, n - 1);
45   s = q->src_addresses;
46
47   for (ii = 0; ii < n; ii++)
48     {
49       srcs[ii].ip4 = *s;
50       s++;
51     }
52
53   return (srcs);
54 }
55
56 static void
57 igmp_send_group_report_v3 (u32 obj, void *data)
58 {
59   igmp_pkt_build_report_t br;
60   igmp_config_t *config;
61   ip46_address_t *srcs;
62   igmp_group_t *group;
63   igmp_main_t *im;
64
65   im = &igmp_main;
66   srcs = data;
67   group = pool_elt_at_index (im->groups, obj);
68   config = pool_elt_at_index (im->configs, group->config);
69
70   igmp_pkt_build_report_init (&br, config->sw_if_index);
71   ASSERT (group->timers[IGMP_GROUP_TIMER_QUERY_REPLY] !=
72           IGMP_TIMER_ID_INVALID);
73
74   IGMP_DBG ("send-group-report: %U",
75             format_vnet_sw_if_index_name,
76             vnet_get_main (), config->sw_if_index);
77
78   if (NULL == srcs)
79     {
80       /*
81        * there were no sources specified, so this is a group-sepcific query.
82        * We should respond with all our sources
83        */
84       igmp_pkt_report_v3_add_group (&br, group,
85                                     IGMP_MEMBERSHIP_GROUP_mode_is_include);
86     }
87   else
88     {
89       /*
90        * the sources stored in the timer object are the combined set of sources
91        * to be quired. We need to respond only to those queried, not our full set.
92        */
93       ip46_address_t *intersect;
94
95       intersect = igmp_group_new_intersect_present (group,
96                                                     IGMP_FILTER_MODE_INCLUDE,
97                                                     srcs);
98
99       if (vec_len (intersect))
100         {
101           igmp_pkt_report_v3_add_report (&br,
102                                          group->key,
103                                          intersect,
104                                          IGMP_MEMBERSHIP_GROUP_mode_is_include);
105           vec_free (intersect);
106         }
107     }
108
109   igmp_pkt_report_v3_send (&br);
110
111   igmp_timer_retire (&group->timers[IGMP_GROUP_TIMER_QUERY_REPLY]);
112   vec_free (srcs);
113 }
114
115 static igmp_membership_group_v3_type_t
116 igmp_filter_mode_to_report_type (igmp_filter_mode_t mode)
117 {
118   switch (mode)
119     {
120     case IGMP_FILTER_MODE_INCLUDE:
121       return (IGMP_MEMBERSHIP_GROUP_mode_is_include);
122     case IGMP_FILTER_MODE_EXCLUDE:
123       return (IGMP_MEMBERSHIP_GROUP_mode_is_exclude);
124     }
125
126   return (IGMP_MEMBERSHIP_GROUP_mode_is_include);
127 }
128
129 /**
130  * Send igmp membership general report.
131  */
132 static void
133 igmp_send_general_report_v3 (u32 obj, void *data)
134 {
135   igmp_pkt_build_report_t br;
136   igmp_config_t *config;
137   igmp_group_t *group;
138   igmp_main_t *im;
139
140   im = &igmp_main;
141   config = pool_elt_at_index (im->configs, obj);
142
143   ASSERT (config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT] !=
144           IGMP_TIMER_ID_INVALID);
145
146   igmp_timer_retire (&config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT]);
147
148   IGMP_DBG ("send-general-report: %U",
149             format_vnet_sw_if_index_name,
150             vnet_get_main (), config->sw_if_index);
151
152   igmp_pkt_build_report_init (&br, config->sw_if_index);
153
154   /* *INDENT-OFF* */
155   FOR_EACH_GROUP (group, config,
156     ({
157       igmp_pkt_report_v3_add_group
158         (&br, group,
159          igmp_filter_mode_to_report_type(group->router_filter_mode));
160     }));
161   /* *INDENT-ON* */
162
163   igmp_pkt_report_v3_send (&br);
164 }
165
166 /**
167  * Called from the main thread on reception of a Query message
168  */
169 void
170 igmp_handle_query (const igmp_query_args_t * args)
171 {
172   igmp_config_t *config;
173
174   config = igmp_config_lookup (args->sw_if_index);
175
176   if (!config)
177     /*
178      * no IGMP config on the interface. quit
179      */
180     return;
181
182   if (IGMP_MODE_ROUTER == config->mode)
183     {
184       ASSERT (0);
185       // code here for querier election */
186     }
187
188   IGMP_DBG ("query-rx: %U", format_vnet_sw_if_index_name,
189             vnet_get_main (), args->sw_if_index);
190
191
192   /*
193      Section 5.2
194      "When a system receives a Query, it does not respond immediately.
195      Instead, it delays its response by a random amount of time, bounded
196      by the Max Resp Time value derived from the Max Resp Code in the
197      received Query message.  A system may receive a variety of Queries on
198      different interfaces and of different kinds (e.g., General Queries,
199      Group-Specific Queries, and Group-and-Source-Specific Queries), each
200      of which may require its own delayed response.
201    */
202   if (igmp_membership_query_v3_is_geeral (args->query))
203     {
204       IGMP_DBG ("...general-query-rx: %U", format_vnet_sw_if_index_name,
205                 vnet_get_main (), args->sw_if_index);
206
207       /*
208        * A general query has no info that needs saving from the response
209        */
210       if (IGMP_TIMER_ID_INVALID ==
211           config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT])
212         {
213           f64 delay = igmp_get_random_resp_delay (&args->query[0].header);
214
215           IGMP_DBG ("...general-query-rx: %U schedule for %f",
216                     format_vnet_sw_if_index_name, vnet_get_main (),
217                     args->sw_if_index, delay);
218
219           /*
220            * no currently running timer, schedule a new one
221            */
222           config->timers[IGMP_CONFIG_TIMER_GENERAL_REPORT] =
223             igmp_timer_schedule (delay,
224                                  igmp_config_index (config),
225                                  igmp_send_general_report_v3, NULL);
226         }
227       /*
228        * else
229        *  don't reschedule timers, we'll reply soon enough..
230        */
231     }
232   else
233     {
234       /*
235        * G or SG query. we'll need to save the sources quered
236        */
237       igmp_key_t key = {
238         .ip4 = args->query[0].group_address,
239       };
240       ip46_address_t *srcs;
241       igmp_timer_id_t tid;
242       igmp_group_t *group;
243
244       group = igmp_group_lookup (config, &key);
245
246       /*
247        * If there is no group config, no worries, we can ignore this
248        * query. If the group state does come soon, we'll send a
249        * state-change report at that time.
250        */
251       if (!group)
252         return;
253
254       srcs = igmp_query_mk_source_list (args->query);
255       tid = group->timers[IGMP_GROUP_TIMER_QUERY_REPLY];
256
257       IGMP_DBG ("...group-query-rx: %U for (%U, %U)",
258                 format_vnet_sw_if_index_name,
259                 vnet_get_main (), args->sw_if_index,
260                 format_igmp_src_addr_list, srcs, format_igmp_key, &key);
261
262
263       if (IGMP_TIMER_ID_INVALID != tid)
264         {
265           /*
266            * There is a timer already running, merge the sources list
267            */
268           ip46_address_t *current, *s;
269
270           current = igmp_timer_get_data (tid);
271
272           vec_foreach (s, srcs)
273           {
274             if (~0 == vec_search_with_function (current, s,
275                                                 ip46_address_is_equal))
276               {
277                 vec_add1 (current, *s);
278               }
279           }
280
281           igmp_timer_set_data (tid, current);
282         }
283       else
284         {
285           /*
286            * schedule a new G-specific query
287            */
288           f64 delay = igmp_get_random_resp_delay (&args->query[0].header);
289
290           IGMP_DBG ("...group-query-rx: schedule:%f", delay);
291
292           group->timers[IGMP_GROUP_TIMER_QUERY_REPLY] =
293             igmp_timer_schedule (delay,
294                                  igmp_group_index (group),
295                                  igmp_send_group_report_v3, srcs);
296         }
297     }
298 }
299
300
301 /*
302  * fd.io coding-style-patch-verification: ON
303  *
304  * Local Variables:
305  * eval: (c-set-style "gnu")
306  * End:
307  */