linux-cp: Add VPP->Linux synchronization
[vpp.git] / src / plugins / linux-cp / lcp_interface_sync.c
1 /* Hey Emacs use -*- mode: C -*- */
2 /*
3  * Copyright 2021 Cisco and/or its affiliates.
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at:
8  *
9  *     http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #include <vnet/vnet.h>
19 #include <vnet/plugin/plugin.h>
20 #include <vnet/devices/netlink.h>
21 #include <vnet/ip/ip.h>
22 #include <vppinfra/linux/netns.h>
23 #include <plugins/linux-cp/lcp_interface.h>
24
25 /* helper function to copy forward all sw interface link state flags
26  * MTU, and IP addresses into their counterpart LIP interface.
27  *
28  * This is called upon MTU changes and state changes.
29  */
30 void
31 lcp_itf_pair_sync_state (lcp_itf_pair_t *lip)
32 {
33   vnet_sw_interface_t *sw;
34   vnet_sw_interface_t *sup_sw;
35   int curr_ns_fd = -1;
36   int vif_ns_fd = -1;
37   u32 mtu;
38   u32 netlink_mtu;
39
40   if (!lcp_sync ())
41     return;
42
43   sw =
44     vnet_get_sw_interface_or_null (vnet_get_main (), lip->lip_phy_sw_if_index);
45   if (!sw)
46     return;
47   sup_sw =
48     vnet_get_sw_interface_or_null (vnet_get_main (), sw->sup_sw_if_index);
49
50   if (lip->lip_namespace)
51     {
52       curr_ns_fd = clib_netns_open (NULL /* self */);
53       vif_ns_fd = clib_netns_open (lip->lip_namespace);
54       if (vif_ns_fd != -1)
55         clib_setns (vif_ns_fd);
56     }
57
58   LCP_ITF_PAIR_INFO ("sync_state: %U flags %u sup-flags %u mtu %u sup-mtu %u",
59                      format_lcp_itf_pair, lip, sw->flags, sup_sw->flags,
60                      sw->mtu[VNET_MTU_L3], sup_sw->mtu[VNET_MTU_L3]);
61
62   /* Linux will not allow children to be admin-up if their parent is
63    * admin-down. If child is up but parent is not, force it down.
64    */
65   int state = sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP;
66
67   if (state && !(sup_sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP))
68     {
69       LCP_ITF_PAIR_WARN (
70         "sync_state: %U flags %u sup-flags %u mtu %u sup-mtu %u: "
71         "forcing state to sup-flags to satisfy netlink",
72         format_lcp_itf_pair, lip, sw->flags, sup_sw->flags,
73         sw->mtu[VNET_MTU_L3], sup_sw->mtu[VNET_MTU_L3]);
74       state = 0;
75     }
76   lcp_itf_set_link_state (lip, state);
77
78   /* Linux will clamp MTU of children when the parent is lower. VPP is fine
79    * with differing MTUs. VPP assumes that if a subint has MTU of 0, that it
80    * inherits from its parent. Linux likes to be more explicit, so we
81    * reconcile any differences.
82    */
83   mtu = sw->mtu[VNET_MTU_L3];
84   if (mtu == 0)
85     mtu = sup_sw->mtu[VNET_MTU_L3];
86
87   if (sup_sw->mtu[VNET_MTU_L3] < sw->mtu[VNET_MTU_L3])
88     {
89       LCP_ITF_PAIR_WARN ("sync_state: %U flags %u mtu %u sup-mtu %u: "
90                          "clamping to sup-mtu to satisfy netlink",
91                          format_lcp_itf_pair, lip, sw->flags,
92                          sw->mtu[VNET_MTU_L3], sup_sw->mtu[VNET_MTU_L3]);
93       mtu = sup_sw->mtu[VNET_MTU_L3];
94     }
95
96   /* Set MTU on all of {sw, tap, netlink}. Only send a netlink message if we
97    * really do want to change the MTU.
98    */
99   vnet_sw_interface_set_mtu (vnet_get_main (), lip->lip_phy_sw_if_index, mtu);
100   vnet_sw_interface_set_mtu (vnet_get_main (), lip->lip_host_sw_if_index, mtu);
101   if (NULL == vnet_netlink_get_link_mtu (lip->lip_vif_index, &netlink_mtu))
102     {
103       if (netlink_mtu != mtu)
104         vnet_netlink_set_link_mtu (lip->lip_vif_index, mtu);
105     }
106
107   /* Linux will remove IPv6 addresses on children when the parent state
108    * goes down, so we ensure all IPv4/IPv6 addresses are synced.
109    */
110   lcp_itf_set_interface_addr (lip);
111
112   if (vif_ns_fd != -1)
113     close (vif_ns_fd);
114
115   if (curr_ns_fd != -1)
116     {
117       clib_setns (curr_ns_fd);
118       close (curr_ns_fd);
119     }
120
121   return;
122 }
123
124 static walk_rc_t
125 lcp_itf_pair_walk_sync_state_all_cb (index_t lipi, void *ctx)
126 {
127   lcp_itf_pair_t *lip;
128   lip = lcp_itf_pair_get (lipi);
129   if (!lip)
130     return WALK_CONTINUE;
131
132   lcp_itf_pair_sync_state (lip);
133   return WALK_CONTINUE;
134 }
135
136 static walk_rc_t
137 lcp_itf_pair_walk_sync_state_hw_cb (vnet_main_t *vnm, u32 sw_if_index,
138                                     void *arg)
139 {
140   lcp_itf_pair_t *lip;
141
142   lip = lcp_itf_pair_get (lcp_itf_pair_find_by_phy (sw_if_index));
143   if (!lip)
144     {
145       return WALK_CONTINUE;
146     }
147
148   lcp_itf_pair_sync_state (lip);
149   return WALK_CONTINUE;
150 }
151
152 void
153 lcp_itf_pair_sync_state_all ()
154 {
155   lcp_itf_pair_walk (lcp_itf_pair_walk_sync_state_all_cb, 0);
156 }
157
158 void
159 lcp_itf_pair_sync_state_hw (vnet_hw_interface_t *hi)
160 {
161   if (!hi)
162     return;
163   LCP_ITF_PAIR_DBG ("sync_state_hw: hi %U", format_vnet_sw_if_index_name,
164                     vnet_get_main (), hi->hw_if_index);
165
166   vnet_hw_interface_walk_sw (vnet_get_main (), hi->hw_if_index,
167                              lcp_itf_pair_walk_sync_state_hw_cb, NULL);
168 }
169
170 static clib_error_t *
171 lcp_itf_admin_state_change (vnet_main_t *vnm, u32 sw_if_index, u32 flags)
172 {
173   lcp_itf_pair_t *lip;
174   vnet_hw_interface_t *hi;
175   vnet_sw_interface_t *si;
176
177   if (!lcp_sync ())
178     return 0;
179
180   LCP_ITF_PAIR_DBG ("admin_state_change: sw %U %u",
181                     format_vnet_sw_if_index_name, vnm, sw_if_index, flags);
182
183   // Sync interface state changes into host
184   lip = lcp_itf_pair_get (lcp_itf_pair_find_by_phy (sw_if_index));
185   if (!lip)
186     return NULL;
187   LCP_ITF_PAIR_INFO ("admin_state_change: %U flags %u", format_lcp_itf_pair,
188                      lip, flags);
189
190   if (vnet_sw_interface_is_sub (vnm, sw_if_index))
191     {
192       lcp_itf_pair_sync_state (lip);
193       return NULL;
194     }
195
196   // When Linux changes link on a parent interface, all of its children also
197   // change. If a parent interface changes MTU, all of its children are clamped
198   // at that MTU by Linux. Neither holds true in VPP, so we are forced to undo
199   // change by walking the sub-interfaces of a phy and syncing their state back
200   // into Linux.
201   si = vnet_get_sw_interface_or_null (vnm, sw_if_index);
202   if (!si)
203     return NULL;
204
205   hi = vnet_get_hw_interface_or_null (vnm, si->hw_if_index);
206   if (!hi)
207     return NULL;
208   LCP_ITF_PAIR_DBG ("admin_state_change: si %U hi %U, syncing children",
209                     format_vnet_sw_if_index_name, vnm, si->sw_if_index,
210                     format_vnet_sw_if_index_name, vnm, hi->sw_if_index);
211
212   lcp_itf_pair_sync_state_hw (hi);
213
214   return NULL;
215 }
216
217 VNET_SW_INTERFACE_ADMIN_UP_DOWN_FUNCTION (lcp_itf_admin_state_change);
218
219 static clib_error_t *
220 lcp_itf_mtu_change (vnet_main_t *vnm, u32 sw_if_index, u32 flags)
221 {
222   vnet_sw_interface_t *si;
223   vnet_hw_interface_t *hi;
224   if (!lcp_sync ())
225     return NULL;
226
227   LCP_ITF_PAIR_DBG ("mtu_change: sw %U %u", format_vnet_sw_if_index_name, vnm,
228                     sw_if_index, flags);
229
230   if (vnet_sw_interface_is_sub (vnm, sw_if_index))
231     {
232       lcp_itf_pair_t *lip;
233       lip = lcp_itf_pair_get (lcp_itf_pair_find_by_phy (sw_if_index));
234       if (lip)
235         lcp_itf_pair_sync_state (lip);
236       return NULL;
237     }
238
239   // When Linux changes link on a parent interface, all of its children also
240   // change. If a parent interface changes MTU, all of its children are clamped
241   // at that MTU by Linux. Neither holds true in VPP, so we are forced to undo
242   // change by walking the sub-interfaces of a phy and syncing their state back
243   // into Linux.
244   si = vnet_get_sw_interface_or_null (vnm, sw_if_index);
245   if (!si)
246     return NULL;
247
248   hi = vnet_get_hw_interface_or_null (vnm, si->hw_if_index);
249   if (!hi)
250     return NULL;
251   LCP_ITF_PAIR_DBG ("mtu_change: si %U hi %U, syncing children",
252                     format_vnet_sw_if_index_name, vnm, si->sw_if_index,
253                     format_vnet_sw_if_index_name, vnm, hi->sw_if_index);
254
255   lcp_itf_pair_sync_state_hw (hi);
256
257   return NULL;
258 }
259
260 VNET_SW_INTERFACE_MTU_CHANGE_FUNCTION (lcp_itf_mtu_change);
261
262 static void
263 lcp_itf_ip4_add_del_interface_addr (ip4_main_t *im, uword opaque,
264                                     u32 sw_if_index, ip4_address_t *address,
265                                     u32 address_length, u32 if_address_index,
266                                     u32 is_del)
267 {
268   const lcp_itf_pair_t *lip;
269   int curr_ns_fd = -1;
270   int vif_ns_fd = -1;
271
272   if (!lcp_sync ())
273     return;
274
275   LCP_ITF_PAIR_DBG ("ip4_addr_%s: si:%U %U/%u", is_del ? "del" : "add",
276                     format_vnet_sw_if_index_name, vnet_get_main (),
277                     sw_if_index, format_ip4_address, address, address_length);
278
279   lip = lcp_itf_pair_get (lcp_itf_pair_find_by_phy (sw_if_index));
280   if (!lip)
281     return;
282
283   if (lip->lip_namespace)
284     {
285       curr_ns_fd = clib_netns_open (NULL /* self */);
286       vif_ns_fd = clib_netns_open (lip->lip_namespace);
287       if (vif_ns_fd != -1)
288         clib_setns (vif_ns_fd);
289     }
290
291   LCP_ITF_PAIR_DBG ("ip4_addr_%s: %U ip4 %U/%u", is_del ? "del" : "add",
292                     format_lcp_itf_pair, lip, format_ip4_address, address,
293                     address_length);
294
295   if (is_del)
296     vnet_netlink_del_ip4_addr (lip->lip_vif_index, address, address_length);
297   else
298     vnet_netlink_add_ip4_addr (lip->lip_vif_index, address, address_length);
299
300   if (vif_ns_fd != -1)
301     close (vif_ns_fd);
302
303   if (curr_ns_fd != -1)
304     {
305       clib_setns (curr_ns_fd);
306       close (curr_ns_fd);
307     }
308   return;
309 }
310
311 static void
312 lcp_itf_ip6_add_del_interface_addr (ip6_main_t *im, uword opaque,
313                                     u32 sw_if_index, ip6_address_t *address,
314                                     u32 address_length, u32 if_address_index,
315                                     u32 is_del)
316 {
317   const lcp_itf_pair_t *lip;
318   int curr_ns_fd = -1;
319   int vif_ns_fd = -1;
320
321   if (!lcp_sync ())
322     return;
323
324   LCP_ITF_PAIR_DBG ("ip6_addr_%s: si:%U %U/%u", is_del ? "del" : "add",
325                     format_vnet_sw_if_index_name, vnet_get_main (),
326                     sw_if_index, format_ip6_address, address, address_length);
327
328   lip = lcp_itf_pair_get (lcp_itf_pair_find_by_phy (sw_if_index));
329   if (!lip)
330     return;
331
332   if (lip->lip_namespace)
333     {
334       curr_ns_fd = clib_netns_open (NULL /* self */);
335       vif_ns_fd = clib_netns_open (lip->lip_namespace);
336       if (vif_ns_fd != -1)
337         clib_setns (vif_ns_fd);
338     }
339   LCP_ITF_PAIR_DBG ("ip6_addr_%s: %U ip4 %U/%u", is_del ? "del" : "add",
340                     format_lcp_itf_pair, lip, format_ip6_address, address,
341                     address_length);
342   if (is_del)
343     vnet_netlink_del_ip6_addr (lip->lip_vif_index, address, address_length);
344   else
345     vnet_netlink_add_ip6_addr (lip->lip_vif_index, address, address_length);
346
347   if (vif_ns_fd != -1)
348     close (vif_ns_fd);
349
350   if (curr_ns_fd != -1)
351     {
352       clib_setns (curr_ns_fd);
353       close (curr_ns_fd);
354     }
355 }
356
357 static clib_error_t *
358 lcp_itf_interface_add_del (vnet_main_t *vnm, u32 sw_if_index, u32 is_create)
359 {
360   const vnet_sw_interface_t *sw;
361   uword is_sub;
362
363   if (!lcp_auto_subint ())
364     return NULL;
365
366   sw = vnet_get_sw_interface_or_null (vnm, sw_if_index);
367   if (!sw)
368     return NULL;
369
370   is_sub = vnet_sw_interface_is_sub (vnm, sw_if_index);
371   if (!is_sub)
372     return NULL;
373
374   LCP_ITF_PAIR_DBG ("interface_%s: sw %U parent %U", is_create ? "add" : "del",
375                     format_vnet_sw_if_index_name, vnet_get_main (),
376                     sw->sw_if_index, format_vnet_sw_if_index_name,
377                     vnet_get_main (), sw->sup_sw_if_index);
378
379   if (is_create)
380     {
381       const lcp_itf_pair_t *sup_lip;
382       u8 *name = 0;
383
384       // If the parent has a LIP auto-create a LIP for this interface
385       sup_lip =
386         lcp_itf_pair_get (lcp_itf_pair_find_by_phy (sw->sup_sw_if_index));
387       if (!sup_lip)
388         return NULL;
389
390       name = format (name, "%s.%d", sup_lip->lip_host_name, sw->sub.id);
391
392       LCP_ITF_PAIR_INFO (
393         "interface_%s: %U has parent %U, auto-creating LCP with host-if %s",
394         is_create ? "add" : "del", format_vnet_sw_if_index_name,
395         vnet_get_main (), sw->sw_if_index, format_lcp_itf_pair, sup_lip, name);
396
397       lcp_itf_pair_create (sw->sw_if_index, name, LCP_ITF_HOST_TAP,
398                            sup_lip->lip_namespace, NULL);
399
400       vec_free (name);
401     }
402   else
403     {
404       lcp_itf_pair_delete (sw_if_index);
405     }
406
407   return NULL;
408 }
409
410 VNET_SW_INTERFACE_ADD_DEL_FUNCTION (lcp_itf_interface_add_del);
411
412 static clib_error_t *
413 lcp_itf_sync_init (vlib_main_t *vm)
414 {
415   ip4_main_t *im4 = &ip4_main;
416   ip6_main_t *im6 = &ip6_main;
417
418   ip4_add_del_interface_address_callback_t cb4;
419   ip6_add_del_interface_address_callback_t cb6;
420
421   cb4.function = lcp_itf_ip4_add_del_interface_addr;
422   cb4.function_opaque = 0;
423   vec_add1 (im4->add_del_interface_address_callbacks, cb4);
424
425   cb6.function = lcp_itf_ip6_add_del_interface_addr;
426   cb6.function_opaque = 0;
427   vec_add1 (im6->add_del_interface_address_callbacks, cb6);
428
429   return NULL;
430 }
431
432 VLIB_INIT_FUNCTION (lcp_itf_sync_init) = {
433   .runs_after = VLIB_INITS ("vnet_interface_init", "tcp_init", "udp_init"),
434 };
435
436 /*
437  * fd.io coding-style-patch-verification: ON
438  *
439  * Local Variables:
440  * eval: (c-set-style "gnu")
441  * End:
442  */