1/*
2 * Copyright (c) 2018 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 <vnet/ip6-nd/ip6_ra.h>
17
18#include <vnet/ip/ip6.h>
19#include <vnet/ip/ip6_link.h>
20#include <vnet/ethernet/ethernet.h>
21#include <vnet/fib/fib_table.h>
22#include <signal.h>
23#include <math.h>
24
25typedef struct
26{
27  u32 sw_if_index;
28  u8 address_length;
29  ip6_address_t address;
30  f64 due_time;
31} slaac_address_t;
32
33typedef struct
34{
35  u32 sw_if_index;
36  ip6_address_t router_address;
37  f64 due_time;
38} default_route_t;
39
40typedef struct
41{
42  u8 enabled;
43  u8 install_default_routes;
44} interface_config_t;
45
46typedef struct
47{
48  u8 enabled;
49  u8 events_on;
50
51  interface_config_t *config_by_sw_if_index;
52  slaac_address_t *slaac_address_pool;
53  default_route_t *default_route_pool;
54
55  /* binary API client */
56  u8 api_connected;
57  u32 my_client_index;
58
59  /* logging */
60  vlib_log_class_t log_class;
61
62  /* convenience */
63  vlib_main_t *vlib_main;
64  vnet_main_t *vnet_main;
65  u32 node_index;
66} rd_cp_main_t;
67
68rd_cp_main_t rd_cp_main;
69
70enum
71{
72  RD_CP_EVENT_INTERRUPT,
73};
74
75#define vl_api_ip6_nd_address_autoconfig_t_print vl_noop_handler
76
77static void
78router_solicitation_start_stop (u32 sw_if_index, u8 start)
79{
80  rd_cp_main_t *rm = &rd_cp_main;
81  icmp6_send_router_solicitation_params_t params = { 0, };
82
83  if (start)
84    {
85      params.irt = 1;
86      params.mrt = 120;
87    }
88
89  icmp6_send_router_solicitation (rm->vlib_main, sw_if_index, !start,
90				  &params);
91}
92
93static void interrupt_process (void);
94
95static int
96add_slaac_address (vlib_main_t * vm, u32 sw_if_index, u8 address_length,
97		   const ip6_address_t * address, f64 due_time)
98{
99  rd_cp_main_t *rm = &rd_cp_main;
100  slaac_address_t *slaac_address;
101  clib_error_t *rv = 0;
102
103  pool_get_zero (rm->slaac_address_pool, slaac_address);
104
105  slaac_address->sw_if_index = sw_if_index;
106  slaac_address->address_length = address_length;
107  slaac_address->address = *address;
108  slaac_address->due_time = due_time;
109
110  rv =
111    ip6_add_del_interface_address (vm, sw_if_index, &slaac_address->address,
112				   address_length, 0);
113
114  return rv != 0;
115}
116
117static void
118add_default_route (vlib_main_t * vm, u32 sw_if_index,
119		   const ip6_address_t * next_hop_address, f64 due_time)
120{
121  rd_cp_main_t *rm = &rd_cp_main;
122  default_route_t *default_route;
123
124  pool_get_zero (rm->default_route_pool, default_route);
125
126  default_route->sw_if_index = sw_if_index;
127  default_route->router_address = *next_hop_address;
128  default_route->due_time = due_time;
129
130  {
131    u32 fib_index = fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6,
132							 default_route->
133							 sw_if_index);
134    fib_prefix_t pfx = {
135      .fp_proto = FIB_PROTOCOL_IP6,
136    };
137    ip46_address_t nh = {
138      .ip6 = default_route->router_address,
139    };
140    fib_table_entry_update_one_path (fib_index, &pfx,
141				     FIB_SOURCE_API,
142				     FIB_ENTRY_FLAG_NONE,
143				     DPO_PROTO_IP6,
144				     &nh,
145				     default_route->sw_if_index,
146				     0, 1, NULL, FIB_ROUTE_PATH_FLAG_NONE);
147  }
148}
149
150static int
151remove_slaac_address (vlib_main_t * vm, slaac_address_t * slaac_address)
152{
153  rd_cp_main_t *rm = &rd_cp_main;
154  clib_error_t *rv = 0;
155
156  rv = ip6_add_del_interface_address (vm, slaac_address->sw_if_index,
157				      &slaac_address->address,
158				      slaac_address->address_length, 1);
159
160  pool_put (rm->slaac_address_pool, slaac_address);
161
162  return rv != 0;
163}
164
165static void
166remove_default_route (vlib_main_t * vm, default_route_t * default_route)
167{
168  rd_cp_main_t *rm = &rd_cp_main;
169
170  {
171    u32 fib_index = fib_table_get_index_for_sw_if_index (FIB_PROTOCOL_IP6,
172							 default_route->
173							 sw_if_index);
174    fib_prefix_t pfx = {
175      .fp_proto = FIB_PROTOCOL_IP6,
176    };
177    ip46_address_t nh = {
178      .ip6 = default_route->router_address,
179    };
180    fib_table_entry_path_remove (fib_index, &pfx,
181				 FIB_SOURCE_API,
182				 DPO_PROTO_IP6,
183				 &nh,
184				 default_route->sw_if_index,
185				 0, 1, FIB_ROUTE_PATH_FLAG_NONE);
186  }
187
188  pool_put (rm->default_route_pool, default_route);
189}
190
191static u32
192get_interface_mac_address (u32 sw_if_index, u8 mac[])
193{
194  rd_cp_main_t *rm = &rd_cp_main;
195  vnet_sw_interface_t *si;
196  ethernet_interface_t *eth_if = 0;
197
198  if (!vnet_sw_interface_is_api_valid (rm->vnet_main, sw_if_index))
199    {
200      vlib_log_warn (rm->log_class, "Invalid sw_if_index");
201      return 1;
202    }
203
204  si = vnet_get_sup_sw_interface (rm->vnet_main, sw_if_index);
205  if (si->type == VNET_SW_INTERFACE_TYPE_HARDWARE)
206    eth_if = ethernet_get_interface (&ethernet_main, si->hw_if_index);
207
208  if (!eth_if)
209    {
210      vlib_log_warn (rm->log_class, "Failed to get hardware interface");
211      return 1;
212    }
213
214  clib_memcpy_fast (mac, eth_if->address, 6);
215
216  return 0;
217}
218
219static u8
220ip6_prefixes_equal (ip6_address_t * prefix1, ip6_address_t * prefix2, u8 len)
221{
222  if (len >= 64)
223    {
224      if (prefix1->as_u64[0] != prefix2->as_u64[0])
225	return 0;
226      if (len == 64)
227	return 1;
228      return prefix1->as_u64[1] >> (128 - len) ==
229	prefix2->as_u64[1] >> (128 - len);
230    }
231  return prefix1->as_u64[0] >> (64 - len) == prefix2->as_u64[0] >> (64 - len);
232}
233
234#define PREFIX_FLAG_A (1 << 6)
235#define PREFIX_FLAG_L (1 << 7)
236
237static void
238ip6_ra_report_handler (const ip6_ra_report_t * r)
239{
240  rd_cp_main_t *rm = &rd_cp_main;
241  vlib_main_t *vm = rm->vlib_main;
242  interface_config_t *if_config;
243  default_route_t *default_route;
244  slaac_address_t *slaac_address;
245  u32 sw_if_index;
246  u16 router_lifetime_in_sec;
247  u32 n_prefixes;
248  ra_report_prefix_info_t *prefix;
249  u8 mac[6];
250  f64 current_time;
251  u32 i;
252
253  current_time = vlib_time_now (vm);
254
255  sw_if_index = r->sw_if_index;
256
257  if (sw_if_index >= vec_len (rm->config_by_sw_if_index))
258    return;
259  if_config = &rm->config_by_sw_if_index[sw_if_index];
260
261  if (if_config->install_default_routes)
262    {
263      router_lifetime_in_sec = r->router_lifetime_in_sec;
264      u8 route_already_present = 0;
265      /* *INDENT-OFF* */
266      pool_foreach (default_route, rm->default_route_pool,
267      ({
268        if (default_route->sw_if_index != sw_if_index)
269          ;
270        else if (0 != memcmp (&default_route->router_address,
271                              &r->router_address, 16))
272          ;
273        else
274          {
275            route_already_present = 1;
276            goto default_route_pool_foreach_out;
277          }
278      }));
279      /* *INDENT-ON* */
280    default_route_pool_foreach_out:
281
282      if (!route_already_present)
283	{
284	  if (router_lifetime_in_sec != 0)
285	    add_default_route (vm, sw_if_index, &r->router_address,
286			       current_time + router_lifetime_in_sec);
287	}
288      else
289	{
290	  if (router_lifetime_in_sec != 0)
291	    default_route->due_time = current_time + router_lifetime_in_sec;
292	  else
293	    remove_default_route (vm, default_route);
294	}
295    }
296
297  if (get_interface_mac_address (sw_if_index, mac) != 0)
298    {
299      vlib_log_warn (rm->log_class, "Error getting MAC address");
300      return;
301    }
302
303  if (!if_config->enabled)
304    return;
305
306  n_prefixes = vec_len (r->prefixes);
307  for (i = 0; i < n_prefixes; i++)
308    {
309      ip6_address_t *dst_address;
310      u8 prefix_length;
311      u32 valid_time;
312      u32 preferred_time;
313      f64 due_time;
314
315      prefix = &r->prefixes[i];
316
317      if (!(prefix->flags & PREFIX_FLAG_A))
318	continue;
319
320      dst_address = &prefix->prefix.fp_addr.ip6;
321      prefix_length = prefix->prefix.fp_len;
322
323      if (ip6_address_is_link_local_unicast (dst_address))
324	continue;
325
326      valid_time = prefix->valid_time;
327      preferred_time = prefix->preferred_time;
328
329      if (preferred_time > valid_time)
330	continue;
331
332      if (prefix_length != 64)
333	continue;
334
335      u8 address_already_present = 0;
336      /* *INDENT-OFF* */
337      pool_foreach (slaac_address, rm->slaac_address_pool,
338      ({
339        if (slaac_address->sw_if_index != sw_if_index)
340          ;
341        else if (slaac_address->address_length != prefix_length)
342          ;
343        else if (!ip6_prefixes_equal (&slaac_address->address, dst_address,
344                                 prefix_length))
345          ;
346        else
347          {
348            address_already_present = 1;
349            goto slaac_address_pool_foreach_out;
350          }
351      }));
352      /* *INDENT-ON* */
353    slaac_address_pool_foreach_out:
354
355      if (address_already_present)
356	{
357	  f64 remaining_life_time = slaac_address->due_time - current_time;
358	  if (valid_time > 2 * 60 * 60 || valid_time > remaining_life_time)
359	    slaac_address->due_time = current_time + valid_time;
360	  else if (remaining_life_time > 2 * 60 * 60)
361	    slaac_address->due_time = current_time + 2 * 60 * 60;
362	  continue;
363	}
364
365      if (valid_time == 0)
366	continue;
367
368      due_time = current_time + valid_time;
369
370      ip6_address_t addr;
371      addr.as_u64[0] = dst_address->as_u64[0];
372      /* Invert the "u" bit */
373      addr.as_u8[8] = mac[0] ^ (1 << 1);
374      addr.as_u8[9] = mac[1];
375      addr.as_u8[10] = mac[2];
376      addr.as_u8[11] = 0xFF;
377      addr.as_u8[12] = 0xFE;
378      addr.as_u8[13] = mac[3];
379      addr.as_u8[14] = mac[4];
380      addr.as_u8[15] = mac[5];
381
382      add_slaac_address (vm, sw_if_index, prefix_length, &addr, due_time);
383    }
384
385  interrupt_process ();
386
387  return;
388}
389
390static uword
391rd_cp_process (vlib_main_t * vm, vlib_node_runtime_t * rt, vlib_frame_t * f)
392{
393  uword *event_data = 0;
394  rd_cp_main_t *rm = &rd_cp_main;
395  slaac_address_t *slaac_address;
396  default_route_t *default_route;
397  f64 sleep_time = 1e9;
398  f64 current_time;
399  f64 due_time;
400
401  while (1)
402    {
403      vlib_process_wait_for_event_or_clock (vm, sleep_time);
404      vlib_process_get_events (vm, &event_data);
405
406      vec_reset_length (event_data);
407
408      current_time = vlib_time_now (vm);
409      do
410	{
411	  due_time = current_time + 1e9;
412	  u32 index;
413	  /*
414	   * we do not use pool_foreach() to iterate over pool elements here
415	   * as we are removing elements inside the loop body
416	   */
417          /* *INDENT-OFF* */
418          pool_foreach_index (index, rm->slaac_address_pool,
419          ({
420            slaac_address = pool_elt_at_index(rm->slaac_address_pool, index);
421            if (slaac_address->due_time > current_time)
422              {
423                if (slaac_address->due_time < due_time)
424                  due_time = slaac_address->due_time;
425              }
426            else
427              {
428                u32 sw_if_index = slaac_address->sw_if_index;
429                remove_slaac_address (vm, slaac_address);
430                /* make sure ip6 stays enabled */
431                ip6_link_enable (sw_if_index, NULL);
432              }
433          }));
434          pool_foreach_index (index, rm->default_route_pool,
435          ({
436            default_route = pool_elt_at_index(rm->default_route_pool, index);
437            if (default_route->due_time > current_time)
438              {
439                if (default_route->due_time < due_time)
440                  due_time = default_route->due_time;
441              }
442            else
443              remove_default_route (vm, default_route);
444          }));
445          /* *INDENT-ON* */
446	  current_time = vlib_time_now (vm);
447	}
448      while (due_time < current_time);
449
450      sleep_time = due_time - current_time;
451    }
452
453  return 0;
454}
455
456/* *INDENT-OFF* */
457VLIB_REGISTER_NODE (rd_cp_process_node) = {
458    .function = rd_cp_process,
459    .type = VLIB_NODE_TYPE_PROCESS,
460    .name = "rd-cp-process",
461};
462/* *INDENT-ON* */
463
464static void
465interrupt_process (void)
466{
467  rd_cp_main_t *rm = &rd_cp_main;
468  vlib_main_t *vm = rm->vlib_main;
469
470  vlib_process_signal_event (vm, rd_cp_process_node.index,
471			     RD_CP_EVENT_INTERRUPT, 0);
472}
473
474int
475rd_cp_set_address_autoconfig (u32 sw_if_index,
476			      u8 enable, u8 install_default_routes)
477{
478  rd_cp_main_t *rm = &rd_cp_main;
479  vlib_main_t *vm = rm->vlib_main;
480  vnet_main_t *vnm = rm->vnet_main;
481  interface_config_t *if_config;
482  interface_config_t empty_config = { 0, 0 };
483  slaac_address_t *slaac_address;
484  default_route_t *default_route;
485
486  if (!enable)
487    install_default_routes = 0;
488
489  if (!vnet_sw_interface_is_api_valid (vnm, sw_if_index))
490    {
491      vlib_log_warn (rm->log_class, "Invalid sw_if_index");
492      return 1;
493    }
494
495  if (!rm->enabled)
496    {
497      /* process kickoff */
498      interrupt_process ();
499      rm->enabled = 1;
500    }
501
502  vec_validate_init_empty (rm->config_by_sw_if_index, sw_if_index,
503			   empty_config);
504  if_config = &rm->config_by_sw_if_index[sw_if_index];
505
506  if (!if_config->enabled && enable)
507    ip6_link_enable (sw_if_index, NULL);
508
509  if ((!if_config->enabled && enable)
510      || (!if_config->install_default_routes && install_default_routes))
511    router_solicitation_start_stop (sw_if_index, 1);
512  else if (if_config->enabled && !enable)
513    router_solicitation_start_stop (sw_if_index, 0);
514
515  if (if_config->enabled && !enable)
516    {
517      /* *INDENT-OFF* */
518      pool_foreach (slaac_address, rm->slaac_address_pool,
519      ({
520          remove_slaac_address (vm, slaac_address);
521      }));
522      /* *INDENT-ON* */
523    }
524  if (if_config->install_default_routes && !install_default_routes)
525    {
526      /* *INDENT-OFF* */
527      pool_foreach (default_route, rm->default_route_pool,
528      ({
529          remove_default_route (vm, default_route);
530      }));
531      /* *INDENT-ON* */
532    }
533
534  if_config->enabled = enable;
535  if_config->install_default_routes = install_default_routes;
536
537  return 0;
538}
539
540static clib_error_t *
541ip6_nd_address_autoconfig (vlib_main_t * vm,
542			   unformat_input_t * input, vlib_cli_command_t * cmd)
543{
544  rd_cp_main_t *rm = &rd_cp_main;
545  vnet_main_t *vnm = rm->vnet_main;
546  clib_error_t *error = 0;
547  u32 sw_if_index = ~0;
548  u8 enable = 1;
549  u8 default_route = 0;
550
551  while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
552    {
553      if (unformat
554	  (input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index))
555	;
556      if (unformat (input, "default-route"))
557	default_route = 1;
558      if (unformat (input, "disable"))
559	enable = 0;
560      else
561	break;
562    }
563
564  if (sw_if_index != ~0)
565    {
566      if (rd_cp_set_address_autoconfig (sw_if_index, enable, default_route) !=
567	  0)
568	error = clib_error_return (0, "Invalid sw_if_index");
569    }
570  else
571    error = clib_error_return (0, "Missing sw_if_index");
572
573  return error;
574}
575
576/*?
577 * This command is used to enable ND address autoconfiguration
578 * on particular interface including setting up default routes.
579 *
580 * @cliexpar
581 * @parblock
582 * Example of how to enable ND address autoconfiguration:
583 * @cliexcmd{ip6 nd address autoconfig GigabitEthernet2/0/0}
584 * Example of how to enable ND address autoconfiguration
585 * with setting up default routes:
586 * @cliexcmd{ip6 nd address autoconfig GigabitEthernet2/0/0 default-route}
587 * Example of how to disable ND address autoconfiguration:
588 * @cliexcmd{ip6 nd address autoconfig GigabitEthernet2/0/0 disable}
589 * @endparblock
590?*/
591/* *INDENT-OFF* */
592VLIB_CLI_COMMAND (ip6_nd_address_autoconfig_command, static) = {
593  .path = "ip6 nd address autoconfig",
594  .short_help = "ip6 nd address autoconfig <interface> [default-route|disable]",
595  .function = ip6_nd_address_autoconfig,
596};
597/* *INDENT-ON* */
598
599static clib_error_t *
600rd_cp_init (vlib_main_t * vm)
601{
602  rd_cp_main_t *rm = &rd_cp_main;
603
604  rm->vlib_main = vm;
605  rm->vnet_main = vnet_get_main ();
606  rm->node_index = rd_cp_process_node.index;
607
608  rm->log_class = vlib_log_register_class ("rd_cp", 0);
609
610  ip6_ra_report_register (ip6_ra_report_handler);
611
612  return (NULL);
613}
614
615VLIB_INIT_FUNCTION (rd_cp_init);
616
617/*
618 * fd.io coding-style-patch-verification: ON
619 *
620 * Local Variables:
621 * eval: (c-set-style "gnu")
622 * End:
623 */
624