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