IGMP: proxy device
[vpp.git] / src / plugins / igmp / igmp_proxy.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 <vlib/vlib.h>
19 #include <vnet/mfib/mfib_entry.h>
20 #include <vnet/mfib/mfib_table.h>
21
22 #include <igmp/igmp_proxy.h>
23 #include <igmp/igmp.h>
24 #include <igmp/igmp_pkt.h>
25
26 void
27 igmp_proxy_device_mfib_path_add_del (igmp_group_t * group, u8 add)
28 {
29   igmp_config_t *config;
30   u32 mfib_index;
31
32   config = igmp_config_get (group->config);
33   mfib_index =
34     mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4,
35                                           config->sw_if_index);
36
37   /* *INDENT-OFF* */
38   mfib_prefix_t mpfx_group_addr = {
39       .fp_proto = FIB_PROTOCOL_IP4,
40       .fp_len = 32,
41       .fp_grp_addr = {
42         .ip4 = (*group->key).ip4,
43       },
44     };
45   fib_route_path_t via_itf_path =
46     {
47       .frp_proto = fib_proto_to_dpo (FIB_PROTOCOL_IP4),
48       .frp_addr = zero_addr,
49       .frp_sw_if_index = config->sw_if_index,
50       .frp_fib_index = 0,
51       .frp_weight = 1,
52     };
53   /* *INDENT-ON* */
54
55   if (add)
56     mfib_table_entry_path_update (mfib_index, &mpfx_group_addr,
57                                   MFIB_SOURCE_IGMP, &via_itf_path,
58                                   MFIB_ITF_FLAG_FORWARD);
59   else
60     mfib_table_entry_path_remove (mfib_index, &mpfx_group_addr,
61                                   MFIB_SOURCE_IGMP, &via_itf_path);
62 }
63
64 igmp_proxy_device_t *
65 igmp_proxy_device_lookup (u32 vrf_id)
66 {
67   igmp_main_t *im = &igmp_main;
68
69   if (vec_len (im->igmp_proxy_device_by_vrf_id) > vrf_id)
70     {
71       u32 index;
72       index = im->igmp_proxy_device_by_vrf_id[vrf_id];
73       if (index != ~0)
74         return (vec_elt_at_index (im->proxy_devices, index));
75     }
76   return NULL;
77 }
78
79 int
80 igmp_proxy_device_add_del (u32 vrf_id, u32 sw_if_index, u8 add)
81 {
82   igmp_main_t *im = &igmp_main;
83   igmp_proxy_device_t *proxy_device;
84   igmp_config_t *config;
85   u32 mfib_index;
86
87   /* check VRF id */
88   mfib_index =
89     mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, sw_if_index);
90   if (mfib_index == ~0)
91     return VNET_API_ERROR_INVALID_INTERFACE;
92   if (vrf_id != mfib_table_get (mfib_index, FIB_PROTOCOL_IP4)->mft_table_id)
93     return VNET_API_ERROR_INVALID_INTERFACE;
94
95   /* check IGMP configuration */
96   config = igmp_config_lookup (sw_if_index);
97   if (!config)
98     return VNET_API_ERROR_INVALID_INTERFACE;
99   if (config->mode != IGMP_MODE_HOST)
100     return VNET_API_ERROR_INVALID_INTERFACE;
101
102   proxy_device = igmp_proxy_device_lookup (vrf_id);
103   if (!proxy_device && add)
104     {
105       vec_validate_init_empty (im->igmp_proxy_device_by_vrf_id, vrf_id, ~0);
106       pool_get (im->proxy_devices, proxy_device);
107       im->igmp_proxy_device_by_vrf_id[vrf_id] =
108         proxy_device - im->proxy_devices;
109       memset (proxy_device, 0, sizeof (igmp_proxy_device_t));
110       proxy_device->vrf_id = vrf_id;
111       proxy_device->upstream_if = sw_if_index;
112       config->proxy_device_id = vrf_id;
113       /* lock mfib table */
114       mfib_table_lock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
115     }
116   else if (proxy_device && !add)
117     {
118       while (vec_len (proxy_device->downstream_ifs) > 0)
119         {
120           igmp_proxy_device_add_del_interface (vrf_id,
121                                                proxy_device->downstream_ifs
122                                                [0], 0);
123         }
124       vec_free (proxy_device->downstream_ifs);
125       proxy_device->downstream_ifs = NULL;
126       im->igmp_proxy_device_by_vrf_id[vrf_id] = ~0;
127       pool_put (im->proxy_devices, proxy_device);
128       config->proxy_device_id = ~0;
129       /* clear proxy database */
130       igmp_clear_config (config);
131       /* unlock mfib table */
132       mfib_table_unlock (mfib_index, FIB_PROTOCOL_IP4, MFIB_SOURCE_IGMP);
133     }
134   else
135     return -1;
136
137   return 0;
138 }
139
140 int
141 igmp_proxy_device_add_del_interface (u32 vrf_id, u32 sw_if_index, u8 add)
142 {
143   igmp_proxy_device_t *proxy_device;
144   u32 index;
145   u32 mfib_index;
146
147   proxy_device = igmp_proxy_device_lookup (vrf_id);
148   if (!proxy_device)
149     return -1;
150
151   /* check VRF id */
152   mfib_index =
153     mfib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP4, sw_if_index);
154   if (mfib_index == ~0)
155     return VNET_API_ERROR_INVALID_INTERFACE;
156   if (vrf_id != mfib_table_get (mfib_index, FIB_PROTOCOL_IP4)->mft_table_id)
157     return VNET_API_ERROR_INVALID_INTERFACE;
158
159   /* check IGMP configuration */
160   igmp_config_t *config;
161   config = igmp_config_lookup (sw_if_index);
162   if (!config)
163     return VNET_API_ERROR_INVALID_INTERFACE;
164   if (config->mode != IGMP_MODE_ROUTER)
165     return VNET_API_ERROR_INVALID_INTERFACE;
166
167   if (add)
168     {
169       if (proxy_device->downstream_ifs)
170         {
171           index = vec_search (proxy_device->downstream_ifs, sw_if_index);
172           if (index != ~0)
173             return -1;
174         }
175       vec_add1 (proxy_device->downstream_ifs, sw_if_index);
176       config->proxy_device_id = vrf_id;
177     }
178   else
179     {
180       if (!proxy_device->downstream_ifs)
181         return -2;
182       index = vec_search (proxy_device->downstream_ifs, sw_if_index);
183       if (index == ~0)
184         return -3;
185       /* remove (S,G)s belonging to this interface from proxy database */
186       igmp_proxy_device_merge_config (config, /* block */ 1);
187       vec_del1 (proxy_device->downstream_ifs, index);
188       config->proxy_device_id = ~0;
189     }
190
191   return 0;
192 }
193
194 void
195 igmp_proxy_device_block_src (igmp_config_t * config, igmp_group_t * group,
196                              igmp_src_t * src)
197 {
198   igmp_proxy_device_t *proxy_device;
199   igmp_config_t *proxy_config;
200   igmp_group_t *proxy_group;
201   igmp_src_t *proxy_src;
202   u8 *ref;
203
204   proxy_device = igmp_proxy_device_lookup (config->proxy_device_id);
205   if (!proxy_device)
206     return;
207
208   proxy_config = igmp_config_lookup (proxy_device->upstream_if);
209   ASSERT (proxy_config);
210
211   proxy_group = igmp_group_lookup (proxy_config, group->key);
212   if (proxy_group == NULL)
213     return;
214
215   proxy_src = igmp_src_lookup (proxy_group, src->key);
216   if (proxy_src == NULL)
217     return;
218
219   if (vec_len (proxy_src->referance_by_config_index) <= group->config)
220     {
221       IGMP_DBG ("proxy block src: invalid config %u", group->config);
222       return;
223     }
224   proxy_src->referance_by_config_index[group->config] = 0;
225   vec_foreach (ref, proxy_src->referance_by_config_index)
226   {
227     if ((*ref) > 0)
228       return;
229   }
230
231   /* build "Block Old Sources" report */
232   igmp_pkt_build_report_t br;
233   ip46_address_t *srcaddrs = NULL;
234
235   igmp_pkt_build_report_init (&br, proxy_config->sw_if_index);
236   vec_add1 (srcaddrs, *proxy_src->key);
237   igmp_pkt_report_v3_add_report (&br, proxy_group->key, srcaddrs,
238                                  IGMP_MEMBERSHIP_GROUP_block_old_sources);
239   igmp_pkt_report_v3_send (&br);
240
241
242   igmp_group_src_remove (proxy_group, proxy_src);
243   igmp_src_free (proxy_src);
244
245   if (igmp_group_n_srcs (proxy_group, IGMP_FILTER_MODE_INCLUDE) == 0)
246     {
247       igmp_proxy_device_mfib_path_add_del (proxy_group, 0);
248       igmp_proxy_device_mfib_path_add_del (group, 0);
249       igmp_group_clear (proxy_group);
250     }
251 }
252
253 always_inline void
254 igmp_proxy_device_merge_src (igmp_group_t * proxy_group, igmp_src_t * src,
255                              ip46_address_t ** srcaddrs, u8 block)
256 {
257   igmp_src_t *proxy_src;
258   u32 d_config;
259
260   proxy_src = igmp_src_lookup (proxy_group, src->key);
261
262   if (proxy_src == NULL)
263     {
264       if (block)
265         return;
266       /* store downstream config index */
267       d_config = igmp_group_get (src->group)->config;
268
269       proxy_src =
270         igmp_src_alloc (igmp_group_index (proxy_group), src->key,
271                         IGMP_MODE_HOST);
272
273       hash_set_mem (proxy_group->igmp_src_by_key
274                     [proxy_group->router_filter_mode], proxy_src->key,
275                     igmp_src_index (proxy_src));
276
277       vec_validate_init_empty (proxy_src->referance_by_config_index, d_config,
278                                0);
279       proxy_src->referance_by_config_index[d_config] = 1;
280       vec_add1 (*srcaddrs, *proxy_src->key);
281     }
282   else
283     {
284       if (block)
285         {
286           d_config = igmp_group_get (src->group)->config;
287           if (vec_len (proxy_src->referance_by_config_index) <= d_config)
288             {
289               IGMP_DBG ("proxy block src: invalid config %u", d_config);
290               return;
291             }
292           proxy_src->referance_by_config_index[d_config] = 0;
293           u8 *ref;
294           vec_foreach (ref, proxy_src->referance_by_config_index)
295           {
296             if ((*ref) > 0)
297               return;
298           }
299
300           vec_add1 (*srcaddrs, *proxy_src->key);
301
302           igmp_group_src_remove (proxy_group, proxy_src);
303           igmp_src_free (proxy_src);
304
305           if (igmp_group_n_srcs (proxy_group, IGMP_FILTER_MODE_INCLUDE) == 0)
306             {
307               igmp_proxy_device_mfib_path_add_del (proxy_group, 0);
308               igmp_group_clear (proxy_group);
309             }
310           return;
311         }
312       d_config = igmp_group_get (src->group)->config;
313       vec_validate (proxy_src->referance_by_config_index, d_config);
314       proxy_src->referance_by_config_index[d_config] = 1;
315       return;
316     }
317 }
318
319 always_inline igmp_group_t *
320 igmp_proxy_device_merge_group (igmp_proxy_device_t * proxy_device,
321                                igmp_group_t * group,
322                                ip46_address_t ** srcaddrs, u8 block)
323 {
324   igmp_config_t *proxy_config;
325   igmp_group_t *proxy_group;
326   igmp_src_t *src;
327
328   proxy_config = igmp_config_lookup (proxy_device->upstream_if);
329   ASSERT (proxy_config);
330
331   proxy_group = igmp_group_lookup (proxy_config, group->key);
332   if (!proxy_group)
333     {
334       if (block)
335         return NULL;
336       u32 tmp = igmp_group_index (group);
337       proxy_group =
338         igmp_group_alloc (proxy_config, group->key,
339                           group->router_filter_mode);
340       igmp_proxy_device_mfib_path_add_del (proxy_group, 1);
341       group = igmp_group_get (tmp);
342     }
343   if (block)
344     {
345       igmp_proxy_device_mfib_path_add_del (group, 0);
346     }
347
348   /* *INDENT-OFF* */
349   FOR_EACH_SRC (src, group, group->router_filter_mode,
350     ({
351       igmp_proxy_device_merge_src (proxy_group, src, srcaddrs, block);
352     }));
353   /* *INDENT-ON* */
354   return proxy_group;
355 }
356
357 void
358 igmp_proxy_device_merge_config (igmp_config_t * config, u8 block)
359 {
360   igmp_proxy_device_t *proxy_device;
361   igmp_group_t *group;
362   igmp_group_t *proxy_group;
363   ip46_address_t *srcaddrs = NULL;
364   igmp_pkt_build_report_t br;
365
366   proxy_device = igmp_proxy_device_lookup (config->proxy_device_id);
367   if (!proxy_device)
368     return;
369
370   igmp_pkt_build_report_init (&br, proxy_device->upstream_if);
371
372   /* *INDENT-OFF* */
373   FOR_EACH_GROUP(group, config,
374     ({
375       proxy_group = igmp_proxy_device_merge_group (proxy_device, group, &srcaddrs, block);
376
377       if ((vec_len(srcaddrs) > 0) && proxy_group)
378         {
379           igmp_pkt_report_v3_add_report (&br, proxy_group->key, srcaddrs,
380                                          block ? IGMP_MEMBERSHIP_GROUP_block_old_sources :
381                                          IGMP_MEMBERSHIP_GROUP_allow_new_sources);
382         }
383       vec_free (srcaddrs);
384     }));
385   /* *INDENT-ON* */
386
387   igmp_pkt_report_v3_send (&br);
388
389 }
390
391 /*
392  * fd.io coding-style-patch-verification: ON
393  *
394  * Local Variables:
395  * eval: (c-set-style "gnu")
396  * End:
397  */