ip6-nd: add ip6-nd proxy
[vpp.git] / src / vnet / ip6-nd / ip6_nd_mirror_proxy.c
1 /*
2  * Copyright (c) 2021 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <vlib/vlib.h>
17
18 #include <vnet/vnet.h>
19 #include <vnet/ethernet/ethernet.h>
20 #include <vnet/feature/feature.h>
21 #include <vnet/ip/ip6_packet.h>
22 #include <vnet/ip-neighbor/ip6_neighbor.h>
23 #include <vnet/ip-neighbor/ip_neighbor.h>
24 #include <vnet/ip-neighbor/ip_neighbor_dp.h>
25 #include <vnet/ip6-nd/ip6_nd_inline.h>
26 #include <vnet/fib/ip6_fib.h>
27 #include <vnet/ip/ip6_ll_table.h>
28
29 #include <vppinfra/error.h>
30
31 int
32 ip6_nd_proxy_enable_disable (u32 sw_if_index, u8 enable)
33 {
34
35   if (enable)
36     {
37       vnet_feature_enable_disable ("ip6-unicast", "ip6-unicast-nd-proxy",
38                                    sw_if_index, 1, NULL, 0);
39       vnet_feature_enable_disable ("ip6-multicast", "ip6-multicast-nd-proxy",
40                                    sw_if_index, 1, NULL, 0);
41     }
42   else
43     {
44       vnet_feature_enable_disable ("ip6-unicast", "ip6-unicast-nd-proxy",
45                                    sw_if_index, 0, NULL, 0);
46       vnet_feature_enable_disable ("ip6-multicast", "ip6-multicast-nd-proxy",
47                                    sw_if_index, 0, NULL, 0);
48     }
49   return 0;
50 }
51
52 static clib_error_t *
53 set_int_ip6_nd_proxy_command_fn (vlib_main_t *vm, unformat_input_t *input,
54                                  vlib_cli_command_t *cmd)
55 {
56   vnet_main_t *vnm = vnet_get_main ();
57   u32 sw_if_index;
58   int enable = 0;
59
60   sw_if_index = ~0;
61
62   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
63     {
64       if (unformat (input, "%U", unformat_vnet_sw_interface, vnm,
65                     &sw_if_index))
66         ;
67       else if (unformat (input, "enable"))
68         enable = 1;
69       else if (unformat (input, "disable"))
70         enable = 0;
71       else
72         break;
73     }
74
75   if (~0 == sw_if_index)
76     return clib_error_return (0, "unknown input '%U'", format_unformat_error,
77                               input);
78
79   ip6_nd_proxy_enable_disable (sw_if_index, enable);
80
81   return 0;
82 }
83
84 VLIB_CLI_COMMAND (set_int_ip6_nd_proxy_enable_command, static) = {
85   .path = "set interface ip6-nd proxy",
86   .short_help = "set interface ip6-nd proxy <intfc> [enable|disable]",
87   .function = set_int_ip6_nd_proxy_command_fn,
88 };
89
90 typedef struct
91 {
92   u8 is_multicast;
93   u32 sw_if_index;
94 } vnet_ip6_nd_proxy_trace_t;
95
96 static u8 *
97 format_ip6_nd_proxy_trace (u8 *s, va_list *args)
98 {
99   CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *);
100   CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *);
101   vnet_main_t *vnm = vnet_get_main ();
102   vnet_ip6_nd_proxy_trace_t *t = va_arg (*args, vnet_ip6_nd_proxy_trace_t *);
103   u32 indent = format_get_indent (s);
104
105   if (t->is_multicast)
106     s = format (s, "%U %U multicast ", format_white_space, indent,
107                 format_vnet_sw_if_index_name, vnm, t->sw_if_index);
108   else
109     s = format (s, "%U %U unicast ", format_white_space, indent,
110                 format_vnet_sw_if_index_name, vnm, t->sw_if_index);
111
112   return s;
113 }
114
115 static_always_inline void
116 ip6_nd_proxy_unicast (vlib_main_t *vm, vlib_node_runtime_t *node,
117                       vlib_buffer_t *b0, ip6_header_t *ip6, u32 *next0)
118 {
119   if (PREDICT_FALSE (ip6->protocol == IP_PROTOCOL_ICMP6))
120     {
121       icmp46_header_t *icmp0;
122       icmp6_type_t type0;
123
124       icmp0 = ip6_next_header (ip6);
125       type0 = icmp0->type;
126       if (type0 == ICMP6_neighbor_solicitation ||
127           type0 == ICMP6_neighbor_advertisement)
128         {
129           icmp6_neighbor_solicitation_or_advertisement_header_t *icmp6_nsa;
130           icmp6_neighbor_discovery_ethernet_link_layer_address_option_t
131             *icmp6_nd_ell_addr;
132           u32 sw_if_index0;
133
134           icmp6_nsa = (void *) icmp0;
135           icmp6_nd_ell_addr = (void *) (icmp6_nsa + 1);
136
137           sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
138
139           /* unicast neighbor solicitation */
140           fib_node_index_t fei;
141           u32 fib_index;
142
143           fib_index = ip6_fib_table_get_index_for_sw_if_index (sw_if_index0);
144
145           if (~0 == fib_index)
146             {
147               *next0 = ICMP6_NEIGHBOR_SOLICITATION_NEXT_DROP;
148             }
149           else
150             {
151               if (ip6_address_is_link_local_unicast (&ip6->dst_address))
152                 {
153                   fei = ip6_fib_table_lookup_exact_match (
154                     ip6_ll_fib_get (sw_if_index0), &ip6->dst_address, 128);
155                 }
156               else
157                 {
158                   fei = ip6_fib_table_lookup_exact_match (
159                     fib_index, &ip6->dst_address, 128);
160                 }
161
162               if (FIB_NODE_INDEX_INVALID != fei)
163                 {
164                   *next0 = ICMP6_NEIGHBOR_SOLICITATION_NEXT_REPLY;
165                   icmp6_send_neighbor_advertisement (
166                     vm, b0, ip6, icmp6_nsa, icmp6_nd_ell_addr, sw_if_index0);
167                 }
168             }
169           if (b0->flags & VLIB_BUFFER_IS_TRACED)
170             {
171               vnet_ip6_nd_proxy_trace_t *t;
172               t = vlib_add_trace (vm, node, b0, sizeof (t[0]));
173               t->sw_if_index = sw_if_index0;
174               t->is_multicast = 0;
175             }
176         }
177     }
178 }
179
180 static_always_inline void
181 ip6_nd_proxy_multicast (vlib_main_t *vm, vlib_node_runtime_t *node,
182                         vlib_buffer_t *b0, ip6_header_t *ip6, u32 *next0)
183 {
184   if (PREDICT_FALSE (ip6->protocol == IP_PROTOCOL_ICMP6))
185     {
186       icmp46_header_t *icmp0;
187       icmp6_type_t type0;
188
189       icmp0 = ip6_next_header (ip6);
190       type0 = icmp0->type;
191       if (type0 == ICMP6_neighbor_solicitation ||
192           type0 == ICMP6_neighbor_advertisement)
193         {
194           icmp6_neighbor_solicitation_or_advertisement_header_t *icmp6_nsa;
195           icmp6_neighbor_discovery_ethernet_link_layer_address_option_t
196             *icmp6_nd_ell_addr;
197           u32 sw_if_index0;
198
199           icmp6_nsa = (void *) icmp0;
200           icmp6_nd_ell_addr = (void *) (icmp6_nsa + 1);
201
202           sw_if_index0 = vnet_buffer (b0)->sw_if_index[VLIB_RX];
203           if (type0 == ICMP6_neighbor_solicitation)
204             {
205               if (
206                 (icmp6_nd_ell_addr->header.type ==
207                  ICMP6_NEIGHBOR_DISCOVERY_OPTION_source_link_layer_address) &&
208                 (!ip6_address_is_unspecified (&ip6->src_address)) &&
209                 (!ip6_address_is_link_local_unicast (&ip6->src_address)))
210                 {
211                   ip_neighbor_learn_t learn = { .sw_if_index = sw_if_index0,
212                                                 .ip = {
213                                                   .version = AF_IP6,
214                                                   .ip.ip6 = ip6->src_address,
215                                                 } };
216                   clib_memcpy (&learn.mac, icmp6_nd_ell_addr->ethernet_address,
217                                sizeof (learn.mac));
218                   ip_neighbor_learn_dp (&learn);
219
220                   *next0 = ICMP6_NEIGHBOR_SOLICITATION_NEXT_REPLY;
221                   icmp6_send_neighbor_advertisement (
222                     vm, b0, ip6, icmp6_nsa, icmp6_nd_ell_addr, sw_if_index0);
223                 }
224             }
225           else // type0 = ICMP6_neighbor_advertisement
226             {
227               icmp6_neighbor_solicitation_or_advertisement_header_t
228                 *icmp6_nsa = (void *) icmp0;
229               icmp6_neighbor_discovery_ethernet_link_layer_address_option_t
230                 *icmp6_nd_ell_addr = (void *) (icmp6_nsa + 1);
231               if (
232                 (icmp6_nd_ell_addr->header.type ==
233                  ICMP6_NEIGHBOR_DISCOVERY_OPTION_target_link_layer_address) &&
234                 (!ip6_address_is_unspecified (&ip6->src_address)) &&
235                 (!ip6_address_is_link_local_unicast (&ip6->src_address)))
236                 {
237                   ip_neighbor_learn_t learn = { .sw_if_index = sw_if_index0,
238                                                 .ip = {
239                                                   .version = AF_IP6,
240                                                   .ip.ip6 =
241                                                     icmp6_nsa->target_address,
242                                                 } };
243                   clib_memcpy (&learn.mac, icmp6_nd_ell_addr->ethernet_address,
244                                sizeof (learn.mac));
245                   ip_neighbor_learn_dp (&learn);
246
247                   *next0 = ICMP6_NEIGHBOR_SOLICITATION_NEXT_DROP;
248                 }
249             }
250
251           if (b0->flags & VLIB_BUFFER_IS_TRACED)
252             {
253               vnet_ip6_nd_proxy_trace_t *t;
254               t = vlib_add_trace (vm, node, b0, sizeof (t[0]));
255               t->sw_if_index = sw_if_index0;
256               t->is_multicast = 1;
257             }
258         }
259     }
260 }
261
262 static_always_inline uword
263 ip6_nd_proxy_node_inline (vlib_main_t *vm, vlib_node_runtime_t *node,
264                           vlib_frame_t *frame, u8 is_multicast)
265 {
266   u32 n_left_from, *from, *to_next;
267   u32 next_index, n_left_to_next;
268   vlib_buffer_t *bufs[VLIB_FRAME_SIZE], **b = bufs;
269
270   from = vlib_frame_vector_args (frame);
271   n_left_from = frame->n_vectors;
272   next_index = node->cached_next_index;
273
274   vlib_get_buffers (vm, from, bufs, n_left_from);
275
276   while (n_left_from > 0)
277     {
278       vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next);
279
280       while (n_left_from > 4 && n_left_to_next > 2)
281         {
282           ip6_header_t *ip6_0, *ip6_1;
283           u32 next0, next1;
284           u32 bi0, bi1;
285
286           /* Prefetch next iteration. */
287           {
288             vlib_prefetch_buffer_header (b[2], LOAD);
289             vlib_prefetch_buffer_header (b[3], LOAD);
290
291             vlib_prefetch_buffer_data (b[2], LOAD);
292             vlib_prefetch_buffer_data (b[3], LOAD);
293           }
294
295           /*
296            * speculatively enqueue b0 and b1 to the current next frame
297            */
298           to_next[0] = bi0 = from[0];
299           to_next[1] = bi1 = from[1];
300           to_next += 2;
301           n_left_to_next -= 2;
302
303           vnet_feature_next (&next0, b[0]);
304           vnet_feature_next (&next1, b[1]);
305
306           ip6_0 = vlib_buffer_get_current (b[0]);
307           ip6_1 = vlib_buffer_get_current (b[1]);
308
309           if (is_multicast)
310             {
311               ip6_nd_proxy_multicast (vm, node, b[0], ip6_0, &next0);
312               ip6_nd_proxy_multicast (vm, node, b[1], ip6_1, &next1);
313             }
314           else
315             {
316               ip6_nd_proxy_unicast (vm, node, b[0], ip6_0, &next0);
317               ip6_nd_proxy_unicast (vm, node, b[1], ip6_1, &next1);
318             }
319           vlib_validate_buffer_enqueue_x2 (vm, node, next_index, to_next,
320                                            n_left_to_next, bi0, bi1, next0,
321                                            next1);
322
323           b += 2;
324           from += 2;
325           n_left_from -= 2;
326         }
327
328       while (n_left_from > 0 && n_left_to_next > 0)
329         {
330           ip6_header_t *ip6_0;
331           u32 next0, bi0;
332
333           /* speculatively enqueue b0 to the current next frame */
334           to_next[0] = bi0 = from[0];
335           to_next += 1;
336           n_left_to_next -= 1;
337
338           vnet_feature_next (&next0, b[0]);
339           ip6_0 = vlib_buffer_get_current (b[0]);
340
341           if (is_multicast)
342             ip6_nd_proxy_multicast (vm, node, b[0], ip6_0, &next0);
343           else
344             ip6_nd_proxy_unicast (vm, node, b[0], ip6_0, &next0);
345
346           vlib_validate_buffer_enqueue_x1 (vm, node, next_index, to_next,
347                                            n_left_to_next, bi0, next0);
348           b += 1;
349           from += 1;
350           n_left_from -= 1;
351         }
352       vlib_put_next_frame (vm, node, next_index, n_left_to_next);
353     }
354
355   return frame->n_vectors;
356 }
357
358 VLIB_NODE_FN (ip6_unicast_nd_proxy_node)
359 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
360 {
361   return ip6_nd_proxy_node_inline (vm, node, frame, 0 /* is_multicast */);
362 }
363
364 VLIB_REGISTER_NODE (ip6_unicast_nd_proxy_node) = {
365   .vector_size = sizeof (u32),
366   .format_trace = format_ip6_nd_proxy_trace,
367   .type = VLIB_NODE_TYPE_INTERNAL,
368   .n_errors = 0,
369   .n_next_nodes = ICMP6_NEIGHBOR_SOLICITATION_N_NEXT,
370   .next_nodes = {
371     [ICMP6_NEIGHBOR_SOLICITATION_NEXT_DROP] = "ip6-drop",
372     [ICMP6_NEIGHBOR_SOLICITATION_NEXT_REPLY] = "interface-output",
373   },
374   .name = "ip6-unicast-nd-proxy",
375 };
376
377 VLIB_NODE_FN (ip6_multicast_nd_proxy_node)
378 (vlib_main_t *vm, vlib_node_runtime_t *node, vlib_frame_t *frame)
379 {
380   return ip6_nd_proxy_node_inline (vm, node, frame, 1 /* is_multicast */);
381 }
382
383 VLIB_REGISTER_NODE (ip6_multicast_nd_proxy_node) = {
384   .vector_size = sizeof (u32),
385   .format_trace = format_ip6_nd_proxy_trace,
386   .type = VLIB_NODE_TYPE_INTERNAL,
387   .n_errors = 0,
388   .n_next_nodes = ICMP6_NEIGHBOR_SOLICITATION_N_NEXT,
389   .next_nodes = {
390     [ICMP6_NEIGHBOR_SOLICITATION_NEXT_DROP] = "ip6-drop",
391     [ICMP6_NEIGHBOR_SOLICITATION_NEXT_REPLY] = "interface-output",
392   },
393   .name = "ip6-multicast-nd-proxy",
394 };
395
396 VNET_FEATURE_INIT (ip6_unicast_nd_proxy_node, static) = {
397   .arc_name = "ip6-unicast",
398   .node_name = "ip6-unicast-nd-proxy",
399   .runs_before = VNET_FEATURES ("ip6-lookup"),
400 };
401
402 VNET_FEATURE_INIT (ip6_multicast_nd_proxy_node, static) = {
403   .arc_name = "ip6-multicast",
404   .node_name = "ip6-multicast-nd-proxy",
405   .runs_before = VNET_FEATURES ("ip6-mfib-forward-lookup"),
406 };
407
408 /*
409  * fd.io coding-style-patch-verification: ON
410  *
411  * Local Variables:
412  * eval: (c-set-style "gnu")
413  * End:
414  */