From 81119e86bdf47f41f06218f91e52024bc4d00e7c Mon Sep 17 00:00:00 2001 From: Juraj Sloboda Date: Fri, 25 May 2018 14:02:20 +0200 Subject: [PATCH] Implement DHCPv6 PD client (VPP-718, VPP-1050) Change-Id: I72a1ccdfdd5573335ef78fc01d5268934c73bd31 Signed-off-by: Juraj Sloboda --- src/vlibapi/api_helper_macros.h | 3 +- src/vnet.am | 10 +- src/vnet/dhcp/dhcp.api | 116 ++- src/vnet/dhcp/dhcp6_packet.h | 108 ++- src/vnet/dhcp/dhcp6_pd_client_cp.api | 61 ++ src/vnet/dhcp/dhcp6_pd_client_cp.c | 1395 ++++++++++++++++++++++++++++++++++ src/vnet/dhcp/dhcp6_pd_client_dp.c | 1141 +++++++++++++++++++++++++++ src/vnet/dhcp/dhcp6_pd_client_dp.h | 130 ++++ src/vnet/dhcp/dhcp6_pd_doc.md | 86 +++ src/vnet/dhcp/dhcp6_proxy_node.c | 29 +- src/vnet/dhcp/dhcp_api.c | 9 +- src/vnet/ip/ip6_neighbor.c | 23 + src/vnet/ip/ip6_neighbor.h | 2 + src/vnet/vnet_all_api_h.h | 1 + test/test_dhcp6.py | 449 +++++++++++ test/vpp_papi_provider.py | 44 ++ 16 files changed, 3562 insertions(+), 45 deletions(-) create mode 100644 src/vnet/dhcp/dhcp6_pd_client_cp.api create mode 100644 src/vnet/dhcp/dhcp6_pd_client_cp.c create mode 100644 src/vnet/dhcp/dhcp6_pd_client_dp.c create mode 100644 src/vnet/dhcp/dhcp6_pd_client_dp.h create mode 100644 src/vnet/dhcp/dhcp6_pd_doc.md create mode 100644 test/test_dhcp6.py diff --git a/src/vlibapi/api_helper_macros.h b/src/vlibapi/api_helper_macros.h index de3c09bdb89..ce80f0e0e20 100644 --- a/src/vlibapi/api_helper_macros.h +++ b/src/vlibapi/api_helper_macros.h @@ -219,7 +219,8 @@ _(oam_events) \ _(bfd_events) \ _(wc_ip6_nd_events) \ _(wc_ip4_arp_events) \ -_(ip6_ra_events) +_(ip6_ra_events) \ +_(dhcp6_pd_reply_events) typedef struct { diff --git a/src/vnet.am b/src/vnet.am index 86f3ad00f33..2601f48ae4f 100644 --- a/src/vnet.am +++ b/src/vnet.am @@ -783,13 +783,19 @@ API_FILES += vnet/lisp-gpe/lisp_gpe.api libvnet_la_SOURCES += \ vnet/dhcp/client.c \ vnet/dhcp/dhcp_client_detect.c \ + vnet/dhcp/dhcp6_pd_client_dp.c \ + vnet/dhcp/dhcp6_pd_client_cp.c \ vnet/dhcp/dhcp_api.c nobase_include_HEADERS += \ vnet/dhcp/client.h \ - vnet/dhcp/dhcp.api.h + vnet/dhcp/dhcp6_pd_client_dp.h \ + vnet/dhcp/dhcp.api.h \ + vnet/dhcp/dhcp6_pd_client_cp.api.h -API_FILES += vnet/dhcp/dhcp.api +API_FILES += \ + vnet/dhcp/dhcp.api \ + vnet/dhcp/dhcp6_pd_client_cp.api ######################################## # DHCP proxy diff --git a/src/vnet/dhcp/dhcp.api b/src/vnet/dhcp/dhcp.api index 30ead320089..82c03fc33c8 100644 --- a/src/vnet/dhcp/dhcp.api +++ b/src/vnet/dhcp/dhcp.api @@ -13,7 +13,7 @@ * limitations under the License. */ -option version = "2.0.0"; +option version = "2.0.1"; /** \brief DHCP Proxy config add / del request @param client_index - opaque cookie to identify the sender @@ -186,6 +186,120 @@ manual_endian manual_print define dhcp_proxy_details vl_api_dhcp_server_t servers[count]; }; +/** \brief Enable/disable listening on DHCPv6 client port + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +autoreply define dhcp6_clients_enable_disable +{ + u32 client_index; + u32 context; + u8 enable; +}; + +/** \brief Struct representing DHCPv6 PD prefix + @param prefix - prefix + @param prefix_length - prefix length + @param valid_time - valid lifetime + @param preferred_time - preferred lifetime +*/ +typeonly define dhcp6_pd_prefix_info +{ + u8 prefix[16]; + u8 prefix_length; + u32 valid_time; + u32 preferred_time; +}; + +/** \brief Send DHCPv6 PD client message of specified type + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - index of TX interface + @param server_index - used to dentify DHCPv6 server, + unique for each DHCPv6 server on the link, + value obrtained from dhcp6_pd_reply_event API message, + use ~0 to send message to all DHCPv6 servers + @param irt - initial retransmission time + @param mrt - maximum retransmission time + @param mrc - maximum retransmission count + @param mrd - maximum retransmission duration + for sending the message + @param stop - if non-zero then stop resending the message, + otherwise start sending the message + @param msg_type - message type + @param T1 - value of T1 in IA_PD option + @param T2 - value of T2 in IA_PD option + @param n_prefixes - number of addresses in IA_PD option + @param prefixes - list of prefixes in IA_PD option +*/ +autoreply define dhcp6_pd_send_client_message +{ + u32 client_index; + u32 context; + u32 sw_if_index; + u32 server_index; + u32 irt; + u32 mrt; + u32 mrc; + u32 mrd; + u8 stop; + u8 msg_type; + u32 T1; + u32 T2; + u32 n_prefixes; + vl_api_dhcp6_pd_prefix_info_t prefixes[n_prefixes]; +}; + +service { + rpc want_dhcp6_pd_reply_events returns want_dhcp6_pd_reply_events_reply + events dhcp6_pd_reply_event; +}; + +/** \brief Register for DHCPv6 PD reply events + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param enable_disable - 1 => register for events, 0 => cancel registration + @param pid - sender's pid +*/ +autoreply define want_dhcp6_pd_reply_events +{ + u32 client_index; + u32 context; + u8 enable_disable; + u32 pid; +}; + +/** \brief Tell client about a DHCPv6 PD server reply event + @param client_index - opaque cookie to identify the sender + @param pid - client pid registered to receive notification + @param sw_if_index - index of RX interface + @param server_index - used to dentify DHCPv6 server, + unique for each DHCPv6 server on the link + @param msg_type - message type + @param T1 - value of T1 in IA_PD option + @param T2 - value of T2 in IA_PD option + @param inner_status_code - value of status code inside IA_PD option + @param status_code - value of the main status code of DHCPv6 message + @param preference - value of preference option in reply message + @param n_prefixes - number of prefixes in IA_PD option + @param prefixes - list of prefixes in IA_PD option +*/ +define dhcp6_pd_reply_event +{ + u32 client_index; + u32 pid; + u32 sw_if_index; + u32 server_index; + u8 msg_type; + u32 T1; + u32 T2; + u16 inner_status_code; + u16 status_code; + u8 preference; + u32 n_prefixes; + vl_api_dhcp6_pd_prefix_info_t prefixes[n_prefixes]; +}; + /* * Local Variables: * eval: (c-set-style "gnu") diff --git a/src/vnet/dhcp/dhcp6_packet.h b/src/vnet/dhcp/dhcp6_packet.h index 97a89385836..1926154db27 100644 --- a/src/vnet/dhcp/dhcp6_packet.h +++ b/src/vnet/dhcp/dhcp6_packet.h @@ -48,33 +48,42 @@ typedef enum dhcpv6_msg_type_ DHCPV6_MSG_RELAY_REPL = 13, } dhcpv6_msg_type_t; +/* Name, code, min payload length */ +#define dhcpv6_foreach_option \ + _(CLIENTID , 1 , 4 ) \ + _(SERVERID , 2 , 4 ) \ + _(IA_NA , 3 , 12) \ + _(IA_TA , 4 , 4 ) \ + _(IAADDR , 5 , 24) \ + _(ORO , 6 , 0 ) \ + _(PREFERENCE , 7 , 1 ) \ + _(ELAPSED_TIME , 8 , 2 ) \ + _(RELAY_MSG , 9 , 0 ) \ + _(AUTH , 11 , 11) \ + _(UNICAST , 12 , 16) \ + _(STATUS_CODE , 13 , 2 ) \ + _(RAPID_COMMIT , 14 , 0 ) \ + _(USER_CLASS , 15 , 0 ) \ + _(VENDOR_CLASS , 16 , 4 ) \ + _(VENDOR_OPTS , 17 , 4 ) \ + _(INTERFACE_ID , 18 , 0 ) \ + _(RECONF_MSG , 19 , 1 ) \ + _(RECONF_ACCEPT , 20 , 0 ) \ + _(DNS_SEARCH , 24 , 0 ) \ + _(IA_PD , 25 , 12) \ + _(IAPREFIX , 26 , 25) \ + _(REMOTEID , 37 , 4 ) \ + _(VSS , 68 , 1 ) \ + _(CLIENT_LINK_LAYER_ADDRESS, 79 , 2 ) + /* * DHCPv6 options types */ enum { - DHCPV6_OPTION_CLIENTID = 1, - DHCPV6_OPTION_SERVERID = 2, - DHCPV6_OPTION_IA_NA = 3, - DHCPV6_OPTION_IA_TA = 4, - DHCPV6_OPTION_IAADDR = 5, - DHCPV6_OPTION_ORO = 6, - DHCPV6_OPTION_PREFERENCE = 7, - DHCPV6_OPTION_ELAPSED_TIME = 8, - DHCPV6_OPTION_RELAY_MSG = 9, - DHCPV6_OPTION_AUTH = 11, - DHCPV6_OPTION_UNICAST = 12, - DHCPV6_OPTION_STATUS_CODE = 13, - DHCPV6_OPTION_RAPID_COMMIT = 14, - DHCPV6_OPTION_USER_CLASS = 15, - DHCPV6_OPTION_VENDOR_CLASS = 16, - DHCPV6_OPTION_VENDOR_OPTS = 17, - DHCPV6_OPTION_INTERFACE_ID = 18, // relay agent fills this - DHCPV6_OPTION_RECONF_MSG = 19, - DHCPV6_OPTION_RECONF_ACCEPT = 20, - DHCPV6_OPTION_REMOTEID = 37, // relay agent fills this - DHCPV6_OPTION_VSS = 68, // relay agent fills this - DHCPV6_OPTION_CLIENT_LINK_LAYER_ADDRESS = 79, +#define _(a,b,c) DHCPV6_OPTION_##a = b, + dhcpv6_foreach_option +#undef _ DHCPV6_OPTION_MAX }; @@ -89,6 +98,7 @@ enum DHCPV6_STATUS_NO_BINDING = 3, DHCPV6_STATUS_NOT_ONLINK = 4, DHCPV6_STATUS_USE_MULTICAST = 5, + DHCPV6_STATUS_NOPREFIX_AVAIL = 6, }; /* @@ -104,16 +114,11 @@ enum //Structure for DHCPv6 payload from client typedef struct dhcpv6_hdr_ { - union - { - u8 msg_type; //DHCP msg type - u32 xid; // transaction id - } u; + u8 msg_type; //DHCP msg type + u8 xid[3]; //Transaction id u8 data[0]; } dhcpv6_header_t; - - /* *INDENT-OFF* */ typedef CLIB_PACKED (struct dhcpv6_relay_ctx_ { dhcpv6_header_t *pkt; @@ -164,6 +169,8 @@ typedef enum dhcpv6_stats_drop_reason_ DHCPV6_RELAY_PKT_DROP_REPLY_FROM_CLIENT, } dhcpv6_stats_drop_reason_t; +#define dhcpv6_optlen(opt) clib_net_to_host_u16((opt)->length) + /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { u16 option; @@ -172,6 +179,13 @@ typedef CLIB_PACKED (struct { }) dhcpv6_option_t; /* *INDENT-ON* */ +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + dhcpv6_option_t opt; + u16 status_code; +}) dhcpv6_status_code_t; +/* *INDENT-ON* */ + /* *INDENT-OFF* */ typedef CLIB_PACKED (struct { dhcpv6_option_t opt; @@ -203,6 +217,42 @@ typedef CLIB_PACKED (struct { }) dhcpv6_client_mac_t; /* *INDENT-ON* */ +typedef CLIB_PACKED (struct + { + dhcpv6_option_t opt; u32 iaid; u32 t1; + u32 t2; + u8 data[0]; + }) dhcpv6_ia_header_t; + +typedef CLIB_PACKED (struct + { + dhcpv6_option_t opt; u32 preferred; u32 valid; u8 prefix; + ip6_address_t addr; + }) dhcpv6_ia_opt_pd_t; + +typedef CLIB_PACKED (struct + { + dhcpv6_option_t opt; + u16 options[0]; + }) dhcpv6_oro_t; + +typedef CLIB_PACKED (struct + { + dhcpv6_option_t opt; u16 elapsed_10ms; + }) dhcpv6_elapsed_t; + +typedef CLIB_PACKED (struct + { + dhcpv6_option_t opt; u16 duid_type; + u16 hardware_type; + }) dhcpv6_duid_t; + +typedef CLIB_PACKED (struct + { + dhcpv6_option_t opt; u16 status_code; + u8 message[0]; + }) dhcpv6_status_t; + #endif /* included_vnet_dhcp6_packet_h */ diff --git a/src/vnet/dhcp/dhcp6_pd_client_cp.api b/src/vnet/dhcp/dhcp6_pd_client_cp.api new file mode 100644 index 00000000000..43ed868e81e --- /dev/null +++ b/src/vnet/dhcp/dhcp6_pd_client_cp.api @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +option version = "1.0.0"; + +/** \brief Enable/disable DHCPv6 PD client on interface + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - interface to enable/disable client on + @param prefix_group - name of prefix group (relevant when 'enable' is 1) + @param enable - 1 to enable, 0 to disable +*/ +autoreply define dhcp6_pd_client_enable_disable +{ + u32 client_index; + u32 context; + u32 sw_if_index; + u8 prefix_group[64]; + u8 enable; +}; + +/** \brief Add/delete IPv6 address optionally using available prefix + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param sw_if_index - software interface index of interface + to add/delete address to/from + @param prefix_group - name of prefix group, + prefix_group[0] == '\0' means no prefix should be used + @param address - address or suffix to be used with a prefix + from selected group + @param prefix_length - subnet prefix for the address + @param is_add - 1 for add, 0 for remove +*/ +autoreply define ip6_add_del_address_using_prefix +{ + u32 client_index; + u32 context; + u32 sw_if_index; + u8 prefix_group[64]; + u8 address[16]; + u8 prefix_length; + u8 is_add; +}; + +/* + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/dhcp/dhcp6_pd_client_cp.c b/src/vnet/dhcp/dhcp6_pd_client_cp.c new file mode 100644 index 00000000000..ecb85e1f5a6 --- /dev/null +++ b/src/vnet/dhcp/dhcp6_pd_client_cp.c @@ -0,0 +1,1395 @@ +/* + * Copyright (c) 2018 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define vl_typedefs /* define message structures */ +#include +#undef vl_typedefs + +#define vl_endianfun /* define message structures */ +#include +#undef vl_endianfun + +#include + +#define foreach_dhcp6_pd_client_cp_msg \ +_(DHCP6_PD_CLIENT_ENABLE_DISABLE, dhcp6_pd_client_enable_disable) \ +_(IP6_ADD_DEL_ADDRESS_USING_PREFIX, ip6_add_del_address_using_prefix) + +#define vl_api_dhcp6_pd_client_enable_disable_t_print vl_noop_handler +#define vl_api_ip6_add_del_address_using_prefix_t_print vl_noop_handler + +typedef struct +{ + u32 prefix_group_index; + uword opaque_data; // used by prefix publisher + u32 TODO; + ip6_address_t prefix; + u8 prefix_length; + u32 preferred_lt; + u32 valid_lt; + f64 due_time; +} prefix_info_t; + +typedef struct +{ + u8 enabled; + u32 prefix_group_index; + u32 server_index; + u32 T1; + u32 T2; + f64 T1_due_time; + f64 T2_due_time; + u32 prefix_count; + u8 rebinding; +} client_state_t; + +typedef struct +{ + client_state_t *client_state_by_sw_if_index; + clib_bitmap_t *prefix_ownership_bitmap; + u32 n_clients; + f64 max_valid_due_time; + + /* convenience */ + vlib_main_t *vlib_main; + vnet_main_t *vnet_main; + api_main_t *api_main; + u32 node_index; +} dhcp6_pd_client_cp_main_t; + +static dhcp6_pd_client_cp_main_t dhcp6_pd_client_cp_main; + +typedef struct +{ + prefix_info_t *prefix_pool; + const u8 **prefix_group_name_by_index; +} ip6_prefix_main_t; + +static ip6_prefix_main_t ip6_prefix_main; + +typedef struct +{ + /* config */ + u32 sw_if_index; + u32 prefix_group_index; + ip6_address_t address; + u8 prefix_length; + + /* state */ + u8 configured_in_data_plane; +} ip6_address_info_t; + +typedef struct +{ + ip6_address_info_t *addresses; + u32 *active_prefix_index_by_prefix_group_index; +} ip6_address_with_prefix_main_t; + +static ip6_address_with_prefix_main_t ip6_address_with_prefix_main; + +enum +{ + DHCPV6_PD_EVENT_INTERRUPT, + DHCPV6_PD_EVENT_DISABLE, +}; + +static_always_inline u32 +active_prefix_index_by_prefix_group_index_get (u32 prefix_group_index) +{ + ip6_address_with_prefix_main_t *apm = &ip6_address_with_prefix_main; + + if (prefix_group_index >= + vec_len (apm->active_prefix_index_by_prefix_group_index)) + return ~0; + + return apm->active_prefix_index_by_prefix_group_index[prefix_group_index]; +} + +static_always_inline void +active_prefix_index_by_prefix_group_index_set (u32 prefix_group_index, + u32 prefix_index) +{ + ip6_address_with_prefix_main_t *apm = &ip6_address_with_prefix_main; + static const u32 empty = ~0; + + ASSERT (prefix_group_index != ~0); + + if (prefix_index == ~0 + && prefix_group_index >= + vec_len (apm->active_prefix_index_by_prefix_group_index)) + return; + + vec_validate_init_empty (apm->active_prefix_index_by_prefix_group_index, + prefix_group_index, empty); + apm->active_prefix_index_by_prefix_group_index[prefix_group_index] = + prefix_index; +} + +static_always_inline u8 +is_dhcpv6_pd_prefix (prefix_info_t * prefix_info) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + ip6_prefix_main_t *pm = &ip6_prefix_main; + u32 prefix_index; + + prefix_index = prefix_info - pm->prefix_pool; + return clib_bitmap_get (rm->prefix_ownership_bitmap, prefix_index); +} + +static_always_inline void +set_is_dhcpv6_pd_prefix (prefix_info_t * prefix_info, u8 value) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + ip6_prefix_main_t *pm = &ip6_prefix_main; + u32 prefix_index; + + prefix_index = prefix_info - pm->prefix_pool; + rm->prefix_ownership_bitmap = + clib_bitmap_set (rm->prefix_ownership_bitmap, prefix_index, value); +} + +static void +cp_ip6_address_prefix_add_del_handler (u32 prefix_index, u8 is_add); + +static void +notify_prefix_add_del (u32 prefix_index, u8 is_add) +{ + // TODO: use registries + cp_ip6_address_prefix_add_del_handler (prefix_index, is_add); +} + +static void +send_client_message_start_stop (u32 sw_if_index, u32 server_index, + u8 msg_type, prefix_info_t * prefix_list, + u8 start) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + dhcp6_pd_send_client_message_params_t params = { 0, }; + dhcp6_pd_send_client_message_params_prefix_t *prefixes = 0, *pref; + u32 i; + + ASSERT (sw_if_index < vec_len (rm->client_state_by_sw_if_index) && + rm->client_state_by_sw_if_index[sw_if_index].enabled); + client_state_t *client_state = + &rm->client_state_by_sw_if_index[sw_if_index]; + + params.sw_if_index = sw_if_index; + params.server_index = server_index; + params.msg_type = msg_type; + if (start) + { + if (msg_type == DHCPV6_MSG_SOLICIT) + { + params.irt = 1; + params.mrt = 120; + } + else if (msg_type == DHCPV6_MSG_REQUEST) + { + params.irt = 1; + params.mrt = 30; + params.mrc = 10; + } + else if (msg_type == DHCPV6_MSG_RENEW) + { + params.irt = 10; + params.mrt = 600; + f64 current_time = vlib_time_now (rm->vlib_main); + i32 diff_time = client_state->T2 - current_time; + if (diff_time < 0) + diff_time = 0; + params.mrd = diff_time; + } + else if (msg_type == DHCPV6_MSG_REBIND) + { + params.irt = 10; + params.mrt = 600; + f64 current_time = vlib_time_now (rm->vlib_main); + i32 diff_time = rm->max_valid_due_time - current_time; + if (diff_time < 0) + diff_time = 0; + params.mrd = diff_time; + } + else if (msg_type == DHCPV6_MSG_RELEASE) + { + params.mrc = 1; + } + } + + params.T1 = 0; + params.T2 = 0; + if (vec_len (prefix_list) != 0) + vec_validate (prefixes, vec_len (prefix_list) - 1); + for (i = 0; i < vec_len (prefix_list); i++) + { + prefix_info_t *prefix = &prefix_list[i]; + pref = &prefixes[i]; + pref->valid_lt = prefix->valid_lt; + pref->preferred_lt = prefix->preferred_lt; + pref->prefix = prefix->prefix; + pref->prefix_length = prefix->prefix_length; + } + params.prefixes = prefixes; + + dhcp6_pd_send_client_message (rm->vlib_main, sw_if_index, !start, ¶ms); + + vec_free (params.prefixes); +} + +static void interrupt_process (void); + +static u32 +ip6_enable (u32 sw_if_index) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + clib_error_t *rv; + + rv = enable_ip6_interface (rm->vlib_main, sw_if_index); + + return rv != 0; +} + +static u8 +ip6_prefixes_equal (ip6_address_t * prefix1, ip6_address_t * prefix2, u8 len) +{ + if (len >= 64) + { + if (prefix1->as_u64[0] != prefix2->as_u64[0]) + return 0; + if (len == 64) + return 1; + return clib_net_to_host_u64 (prefix1->as_u64[1]) >> (128 - len) == + clib_net_to_host_u64 (prefix2->as_u64[1]) >> (128 - len); + } + return clib_net_to_host_u64 (prefix1->as_u64[0]) >> (64 - len) == + clib_net_to_host_u64 (prefix2->as_u64[0]) >> (64 - len); +} + +static clib_error_t * +dhcp6_pd_reply_event_handler (vl_api_dhcp6_pd_reply_event_t * mp) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + ip6_prefix_main_t *pm = &ip6_prefix_main; + vlib_main_t *vm = rm->vlib_main; + client_state_t *client_state; + ip6_address_t *prefix; + u32 sw_if_index; + u32 n_prefixes; + vl_api_dhcp6_pd_prefix_info_t *api_prefix; + u32 inner_status_code; + u32 status_code; + u32 server_index; + f64 current_time; + clib_error_t *error = 0; + u32 i; + + current_time = vlib_time_now (vm); + + sw_if_index = ntohl (mp->sw_if_index); + + if (sw_if_index >= vec_len (rm->client_state_by_sw_if_index)) + return 0; + + client_state = &rm->client_state_by_sw_if_index[sw_if_index]; + + if (!client_state->enabled) + return 0; + + server_index = ntohl (mp->server_index); + + n_prefixes = ntohl (mp->n_prefixes); + + inner_status_code = ntohs (mp->inner_status_code); + status_code = ntohs (mp->status_code); + + if (mp->msg_type == DHCPV6_MSG_ADVERTISE + && client_state->server_index == ~0) + { + prefix_info_t *prefix_list = 0, *prefix_info; + u8 prefix_length; + + if (inner_status_code == DHCPV6_STATUS_NOPREFIX_AVAIL) + { + clib_warning + ("Advertise message arrived with NoPrefixAvail status code"); + return 0; + } + + if (n_prefixes > 0) + vec_validate (prefix_list, n_prefixes - 1); + for (i = 0; i < n_prefixes; i++) + { + api_prefix = &mp->prefixes[i]; + prefix = (ip6_address_t *) api_prefix->prefix; + prefix_length = api_prefix->prefix_length; + + prefix_info = &prefix_list[i]; + prefix_info->prefix = *prefix; + prefix_info->prefix_length = prefix_length; + prefix_info->preferred_lt = 0; + prefix_info->valid_lt = 0; + } + + client_state->server_index = server_index; + + send_client_message_start_stop (sw_if_index, server_index, + DHCPV6_MSG_REQUEST, prefix_list, 1); + vec_free (prefix_list); + } + + if (mp->msg_type != DHCPV6_MSG_REPLY) + return 0; + + if (!client_state->rebinding && client_state->server_index != server_index) + { + clib_warning ("Reply message arrived with Server ID different " + "from that in Request of Renew message"); + return 0; + } + + if (inner_status_code == DHCPV6_STATUS_NOPREFIX_AVAIL) + { + clib_warning ("Reply message arrived with NoPrefixAvail status code"); + if (n_prefixes > 0) + { + clib_warning + ("Invalid Reply message arrived: It contains NoPrefixAvail " + "status code but also contains prefixes"); + return 0; + } + } + + if (status_code == DHCPV6_STATUS_UNSPEC_FAIL) + { + clib_warning ("Reply message arrived with UnspecFail status code"); + return 0; + } + + send_client_message_start_stop (sw_if_index, server_index, + mp->msg_type, 0, 0); + + for (i = 0; i < n_prefixes; i++) + { + prefix_info_t *prefix_info = 0; + u8 prefix_length; + u32 valid_time; + u32 preferred_time; + + api_prefix = &mp->prefixes[i]; + + prefix = (ip6_address_t *) api_prefix->prefix; + prefix_length = api_prefix->prefix_length; + + if (ip6_address_is_link_local_unicast (prefix)) + continue; + + valid_time = ntohl (api_prefix->valid_time); + preferred_time = ntohl (api_prefix->preferred_time); + prefix_length = api_prefix->prefix_length; + + if (preferred_time > valid_time) + continue; + + u8 address_prefix_present = 0; + /* *INDENT-OFF* */ + pool_foreach (prefix_info, pm->prefix_pool, + ({ + if (is_dhcpv6_pd_prefix (prefix_info) && + prefix_info->opaque_data == sw_if_index && + prefix_info->prefix_length == prefix_length && + ip6_prefixes_equal (&prefix_info->prefix, prefix, prefix_length)) + { + address_prefix_present = 1; + goto prefix_pool_foreach_out; + } + })); + /* *INDENT-ON* */ + prefix_pool_foreach_out: + + if (address_prefix_present) + { + prefix_info->preferred_lt = preferred_time; + prefix_info->valid_lt = valid_time; + prefix_info->due_time = current_time + valid_time; + continue; + } + + if (valid_time == 0) + continue; + + pool_get (pm->prefix_pool, prefix_info); + prefix_info->prefix_group_index = client_state->prefix_group_index; + set_is_dhcpv6_pd_prefix (prefix_info, 1); + prefix_info->opaque_data = sw_if_index; + prefix_info->prefix_length = prefix_length; + prefix_info->prefix = *prefix; + prefix_info->preferred_lt = preferred_time; + prefix_info->valid_lt = valid_time; + prefix_info->due_time = current_time + valid_time; + rm->client_state_by_sw_if_index[sw_if_index].prefix_count++; + + u32 prefix_index = prefix_info - pm->prefix_pool; + notify_prefix_add_del (prefix_index, 1); + } + + client_state->server_index = server_index; + client_state->T1 = ntohl (mp->T1); + client_state->T2 = ntohl (mp->T2); + if (client_state->T1 != 0) + client_state->T1_due_time = current_time + client_state->T1; + if (client_state->T2 != 0) + client_state->T2_due_time = current_time + client_state->T2; + client_state->rebinding = 0; + + interrupt_process (); + + return error; +} + +static prefix_info_t * +create_prefix_list (u32 sw_if_index) +{ + ip6_prefix_main_t *pm = &ip6_prefix_main; + prefix_info_t *prefix_info, *prefix_list = 0;; + + /* *INDENT-OFF* */ + pool_foreach (prefix_info, pm->prefix_pool, + ({ + if (is_dhcpv6_pd_prefix (prefix_info) && + prefix_info->opaque_data == sw_if_index) + { + u32 pos = vec_len (prefix_list); + vec_validate (prefix_list, pos); + clib_memcpy (&prefix_list[pos], prefix_info, sizeof (*prefix_info)); + } + })); + /* *INDENT-ON* */ + + return prefix_list; +} + +VNET_DHCP6_PD_REPLY_EVENT_FUNCTION (dhcp6_pd_reply_event_handler); + +static uword +dhcp6_pd_client_cp_process (vlib_main_t * vm, vlib_node_runtime_t * rt, + vlib_frame_t * f) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + ip6_prefix_main_t *pm = &ip6_prefix_main; + prefix_info_t *prefix_info; + client_state_t *client_state; + f64 sleep_time = 1e9; + f64 current_time; + f64 due_time; + uword event_type; + uword *event_data = 0; + int i; + + while (1) + { + vlib_process_wait_for_event_or_clock (vm, sleep_time); + event_type = vlib_process_get_events (vm, &event_data); + vec_reset_length (event_data); + + if (event_type == DHCPV6_PD_EVENT_DISABLE) + { + vlib_node_set_state (vm, rm->node_index, VLIB_NODE_STATE_DISABLED); + sleep_time = 1e9; + continue; + } + + current_time = vlib_time_now (vm); + do + { + due_time = current_time + 1e9; + /* *INDENT-OFF* */ + pool_foreach (prefix_info, pm->prefix_pool, + ({ + if (is_dhcpv6_pd_prefix (prefix_info)) + { + if (prefix_info->due_time > current_time) + { + if (prefix_info->due_time < due_time) + due_time = prefix_info->due_time; + } + else + { + u32 prefix_index = prefix_info - pm->prefix_pool; + notify_prefix_add_del (prefix_index, 0); + u32 sw_if_index = prefix_info->opaque_data; + set_is_dhcpv6_pd_prefix (prefix_info, 0); + pool_put (pm->prefix_pool, prefix_info); + client_state = &rm->client_state_by_sw_if_index[sw_if_index]; + if (--client_state->prefix_count == 0) + { + client_state->rebinding = 0; + client_state->server_index = ~0; + send_client_message_start_stop (sw_if_index, ~0, + DHCPV6_MSG_SOLICIT, + 0, 1); + } + } + } + })); + /* *INDENT-ON* */ + for (i = 0; i < vec_len (rm->client_state_by_sw_if_index); i++) + { + client_state_t *cs = &rm->client_state_by_sw_if_index[i]; + if (cs->enabled && cs->server_index != ~0) + { + if (cs->T2_due_time > current_time) + { + if (cs->T2_due_time < due_time) + due_time = cs->T2_due_time; + if (cs->T1_due_time > current_time) + { + if (cs->T1_due_time < due_time) + due_time = cs->T1_due_time; + } + else + { + cs->T1_due_time = DBL_MAX; + prefix_info_t *prefix_list; + prefix_list = create_prefix_list (i); + send_client_message_start_stop (i, cs->server_index, + DHCPV6_MSG_RENEW, + prefix_list, 1); + vec_free (prefix_list); + } + } + else + { + cs->T2_due_time = DBL_MAX; + prefix_info_t *prefix_list; + prefix_list = create_prefix_list (i); + cs->rebinding = 1; + send_client_message_start_stop (i, ~0, + DHCPV6_MSG_REBIND, + prefix_list, 1); + vec_free (prefix_list); + } + } + } + current_time = vlib_time_now (vm); + } + while (due_time < current_time); + + sleep_time = due_time - current_time; + } + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dhcp6_pd_client_cp_process_node) = { + .function = dhcp6_pd_client_cp_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "dhcp6-pd-client-cp-process", +}; +/* *INDENT-ON* */ + +static void +interrupt_process (void) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + vlib_main_t *vm = rm->vlib_main; + + vlib_process_signal_event (vm, dhcp6_pd_client_cp_process_node.index, + DHCPV6_PD_EVENT_INTERRUPT, 0); +} + +static void +disable_process (void) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + vlib_main_t *vm = rm->vlib_main; + + vlib_process_signal_event (vm, dhcp6_pd_client_cp_process_node.index, + DHCPV6_PD_EVENT_DISABLE, 0); +} + +static void +enable_process (void) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + vlib_main_t *vm = rm->vlib_main; + vlib_node_t *node; + + node = vec_elt (vm->node_main.nodes, rm->node_index); + + vlib_node_set_state (vm, rm->node_index, VLIB_NODE_STATE_POLLING); + vlib_start_process (vm, node->runtime_index); +} + +static u32 +cp_ip6_construct_address (ip6_address_info_t * address_info, u32 prefix_index, + ip6_address_t * r_addr) +{ + ip6_prefix_main_t *pm = &ip6_prefix_main; + prefix_info_t *prefix; + u64 mask, addr0, pref; + + addr0 = clib_net_to_host_u64 (address_info->address.as_u64[0]); + prefix = &pm->prefix_pool[prefix_index]; + if (prefix->prefix_length > 64) + { + clib_warning ("Prefix length is bigger that 64 bits"); + return 1; + } + mask = (1 << (64 - prefix->prefix_length)) - 1; + addr0 &= mask; + pref = clib_host_to_net_u64 (prefix->prefix.as_u64[0]); + pref &= ~mask; + addr0 |= pref; + r_addr->as_u64[0] = clib_host_to_net_u64 (addr0); + r_addr->as_u64[1] = address_info->address.as_u64[1]; + + return 0; +} + +static void +cp_ip6_address_add_del_now (ip6_address_info_t * address_info, u8 is_add) +{ + vlib_main_t *vm = vlib_get_main (); + u32 prefix_index; + ip6_address_t addr; + clib_error_t *error; + + if (address_info->prefix_group_index != ~0) + prefix_index = + active_prefix_index_by_prefix_group_index_get + (address_info->prefix_group_index); + else + prefix_index = ~0; + + if (is_add && !address_info->configured_in_data_plane) + { + if (prefix_index != ~0) + { + if (cp_ip6_construct_address (address_info, prefix_index, &addr) != + 0) + return; + error = + ip6_add_del_interface_address (vm, address_info->sw_if_index, + &addr, address_info->prefix_length, + 0 /* add */ ); + if (error) + clib_warning ("Failed adding IPv6 address: %U", + format_clib_error, error); + else + address_info->configured_in_data_plane = 1; + } + else + { + if (address_info->prefix_group_index == ~0) + { + error = + ip6_add_del_interface_address (vm, address_info->sw_if_index, + &address_info->address, + address_info->prefix_length, + 0 /* add */ ); + if (error) + clib_warning ("Failed adding IPv6 address: %U", + format_clib_error, error); + else + address_info->configured_in_data_plane = 1; + } + } + } + else if (!is_add && address_info->configured_in_data_plane) + { + if (prefix_index == ~0) + { + if (address_info->prefix_group_index == ~0) + { + error = + ip6_add_del_interface_address (vm, address_info->sw_if_index, + &address_info->address, + address_info->prefix_length, + 1 /* del */ ); + if (error) + clib_warning ("Failed deleting IPv6 address: %U", + format_clib_error, error); + address_info->configured_in_data_plane = 0; + } + else + clib_warning ("Deleting address with prefix " + "but active prefix index is not set"); + } + else + { + if (cp_ip6_construct_address (address_info, prefix_index, &addr) != + 0) + return; + error = + ip6_add_del_interface_address (vm, address_info->sw_if_index, + &addr, address_info->prefix_length, + 1 /* del */ ); + if (error) + clib_warning ("Failed deleting IPv6 address: %U", + format_clib_error, error); + address_info->configured_in_data_plane = 0; + } + } +} + +static u32 +cp_ip6_address_find_new_active_prefix (u32 prefix_group_index, + u32 ignore_prefix_index) +{ + ip6_prefix_main_t *pm = &ip6_prefix_main; + prefix_info_t *prefix_info; + + /* *INDENT-OFF* */ + pool_foreach (prefix_info, pm->prefix_pool, + ({ + if (prefix_info->prefix_group_index == prefix_group_index && + prefix_info - pm->prefix_pool != ignore_prefix_index) + return prefix_info - pm->prefix_pool; + })); + /* *INDENT-ON* */ + return ~0; +} + +static void +cp_ip6_address_prefix_add_del_handler (u32 prefix_index, u8 is_add) +{ + ip6_address_with_prefix_main_t *apm = &ip6_address_with_prefix_main; + ip6_prefix_main_t *pm = &ip6_prefix_main; + ip6_address_info_t *address_info; + prefix_info_t *prefix; + u32 new_prefix_index; + u32 prefix_group_index; + u32 i; + + prefix = &pm->prefix_pool[prefix_index]; + prefix_group_index = prefix->prefix_group_index; + + if (is_add) + { + if (active_prefix_index_by_prefix_group_index_get + (prefix_group_index) == ~0) + { + active_prefix_index_by_prefix_group_index_set + (prefix_group_index, prefix_index); + for (i = 0; i < vec_len (apm->addresses); i++) + { + address_info = &apm->addresses[i]; + if (address_info->prefix_group_index == prefix_group_index) + cp_ip6_address_add_del_now (address_info, 1 /* add */ ); + } + } + } + else + { + if (active_prefix_index_by_prefix_group_index_get + (prefix_group_index) == prefix_index) + { + for (i = 0; i < vec_len (apm->addresses); i++) + { + address_info = &apm->addresses[i]; + if (address_info->prefix_group_index == prefix_group_index) + cp_ip6_address_add_del_now (address_info, 0 /* del */ ); + } + active_prefix_index_by_prefix_group_index_set + (prefix_group_index, ~0); + new_prefix_index = + cp_ip6_address_find_new_active_prefix (prefix_group_index, + prefix_index); + if (new_prefix_index != ~0) + { + active_prefix_index_by_prefix_group_index_set + (prefix_group_index, new_prefix_index); + for (i = 0; i < vec_len (apm->addresses); i++) + { + address_info = &apm->addresses[i]; + if (address_info->prefix_group_index == prefix_group_index) + cp_ip6_address_add_del_now (address_info, 1 /* add */ ); + } + } + } + } +} + +static u32 +prefix_group_find_or_create (const u8 * name, u8 create) +{ + ip6_prefix_main_t *pm = &ip6_prefix_main; + u32 free_index = ~0; + u8 *name_dup; + u32 i; + + for (i = 0; i < vec_len (pm->prefix_group_name_by_index); i++) + { + if (pm->prefix_group_name_by_index[i] == 0) + free_index = i; + else if (0 == + strcmp ((const char *) pm->prefix_group_name_by_index[i], + (const char *) name)) + return i; + } + if (!create) + return ~0; + name_dup = (u8 *) strdup ((const char *) name); + if (free_index != ~0) + { + pm->prefix_group_name_by_index[free_index] = name_dup; + return free_index; + } + else + { + vec_add1 (pm->prefix_group_name_by_index, name_dup); + return vec_len (pm->prefix_group_name_by_index) - 1; + } +} + +static int +cp_ip6_address_add_del (u32 sw_if_index, const u8 * prefix_group, + ip6_address_t address, u8 prefix_length, u8 is_add) +{ + + ip6_address_with_prefix_main_t *apm = &ip6_address_with_prefix_main; + ip6_address_info_t *address_info; + u32 prefix_group_index; + u32 n; + + if (prefix_group != 0 && prefix_group[0] != '\0') + { + if (strnlen ((const char *) prefix_group, 64) == 64) + return VNET_API_ERROR_INVALID_VALUE; + + prefix_group_index = prefix_group_find_or_create (prefix_group, 1); + } + else + prefix_group_index = ~0; + + n = vec_len (apm->addresses); + + vec_foreach (address_info, apm->addresses) + { + if (address_info->sw_if_index == sw_if_index && + address_info->prefix_group_index == prefix_group_index && + address_info->prefix_length == prefix_length && + 0 == memcmp (&address_info->address, &address, 16)) + { + if (is_add) + return VNET_API_ERROR_DUPLICATE_IF_ADDRESS; + cp_ip6_address_add_del_now (address_info, 0 /* del */ ); + *address_info = apm->addresses[n - 1]; + _vec_len (apm->addresses) = n - 1; + return 0; + } + } + + if (!is_add) + return VNET_API_ERROR_ADDRESS_NOT_FOUND_FOR_INTERFACE; + + vec_validate (apm->addresses, n); + address_info = &apm->addresses[n]; + address_info->sw_if_index = sw_if_index; + address_info->prefix_group_index = prefix_group_index; + address_info->address = address; + address_info->prefix_length = prefix_length; + cp_ip6_address_add_del_now (address_info, 1 /* add */ ); + + return 0; +} + +static void + vl_api_ip6_add_del_address_using_prefix_t_handler + (vl_api_ip6_add_del_address_using_prefix_t * mp) +{ + vl_api_ip6_add_del_address_using_prefix_reply_t *rmp; + u32 sw_if_index; + ip6_address_t address; + u8 prefix_length; + int rv = 0; + + sw_if_index = ntohl (mp->sw_if_index); + if (!vnet_sw_if_index_is_api_valid (sw_if_index)) + { + rv = VNET_API_ERROR_INVALID_SW_IF_INDEX; + goto bad_sw_if_index; + } + + memcpy (address.as_u8, mp->address, 16); + prefix_length = mp->prefix_length; + + rv = + cp_ip6_address_add_del (sw_if_index, mp->prefix_group, address, + prefix_length, mp->is_add); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_SW_INTERFACE_SET_TABLE_REPLY); +} + +static clib_error_t * +cp_ip6_address_add_del_command_function (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + vnet_main_t *vnm = vnet_get_main (); + clib_error_t *error = 0; + u32 sw_if_index = ~0; + u8 *prefix_group = 0; + ip6_address_t address; + u32 prefix_length; + u8 address_set = 0; + u8 add = 1; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat + (input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index)); + else if (unformat (input, "prefix group %s", &prefix_group)); + else + if (unformat + (input, "%U/%d", unformat_ip6_address, &address, &prefix_length)) + address_set = 1; + else if (unformat (input, "del")) + add = 0; + else + { + error = clib_error_return (0, "unexpected input `%U'", + format_unformat_error, input); + goto done; + } + } + + if (sw_if_index == ~0) + error = clib_error_return (0, "Missing sw_if_index"); + else if (address_set == 0) + error = clib_error_return (0, "Missing address"); + else + { + if (cp_ip6_address_add_del + (sw_if_index, prefix_group, address, prefix_length, add) != 0) + error = clib_error_return (0, "Error adding or removing address"); + } + +done: + return error; +} + +/*? + * This command is used to add/delete IPv6 address + * potentially using available prefix from specified prefix group + * + * @cliexpar + * @parblock + * Example of how to add IPv6 address: + * @cliexcmd{set ip6 address GigabitEthernet2/0/0 + * prefix group my-prefix-group ::7/64} + * Example of how to delete IPv6 address: + * @cliexcmd{set ip6 address GigabitEthernet2/0/0 + * prefix group my-prefix-group ::7/64 del} + * @endparblock +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (ip6_address_add_del_command, static) = { + .path = "set ip6 address", + .short_help = "set ip6 address [prefix group ] " + "
[del]", + .function = cp_ip6_address_add_del_command_function, +}; +/* *INDENT-ON* */ + +static clib_error_t * +cp_ip6_addresses_show_command_function (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + ip6_address_with_prefix_main_t *apm = &ip6_address_with_prefix_main; + ip6_prefix_main_t *pm = &ip6_prefix_main; + ip6_address_info_t *address_info; + const u8 *prefix_group; + clib_error_t *error = 0; + int i; + + for (i = 0; i < vec_len (apm->addresses); i++) + { + address_info = &apm->addresses[i]; + if (address_info->prefix_group_index == ~0) + prefix_group = (const u8 *) "NONE"; + else + prefix_group = + pm->prefix_group_name_by_index[address_info->prefix_group_index]; + vlib_cli_output (vm, + "sw_if_index: %u, prefix_group: %s, address: %U/%d", + address_info->sw_if_index, prefix_group, + format_ip6_address, &address_info->address, + address_info->prefix_length); + } + + return error; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (ip6_addresses_show_command, static) = { + .path = "show ip6 addresses", + .short_help = "show ip6 addresses", + .function = cp_ip6_addresses_show_command_function, +}; +/* *INDENT-ON* */ + +static clib_error_t * +cp_ip6_prefixes_show_command_function (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + ip6_prefix_main_t *pm = &ip6_prefix_main; + clib_error_t *error = 0; + prefix_info_t *prefix_info; + const u8 *prefix_group; + f64 current_time = vlib_time_now (vm); + + /* *INDENT-OFF* */ + pool_foreach (prefix_info, pm->prefix_pool, + ({ + prefix_group = + pm->prefix_group_name_by_index[prefix_info->prefix_group_index]; + vlib_cli_output (vm, "opaque_data: %lu, prefix: %U/%d, prefix group: %s, " + "preferred lifetime: %u, valid lifetime: %u " + "(%f remaining)", + prefix_info->opaque_data, format_ip6_address, + &prefix_info->prefix, prefix_info->prefix_length, + prefix_group, + prefix_info->preferred_lt, prefix_info->valid_lt, + prefix_info->due_time - current_time); + })); + /* *INDENT-ON* */ + + return error; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (ip6_prefixes_show_command, static) = { + .path = "show ip6 prefixes", + .short_help = "show ip6 prefixes", + .function = cp_ip6_prefixes_show_command_function, +}; +/* *INDENT-ON* */ + +static clib_error_t * +ip6_pd_clients_show_command_function (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + ip6_prefix_main_t *pm = &ip6_prefix_main; + clib_error_t *error = 0; + client_state_t *cs; + f64 current_time = vlib_time_now (vm); + const u8 *prefix_group; + char buf1[256]; + char buf2[256]; + const char *rebinding; + u32 i; + + for (i = 0; i < vec_len (rm->client_state_by_sw_if_index); i++) + { + cs = &rm->client_state_by_sw_if_index[i]; + if (cs->enabled) + { + if (cs->T1_due_time != DBL_MAX && cs->T1_due_time > current_time) + { + sprintf (buf1, "%u remaining", + (u32) round (cs->T1_due_time - current_time)); + } + else + sprintf (buf1, "timeout"); + if (cs->T2_due_time != DBL_MAX && cs->T2_due_time > current_time) + sprintf (buf2, "%u remaining", + (u32) round (cs->T2_due_time - current_time)); + else + sprintf (buf2, "timeout"); + if (cs->rebinding) + rebinding = ", REBINDING"; + else + rebinding = ""; + prefix_group = + pm->prefix_group_name_by_index[cs->prefix_group_index]; + if (cs->T1) + vlib_cli_output (vm, + "sw_if_index: %u, prefix group: %s, T1: %u (%s), " + "T2: %u (%s), server index: %d%s", i, + prefix_group, cs->T1, buf1, cs->T2, buf2, + cs->server_index, rebinding); + else + vlib_cli_output (vm, "sw_if_index: %u, prefix group: %s%s", i, + prefix_group, rebinding); + } + } + + return error; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (ip6_pd_clients_show_command, static) = { + .path = "show ip6 pd clients", + .short_help = "show ip6 pd clients", + .function = ip6_pd_clients_show_command_function, +}; +/* *INDENT-ON* */ + +static int +dhcp6_pd_client_enable_disable (u32 sw_if_index, const u8 * prefix_group, + u8 enable) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + ip6_prefix_main_t *pm = &ip6_prefix_main; + vnet_main_t *vnm = rm->vnet_main; + client_state_t *client_state; + static client_state_t empty_config = { + 0 + }; + prefix_info_t *prefix_info; + prefix_info_t *prefix_list = 0; + u32 prefix_group_index; + + if (!vnet_sw_interface_is_api_valid (vnm, sw_if_index)) + { + clib_warning ("Invalid sw_if_index"); + return VNET_API_ERROR_INVALID_VALUE; + } + + vec_validate_init_empty (rm->client_state_by_sw_if_index, sw_if_index, + empty_config); + client_state = &rm->client_state_by_sw_if_index[sw_if_index]; + + u8 old_enabled = client_state->enabled; + + if (enable) + { + if (strnlen ((const char *) prefix_group, 64) == 64 + || prefix_group[0] == '\0') + return VNET_API_ERROR_INVALID_VALUE; + prefix_group_index = + prefix_group_find_or_create (prefix_group, !old_enabled); + if (old_enabled + && prefix_group_index != client_state->prefix_group_index) + return VNET_API_ERROR_INVALID_VALUE; + } + + if (!old_enabled && enable) + { + client_state->enabled = 1; + client_state->prefix_group_index = prefix_group_index; + ASSERT (client_state->prefix_group_index != ~0); + client_state->server_index = ~0; + + rm->n_clients++; + if (rm->n_clients == 1) + { + enable_process (); + dhcp6_clients_enable_disable (1); + } + + ip6_enable (sw_if_index); + send_client_message_start_stop (sw_if_index, ~0, DHCPV6_MSG_SOLICIT, + 0, 1); + } + else if (old_enabled && !enable) + { + send_client_message_start_stop (sw_if_index, ~0, ~0, 0, 0); + + rm->n_clients--; + if (rm->n_clients == 0) + { + dhcp6_clients_enable_disable (0); + disable_process (); + } + + vec_validate (prefix_list, 0); + + /* *INDENT-OFF* */ + pool_foreach (prefix_info, pm->prefix_pool, + ({ + if (is_dhcpv6_pd_prefix (prefix_info) && + prefix_info->opaque_data == sw_if_index) + { + ASSERT (sw_if_index < vec_len (rm->client_state_by_sw_if_index) && + rm->client_state_by_sw_if_index[sw_if_index].enabled); + client_state_t *client_state = + &rm->client_state_by_sw_if_index[sw_if_index]; + prefix_list[0] = *prefix_info; + send_client_message_start_stop (sw_if_index, + client_state->server_index, + DHCPV6_MSG_RELEASE, prefix_list, + 1); + u32 prefix_index = prefix_info - pm->prefix_pool; + notify_prefix_add_del (prefix_index, 0); + set_is_dhcpv6_pd_prefix (prefix_info, 0); + pool_put (pm->prefix_pool, prefix_info); + } + })); + /* *INDENT-ON* */ + + vec_free (prefix_list); + + memset (client_state, 0, sizeof (*client_state)); + } + + return 0; +} + +static clib_error_t * +dhcp6_pd_client_enable_disable_command_fn (vlib_main_t * + vm, + unformat_input_t + * input, vlib_cli_command_t * cmd) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + vnet_main_t *vnm = rm->vnet_main; + clib_error_t *error = 0; + u8 *prefix_group = 0; + u32 sw_if_index = ~0; + u8 enable = 1; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat + (input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index)) + ; + else if (unformat (input, "prefix group %s", &prefix_group)); + else if (unformat (input, "disable")) + enable = 0; + else + { + error = clib_error_return (0, "unexpected input `%U'", + format_unformat_error, input); + goto done; + } + } + + if (prefix_group == 0 && enable) + error = clib_error_return (0, "Prefix group must be set when enabling"); + else if (sw_if_index != ~0) + { + if (dhcp6_pd_client_enable_disable (sw_if_index, prefix_group, enable) + != 0) + error = clib_error_return (0, "Invalid sw_if_index or prefix group"); + } + else + error = clib_error_return (0, "Missing sw_if_index"); + +done: + vec_free (prefix_group); + + return error; +} + +/*? + * This command is used to enable/disable DHCPv6 PD client + * on particular interface. + * + * @cliexpar + * @parblock + * Example of how to enable DHCPv6 PD client: + * @cliexcmd{dhcp6 pd client GigabitEthernet2/0/0 prefix group my-pd-group} + * Example of how to disable DHCPv6 PD client: + * @cliexcmd{dhcp6 pd client GigabitEthernet2/0/0 disable} + * @endparblock +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcp6_pd_client_enable_disable_command, static) = { + .path = "dhcp6 pd client", + .short_help = "dhcp6 pd client (prefix group | disable)", + .function = dhcp6_pd_client_enable_disable_command_fn, +}; +/* *INDENT-ON* */ + +static void + vl_api_dhcp6_pd_client_enable_disable_t_handler + (vl_api_dhcp6_pd_client_enable_disable_t * mp) +{ + vl_api_dhcp6_pd_client_enable_disable_reply_t *rmp; + u32 sw_if_index; + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + sw_if_index = ntohl (mp->sw_if_index); + + rv = + dhcp6_pd_client_enable_disable (sw_if_index, mp->prefix_group, + mp->enable); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_SW_INTERFACE_SET_TABLE_REPLY); +} + +#define vl_msg_name_crc_list +#include +#undef vl_msg_name_crc_list + +static void +setup_message_id_table (api_main_t * am) +{ +#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id); + foreach_vl_msg_name_crc_dhcp6_pd_client_cp; +#undef _ +} + +static clib_error_t * +dhcp_pd_client_cp_init (vlib_main_t * vm) +{ + dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main; + api_main_t *am = &api_main; + + rm->vlib_main = vm; + rm->vnet_main = vnet_get_main (); + rm->api_main = am; + rm->node_index = dhcp6_pd_client_cp_process_node.index; + +#define _(N,n) \ + vl_msg_api_set_handlers(VL_API_##N, #n, \ + vl_api_##n##_t_handler, \ + vl_noop_handler, \ + vl_api_##n##_t_endian, \ + vl_api_##n##_t_print, \ + sizeof(vl_api_##n##_t), 0/* do NOT trace! */); + foreach_dhcp6_pd_client_cp_msg; +#undef _ + + /* + * Set up the (msg_name, crc, message-id) table + */ + setup_message_id_table (am); + + return 0; +} + +VLIB_INIT_FUNCTION (dhcp_pd_client_cp_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/dhcp/dhcp6_pd_client_dp.c b/src/vnet/dhcp/dhcp6_pd_client_dp.c new file mode 100644 index 00000000000..c14a3119c43 --- /dev/null +++ b/src/vnet/dhcp/dhcp6_pd_client_dp.c @@ -0,0 +1,1141 @@ +/* + * Copyright (c) 2018 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define vl_typedefs /* define message structures */ +#include +#undef vl_typedefs + +#define vl_endianfun /* define message structures */ +#include +#undef vl_endianfun + +/* instantiate all the print functions we know about */ +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) +#define vl_printfun +#include +#undef vl_printfun + +#include + +typedef struct +{ + u8 entry_valid; + u8 keep_sending_client_message; /* when true then next fields are valid */ + dhcp6_pd_send_client_message_params_t params; + f64 transaction_start; + f64 sleep_interval; + f64 due_time; + u32 n_left; + f64 start_time; + u32 transaction_id; + vlib_buffer_t *buffer; + u32 elapsed_pos; + u32 adj_index; +} dhcp6_pd_client_state_t; + +typedef struct +{ + u8 *data; + u16 len; +} server_id_t; + +typedef struct +{ + dhcp6_pd_client_state_t *client_state_by_sw_if_index; + server_id_t *server_ids; + + uword publisher_node; + uword publisher_et; + + u32 seed; + + /* convenience */ + vlib_main_t *vlib_main; + vnet_main_t *vnet_main; +} dhcp6_pd_client_main_t; + +static dhcp6_pd_client_main_t dhcp6_pd_client_main; +dhcp6_pd_client_public_main_t dhcp6_pd_client_public_main; + +typedef struct +{ + ip6_address_t prefix; + u8 prefix_length; + u32 valid_time; + u32 preferred_time; + u16 status_code; +} prefix_info_t; + +typedef struct +{ + u32 sw_if_index; + u32 server_index; + u8 msg_type; + u32 T1; + u32 T2; + u16 inner_status_code; + u16 status_code; + u8 preference; + u32 n_prefixes; + prefix_info_t *prefixes; +} report_t; + +typedef union +{ + CLIB_PACKED (struct + { + u16 duid_type; + u16 hardware_type; + u32 time; + u8 lla[6]; + }); + char bin_string[14]; +} dhcpv6_duid_string_t; + +static dhcpv6_duid_string_t client_duid; +#define CLIENT_DUID_LENGTH sizeof (client_duid) +#define DHCPV6_CLIENT_IAID 1 + +static void +signal_report (report_t * r) +{ + vlib_main_t *vm = vlib_get_main (); + dhcp6_pd_client_main_t *cm = &dhcp6_pd_client_main; + uword ni = cm->publisher_node; + uword et = cm->publisher_et; + + if (ni == (uword) ~ 0) + return; + report_t *q = vlib_process_signal_event_data (vm, ni, et, 1, sizeof *q); + + *q = *r; +} + +static int +publish_report (report_t * r) +{ + void vl_api_rpc_call_main_thread (void *fp, u8 * data, u32 data_length); + vl_api_rpc_call_main_thread (signal_report, (u8 *) r, sizeof *r); + return 0; +} + +void +dhcp6_pd_set_publisher_node (uword node_index, uword event_type) +{ + dhcp6_pd_client_main_t *cm = &dhcp6_pd_client_main; + cm->publisher_node = node_index; + cm->publisher_et = event_type; +} + +#define foreach_dhcpv6_pd_client \ + _(DROP, "error-drop") \ + _(LOOKUP, "ip6-lookup") + +typedef enum +{ +#define _(sym,str) DHCPV6_PD_CLIENT_NEXT_##sym, + foreach_dhcpv6_pd_client +#undef _ + DHCPV6_PD_CLIENT_N_NEXT, +} dhcpv6_pd_client_next_t; + +/** + * per-packet trace data + */ +typedef struct dhcpv6_pd_client_trace_t_ +{ +} dhcpv6_pd_client_trace_t; + +static u8 * +format_dhcpv6_pd_client_trace (u8 * s, va_list * args) +{ + CLIB_UNUSED (vlib_main_t * vm) = va_arg (*args, vlib_main_t *); + CLIB_UNUSED (vlib_node_t * node) = va_arg (*args, vlib_node_t *); + //dhcpv6_pd_client_trace_t *t = va_arg (*args, dhcpv6_pd_client_trace_t *); + + s = format (s, "nothing"); + + return s; +} + +static u32 +server_index_get_or_create (u8 * data, u16 len) +{ + dhcp6_pd_client_main_t *cm = &dhcp6_pd_client_main; + u32 i; + server_id_t *se; + server_id_t new_se; + + for (i = 0; i < vec_len (cm->server_ids); i++) + { + se = &cm->server_ids[i]; + if (se->len == len && 0 == memcmp (se->data, data, len)) + return i; + } + + new_se.len = len; + new_se.data = 0; + vec_validate (new_se.data, len - 1); + memcpy (new_se.data, data, len); + + vec_add1 (cm->server_ids, new_se); + + return vec_len (cm->server_ids) - 1; +} + +static void +stop_sending_client_message (vlib_main_t * vm, + dhcp6_pd_client_state_t * client_state) +{ + u32 bi0; + + client_state->keep_sending_client_message = 0; + vec_free (client_state->params.prefixes); + if (client_state->buffer) + { + bi0 = vlib_get_buffer_index (vm, client_state->buffer); + vlib_buffer_free (vm, &bi0, 1); + client_state->buffer = 0; + adj_unlock (client_state->adj_index); + client_state->adj_index = ~0; + } +} + +static uword +dhcpv6_pd_client_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + dhcp6_pd_client_main_t *cm = &dhcp6_pd_client_main; + + dhcpv6_pd_client_next_t next_index; + u32 n_left_from, *from, *to_next; + next_index = 0; + n_left_from = frame->n_vectors; + from = vlib_frame_vector_args (frame); + + while (n_left_from > 0) + { + u32 n_left_to_next; + + vlib_get_next_frame (vm, node, next_index, to_next, n_left_to_next); + + while (n_left_from > 0 && n_left_to_next > 0) + { + ip6_header_t *ip0; + u32 options_length; + dhcpv6_header_t *dhcpv60; + dhcpv6_option_t *option; + vlib_buffer_t *b0; + report_t report; + u32 next0 = DHCPV6_PD_CLIENT_NEXT_DROP; + u32 bi0; + u32 xid; + u32 sw_if_index; + u32 iaid; + u8 client_id_present = 0; + u8 discard = 0; + + dhcp6_pd_client_state_t *client_state = NULL; + + bi0 = from[0]; + to_next[0] = bi0; + from += 1; + to_next += 1; + n_left_from -= 1; + n_left_to_next -= 1; + + b0 = vlib_get_buffer (vm, bi0); + + dhcpv60 = vlib_buffer_get_current (b0); + ip0 = (void *) (b0->data + vnet_buffer (b0)->l3_hdr_offset); + u32 dhcpv6_ip6_palyoad_offset = + (u8 *) dhcpv60 - ((u8 *) ip0 + sizeof (*ip0)); + options_length = + ntohs (ip0->payload_length) - dhcpv6_ip6_palyoad_offset - + sizeof (*dhcpv60); + + memset (&report, 0, sizeof (report)); + + sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + if (sw_if_index >= vec_len (cm->client_state_by_sw_if_index)) + client_state = 0; + else + client_state = &cm->client_state_by_sw_if_index[sw_if_index]; + + xid = + (dhcpv60->xid[0] << 16) + (dhcpv60->xid[1] << 8) + + dhcpv60->xid[2]; + if (!client_state || client_state->transaction_id != xid) + { + clib_warning + ("Received DHCPv6 message with wrong Transaction ID"); + discard = 1; + } + + report.sw_if_index = sw_if_index; + report.msg_type = dhcpv60->msg_type; + report.server_index = ~0; + + switch (dhcpv60->msg_type) + { + case DHCPV6_MSG_ADVERTISE: + case DHCPV6_MSG_REPLY: + option = (dhcpv6_option_t *) (dhcpv60 + 1); + while (options_length > 0) + { + if (options_length < + ntohs (option->length) + sizeof (*option)) + { + clib_warning + ("remaining payload length < option length (%d < %d)", + options_length, + ntohs (option->length) + sizeof (*option)); + break; + } + u16 oo = ntohs (option->option); + if (oo == DHCPV6_OPTION_IA_PD) + { + u8 discard_ia_pd = 0; + dhcpv6_ia_header_t *ia_header = (void *) option; + iaid = ntohl (ia_header->iaid); + u32 T1 = ntohl (ia_header->t1); + u32 T2 = ntohl (ia_header->t2); + if (iaid != DHCPV6_CLIENT_IAID) + discard_ia_pd = 1; + if (T1 != 0 && T2 != 0 && T1 > T2) + discard_ia_pd = 1; + if (!discard_ia_pd) + { + report.T1 = T1; + report.T2 = T2; + } + dhcpv6_option_t *inner_option = + (void *) ia_header->data; + u16 inner_options_length = + ntohs (option->length) - (sizeof (*ia_header) - + sizeof (dhcpv6_option_t)); + while (inner_options_length > 0) + { + u16 inner_oo = ntohs (inner_option->option); + if (discard_ia_pd) + ; + else if (inner_oo == DHCPV6_OPTION_IAPREFIX) + { + dhcpv6_ia_opt_pd_t *iaprefix = + (void *) inner_option; + vec_validate (report.prefixes, + report.n_prefixes); + prefix_info_t *prefix_info = + &report.prefixes[report.n_prefixes]; + report.n_prefixes++; + prefix_info->preferred_time = + ntohl (iaprefix->preferred); + prefix_info->valid_time = + ntohl (iaprefix->valid); + prefix_info->prefix_length = iaprefix->prefix; + prefix_info->prefix = iaprefix->addr; + } + else if (inner_oo == DHCPV6_OPTION_STATUS_CODE) + { + dhcpv6_status_code_t *sc = + (void *) inner_option; + report.inner_status_code = + ntohs (sc->status_code); + } + inner_options_length -= + sizeof (*inner_option) + + ntohs (inner_option->length); + inner_option = + (void *) ((u8 *) inner_option + + sizeof (*inner_option) + + ntohs (inner_option->length)); + } + } + else if (oo == DHCPV6_OPTION_CLIENTID) + { + if (client_id_present) + { + clib_warning + ("Duplicate Client ID in received DHVPv6 message"); + discard = 1; + } + else + { + u16 len = ntohs (option->length); + client_id_present = 1; + if (len != CLIENT_DUID_LENGTH || + 0 != memcmp (option->data, + client_duid.bin_string, + CLIENT_DUID_LENGTH)) + { + clib_warning + ("Unrecognized client DUID inside received DHVPv6 message"); + discard = 1; + } + } + } + else if (oo == DHCPV6_OPTION_SERVERID) + { + if (report.server_index != ~0) + { + clib_warning + ("Duplicate Server ID in received DHVPv6 message"); + discard = 1; + } + else + report.server_index = + server_index_get_or_create (option->data, + ntohs (option->length)); + } + else if (oo == DHCPV6_OPTION_PREFERENCE) + { + report.preference = option->data[0]; + } + else if (oo == DHCPV6_OPTION_STATUS_CODE) + { + dhcpv6_status_code_t *sc = (void *) option; + report.status_code = ntohs (sc->status_code); + } + options_length -= sizeof (*option) + ntohs (option->length); + option = + (void *) ((u8 *) option + sizeof (*option) + + ntohs (option->length)); + } + + if (!client_id_present) + { + clib_warning + ("Missing Client ID in received DHVPv6 message"); + discard = 1; + } + if (report.server_index == ~0) + { + clib_warning + ("Missing Server ID in received DHVPv6 message"); + discard = 1; + } + + if (!discard) + publish_report (&report); + else + vec_free (report.prefixes); + + break; + default: + break; + } + + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcpv6_pd_client_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + } + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + bi0, next0); + } + + vlib_put_next_frame (vm, node, next_index, n_left_to_next); + } + + return frame->n_vectors; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dhcpv6_pd_client_node, static) = { + .function = dhcpv6_pd_client_node_fn, + .name = "dhcpv6-pd-client", + .vector_size = sizeof (u32), + + .n_errors = 0, + + .n_next_nodes = DHCPV6_PD_CLIENT_N_NEXT, + .next_nodes = { + #define _(s,n) [DHCPV6_PD_CLIENT_NEXT_##s] = n, + foreach_dhcpv6_pd_client + #undef _ + }, + + .format_trace = format_dhcpv6_pd_client_trace, +}; +/* *INDENT-ON* */ + +static_always_inline f64 +random_f64_from_to (f64 from, f64 to) +{ + static u32 seed = 0; + static u8 seed_set = 0; + if (!seed_set) + { + seed = random_default_seed (); + seed_set = 1; + } + return random_f64 (&seed) * (to - from) + from; +} + +static const ip6_address_t all_dhcp6_relay_agents_and_servers = { + .as_u8 = { + 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02} +}; + +static vlib_buffer_t * +create_buffer_for_client_message (vlib_main_t * vm, + u32 sw_if_index, + dhcp6_pd_client_state_t + * client_state, u32 type) +{ + dhcp6_pd_client_main_t *dm = &dhcp6_pd_client_main; + vnet_main_t *vnm = vnet_get_main (); + + vlib_buffer_t *b; + u32 bi; + ip6_header_t *ip; + udp_header_t *udp; + dhcpv6_header_t *dhcp; + ip6_address_t src_addr; + u32 dhcp_opt_len = 0; + client_state->transaction_start = vlib_time_now (vm); + u32 n_prefixes; + u32 i; + + vnet_hw_interface_t *hw = vnet_get_sup_hw_interface (vnm, sw_if_index); + vnet_sw_interface_t *sup_sw = vnet_get_sup_sw_interface (vnm, sw_if_index); + vnet_sw_interface_t *sw = vnet_get_sw_interface (vnm, sw_if_index); + + /* Interface(s) down? */ + if ((hw->flags & VNET_HW_INTERFACE_FLAG_LINK_UP) == 0) + return NULL; + if ((sup_sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0) + return NULL; + if ((sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0) + return NULL; + + /* Get a link-local address */ + src_addr = ip6_neighbor_get_link_local_address (sw_if_index); + + if (src_addr.as_u8[0] != 0xfe) + { + clib_warning ("Could not find source address to send DHCPv6 packet"); + return NULL; + } + + if (vlib_buffer_alloc (vm, &bi, 1) != 1) + { + clib_warning ("Buffer allocation failed"); + return NULL; + } + + b = vlib_get_buffer (vm, bi); + vnet_buffer (b)->sw_if_index[VLIB_RX] = sw_if_index; + vnet_buffer (b)->sw_if_index[VLIB_TX] = sw_if_index; + client_state->adj_index = adj_mcast_add_or_lock (FIB_PROTOCOL_IP6, + VNET_LINK_IP6, + sw_if_index); + vnet_buffer (b)->ip.adj_index[VLIB_TX] = client_state->adj_index; + b->flags |= VNET_BUFFER_F_LOCALLY_ORIGINATED; + + ip = (ip6_header_t *) vlib_buffer_get_current (b); + udp = (udp_header_t *) (ip + 1); + dhcp = (dhcpv6_header_t *) (udp + 1); + + ip->src_address = src_addr; + ip->hop_limit = 255; + ip->ip_version_traffic_class_and_flow_label = + clib_host_to_net_u32 (0x6 << 28); + ip->payload_length = 0; + ip->protocol = IP_PROTOCOL_UDP; + + udp->src_port = clib_host_to_net_u16 (DHCPV6_CLIENT_PORT); + udp->dst_port = clib_host_to_net_u16 (DHCPV6_SERVER_PORT); + udp->checksum = 0; + udp->length = 0; + + dhcp->msg_type = type; + dhcp->xid[0] = (client_state->transaction_id & 0x00ff0000) >> 16; + dhcp->xid[1] = (client_state->transaction_id & 0x0000ff00) >> 8; + dhcp->xid[2] = (client_state->transaction_id & 0x000000ff) >> 0; + + void *d = (void *) dhcp->data; + dhcpv6_option_t *duid; + dhcpv6_elapsed_t *elapsed; + dhcpv6_ia_header_t *ia_hdr; + dhcpv6_ia_opt_pd_t *opt_pd; + if (type == DHCPV6_MSG_SOLICIT || type == DHCPV6_MSG_REQUEST || + type == DHCPV6_MSG_RENEW || type == DHCPV6_MSG_REBIND || + type == DHCPV6_MSG_RELEASE) + { + duid = (dhcpv6_option_t *) d; + duid->option = clib_host_to_net_u16 (DHCPV6_OPTION_CLIENTID); + duid->length = clib_host_to_net_u16 (CLIENT_DUID_LENGTH); + clib_memcpy (duid + 1, client_duid.bin_string, CLIENT_DUID_LENGTH); + d += sizeof (*duid) + CLIENT_DUID_LENGTH; + + if (client_state->params.server_index != ~0) + { + server_id_t *se = + &dm->server_ids[client_state->params.server_index]; + + duid = (dhcpv6_option_t *) d; + duid->option = clib_host_to_net_u16 (DHCPV6_OPTION_SERVERID); + duid->length = clib_host_to_net_u16 (se->len); + clib_memcpy (duid + 1, se->data, se->len); + d += sizeof (*duid) + se->len; + } + + elapsed = (dhcpv6_elapsed_t *) d; + elapsed->opt.option = clib_host_to_net_u16 (DHCPV6_OPTION_ELAPSED_TIME); + elapsed->opt.length = + clib_host_to_net_u16 (sizeof (*elapsed) - sizeof (elapsed->opt)); + elapsed->elapsed_10ms = 0; + client_state->elapsed_pos = + (char *) &elapsed->elapsed_10ms - + (char *) vlib_buffer_get_current (b); + d += sizeof (*elapsed); + + ia_hdr = (dhcpv6_ia_header_t *) d; + ia_hdr->opt.option = clib_host_to_net_u16 (DHCPV6_OPTION_IA_PD); + ia_hdr->iaid = clib_host_to_net_u32 (DHCPV6_CLIENT_IAID); + ia_hdr->t1 = clib_host_to_net_u32 (client_state->params.T1); + ia_hdr->t2 = clib_host_to_net_u32 (client_state->params.T2); + d += sizeof (*ia_hdr); + + n_prefixes = vec_len (client_state->params.prefixes); + + ia_hdr->opt.length = + clib_host_to_net_u16 (sizeof (*ia_hdr) + + n_prefixes * sizeof (*opt_pd) - + sizeof (ia_hdr->opt)); + + for (i = 0; i < n_prefixes; i++) + { + dhcp6_pd_send_client_message_params_prefix_t *pref = + &client_state->params.prefixes[i]; + opt_pd = (dhcpv6_ia_opt_pd_t *) d; + opt_pd->opt.option = clib_host_to_net_u16 (DHCPV6_OPTION_IAPREFIX); + opt_pd->opt.length = + clib_host_to_net_u16 (sizeof (*opt_pd) - sizeof (opt_pd->opt)); + opt_pd->addr = pref->prefix; + opt_pd->prefix = pref->prefix_length; + opt_pd->valid = clib_host_to_net_u32 (pref->valid_lt); + opt_pd->preferred = clib_host_to_net_u32 (pref->preferred_lt); + d += sizeof (*opt_pd); + } + } + else + { + clib_warning ("State not implemented"); + } + + dhcp_opt_len = ((u8 *) d) - dhcp->data; + udp->length = + clib_host_to_net_u16 (sizeof (*udp) + sizeof (*dhcp) + dhcp_opt_len); + ip->payload_length = udp->length; + b->current_length = + sizeof (*ip) + sizeof (*udp) + sizeof (*dhcp) + dhcp_opt_len; + + ip->dst_address = all_dhcp6_relay_agents_and_servers; + + return b; +} + +static inline u8 +check_pd_send_client_message (vlib_main_t * vm, + dhcp6_pd_client_state_t * client_state, + f64 current_time, f64 * due_time) +{ + vlib_buffer_t *p0; + vlib_frame_t *f; + u32 *to_next; + u32 next_index; + vlib_buffer_t *c0; + ip6_header_t *ip; + udp_header_t *udp; + u32 ci0; + int bogus_length = 0; + + dhcp6_pd_send_client_message_params_t *params; + + f64 now = vlib_time_now (vm); + + if (!client_state->keep_sending_client_message) + return false; + + params = &client_state->params; + + if (client_state->due_time > current_time) + { + *due_time = client_state->due_time; + return true; + } + + p0 = client_state->buffer; + + next_index = ip6_rewrite_mcast_node.index; + + c0 = vlib_buffer_copy (vm, p0); + ci0 = vlib_get_buffer_index (vm, c0); + + ip = (ip6_header_t *) vlib_buffer_get_current (c0); + udp = (udp_header_t *) (ip + 1); + + u16 *elapsed_field = (u16 *) ((void *) ip + client_state->elapsed_pos); + *elapsed_field = + clib_host_to_net_u16 ((u16) + ((now - client_state->transaction_start) * 100)); + + udp->checksum = 0; + udp->checksum = + ip6_tcp_udp_icmp_compute_checksum (vm, 0, ip, &bogus_length); + + f = vlib_get_frame_to_node (vm, next_index); + to_next = vlib_frame_vector_args (f); + to_next[0] = ci0; + f->n_vectors = 1; + vlib_put_frame_to_node (vm, next_index, f); + + if (params->mrc != 0 && --client_state->n_left == 0) + stop_sending_client_message (vm, client_state); + else + { + client_state->sleep_interval = + (2 + random_f64_from_to (-0.1, 0.1)) * client_state->sleep_interval; + if (client_state->sleep_interval > params->mrt) + client_state->sleep_interval = + (1 + random_f64_from_to (-0.1, 0.1)) * params->mrt; + + client_state->due_time = current_time + client_state->sleep_interval; + + if (params->mrd != 0 + && current_time > client_state->start_time + params->mrd) + stop_sending_client_message (vm, client_state); + else + *due_time = client_state->due_time; + } + + return client_state->keep_sending_client_message; +} + +static uword +send_dhcp6_pd_client_message_process (vlib_main_t * vm, + vlib_node_runtime_t * rt, + vlib_frame_t * f0) +{ + dhcp6_pd_client_main_t *cm = &dhcp6_pd_client_main; + dhcp6_pd_client_state_t *client_state; + uword *event_data = 0; + f64 sleep_time = 1e9; + f64 current_time; + f64 due_time; + f64 dt = 0; + int i; + + while (true) + { + vlib_process_wait_for_event_or_clock (vm, sleep_time); + vlib_process_get_events (vm, &event_data); + vec_reset_length (event_data); + + current_time = vlib_time_now (vm); + do + { + due_time = current_time + 1e9; + for (i = 0; i < vec_len (cm->client_state_by_sw_if_index); i++) + { + client_state = &cm->client_state_by_sw_if_index[i]; + if (!client_state->entry_valid) + continue; + if (check_pd_send_client_message + (vm, client_state, current_time, &dt) && (dt < due_time)) + due_time = dt; + } + current_time = vlib_time_now (vm); + } + while (due_time < current_time); + + sleep_time = due_time - current_time; + } + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (send_dhcp6_pd_client_message_process_node) = { + .function = send_dhcp6_pd_client_message_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "send-dhcp6-pd-client-message-process", +}; +/* *INDENT-ON* */ + +void +dhcp6_pd_send_client_message (vlib_main_t * vm, u32 sw_if_index, u8 stop, + dhcp6_pd_send_client_message_params_t * params) +{ + dhcp6_pd_client_main_t *cm = &dhcp6_pd_client_main; + dhcp6_pd_client_state_t *client_state = 0; + dhcp6_pd_client_state_t empty_state = { + 0, + }; + + ASSERT (~0 != sw_if_index); + + vec_validate_init_empty (cm->client_state_by_sw_if_index, sw_if_index, + empty_state); + client_state = &cm->client_state_by_sw_if_index[sw_if_index]; + if (!client_state->entry_valid) + { + client_state->entry_valid = 1; + client_state->adj_index = ~0; + } + + stop_sending_client_message (vm, client_state); + + if (!stop) + { + client_state->keep_sending_client_message = 1; + vec_free (client_state->params.prefixes); + client_state->params = *params; + client_state->params.prefixes = vec_dup (params->prefixes); + client_state->n_left = params->mrc; + client_state->start_time = vlib_time_now (vm); + client_state->sleep_interval = + (1 + random_f64_from_to (-0.1, 0.1)) * params->irt; + client_state->due_time = 0; /* send first packet ASAP */ + client_state->transaction_id = random_u32 (&cm->seed) & 0x00ffffff; + client_state->buffer = + create_buffer_for_client_message (vm, sw_if_index, client_state, + params->msg_type); + if (!client_state->buffer) + client_state->keep_sending_client_message = 0; + else + vlib_process_signal_event (vm, + send_dhcp6_pd_client_message_process_node.index, + 1, 0); + } +} + +void + vl_api_dhcp6_pd_send_client_message_t_handler + (vl_api_dhcp6_pd_send_client_message_t * mp) +{ + vl_api_dhcp6_pd_send_client_message_reply_t *rmp; + dhcp6_pd_send_client_message_params_t params; + vlib_main_t *vm = vlib_get_main (); + u32 n_prefixes; + u32 i; + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_DHCP6_PD_SEND_CLIENT_MESSAGE_REPLY); + + if (rv != 0) + return; + + params.sw_if_index = ntohl (mp->sw_if_index); + params.server_index = ntohl (mp->server_index); + params.irt = ntohl (mp->irt); + params.mrt = ntohl (mp->mrt); + params.mrc = ntohl (mp->mrc); + params.mrd = ntohl (mp->mrd); + params.msg_type = mp->msg_type; + params.T1 = ntohl (mp->T1); + params.T2 = ntohl (mp->T2); + n_prefixes = ntohl (mp->n_prefixes); + params.prefixes = 0; + if (n_prefixes > 0) + vec_validate (params.prefixes, n_prefixes - 1); + for (i = 0; i < n_prefixes; i++) + { + vl_api_dhcp6_pd_prefix_info_t *pi = &mp->prefixes[i]; + dhcp6_pd_send_client_message_params_prefix_t *pref = + ¶ms.prefixes[i]; + pref->preferred_lt = ntohl (pi->preferred_time); + pref->valid_lt = ntohl (pi->valid_time); + memcpy (pref->prefix.as_u8, pi->prefix, 16); + pref->prefix_length = pi->prefix_length; + } + + dhcp6_pd_send_client_message (vm, ntohl (mp->sw_if_index), mp->stop, + ¶ms); +} + +static clib_error_t * +call_dhcp6_pd_reply_event_callbacks (void *data, + _vnet_dhcp6_pd_reply_event_function_list_elt_t + * elt) +{ + clib_error_t *error = 0; + + while (elt) + { + error = elt->fp (data); + if (error) + return error; + elt = elt->next_dhcp6_pd_reply_event_function; + } + + return error; +} + +static uword +dhcp6_pd_reply_process (vlib_main_t * vm, vlib_node_runtime_t * rt, + vlib_frame_t * f) +{ + /* These cross the longjmp boundry (vlib_process_wait_for_event) + * and need to be volatile - to prevent them from being optimized into + * a register - which could change during suspension */ + + while (1) + { + vlib_process_wait_for_event (vm); + uword event_type = DHCP6_PD_DP_REPLY_REPORT; + void *event_data = vlib_process_get_event_data (vm, &event_type); + + int i; + if (event_type == DHCP6_PD_DP_REPLY_REPORT) + { + report_t *events = event_data; + for (i = 0; i < vec_len (events); i++) + { + u32 event_size = + sizeof (vl_api_dhcp6_pd_reply_event_t) + + vec_len (events[i].prefixes) * + sizeof (vl_api_dhcp6_pd_prefix_info_t); + vl_api_dhcp6_pd_reply_event_t *event = + clib_mem_alloc (event_size); + memset (event, 0, event_size); + + event->sw_if_index = htonl (events[i].sw_if_index); + event->server_index = htonl (events[i].server_index); + event->msg_type = events[i].msg_type; + event->T1 = htonl (events[i].T1); + event->T2 = htonl (events[i].T2); + event->inner_status_code = htons (events[i].inner_status_code); + event->status_code = htons (events[i].status_code); + event->preference = events[i].preference; + + event->n_prefixes = htonl (vec_len (events[i].prefixes)); + vl_api_dhcp6_pd_prefix_info_t *prefix = + (typeof (prefix)) event->prefixes; + u32 j; + for (j = 0; j < vec_len (events[i].prefixes); j++) + { + prefix_info_t *info = &events[i].prefixes[j]; + memcpy (prefix->prefix, &info->prefix, 16); + prefix->prefix_length = info->prefix_length; + prefix->valid_time = htonl (info->valid_time); + prefix->preferred_time = htonl (info->preferred_time); + prefix++; + } + + dhcp6_pd_client_public_main_t *dpcpm = + &dhcp6_pd_client_public_main; + call_dhcp6_pd_reply_event_callbacks (event, dpcpm->functions); + + vpe_client_registration_t *reg; + /* *INDENT-OFF* */ + pool_foreach(reg, vpe_api_main.dhcp6_pd_reply_events_registrations, + ({ + vl_api_registration_t *vl_reg; + vl_reg = + vl_api_client_index_to_registration (reg->client_index); + if (vl_reg && vl_api_can_send_msg (vl_reg)) + { + vl_api_dhcp6_pd_reply_event_t *msg = + vl_msg_api_alloc (event_size); + clib_memcpy (msg, event, event_size); + msg->_vl_msg_id = htons (VL_API_DHCP6_PD_REPLY_EVENT); + msg->client_index = reg->client_index; + msg->pid = reg->client_pid; + vl_api_send_msg (vl_reg, (u8 *) msg); + } + })); + /* *INDENT-ON* */ + + clib_mem_free (event); + } + } + vlib_process_put_event_data (vm, event_data); + } + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dhcp6_pd_reply_process_node, ) = { + .function = dhcp6_pd_reply_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "dhcp6-pd-reply-publisher-process", +}; +/* *INDENT-ON* */ + +void + vl_api_want_dhcp6_pd_reply_events_t_handler + (vl_api_want_dhcp6_pd_reply_events_t * mp) +{ + vpe_api_main_t *am = &vpe_api_main; + vl_api_want_dhcp6_pd_reply_events_reply_t *rmp; + int rv = 0; + + uword *p = + hash_get (am->dhcp6_pd_reply_events_registration_hash, mp->client_index); + vpe_client_registration_t *rp; + if (p) + { + if (mp->enable_disable) + { + clib_warning ("pid %d: already enabled...", ntohl (mp->pid)); + rv = VNET_API_ERROR_INVALID_REGISTRATION; + goto reply; + } + else + { + rp = + pool_elt_at_index (am->dhcp6_pd_reply_events_registrations, p[0]); + pool_put (am->dhcp6_pd_reply_events_registrations, rp); + hash_unset (am->dhcp6_pd_reply_events_registration_hash, + mp->client_index); + if (pool_elts (am->dhcp6_pd_reply_events_registrations) == 0) + dhcp6_pd_set_publisher_node (~0, REPORT_MAX); + goto reply; + } + } + if (mp->enable_disable == 0) + { + clib_warning ("pid %d: already disabled...", ntohl (mp->pid)); + rv = VNET_API_ERROR_INVALID_REGISTRATION; + goto reply; + } + pool_get (am->dhcp6_pd_reply_events_registrations, rp); + rp->client_index = mp->client_index; + rp->client_pid = ntohl (mp->pid); + hash_set (am->dhcp6_pd_reply_events_registration_hash, rp->client_index, + rp - am->dhcp6_pd_reply_events_registrations); + dhcp6_pd_set_publisher_node (dhcp6_pd_reply_process_node.index, + DHCP6_PD_DP_REPLY_REPORT); + +reply: + REPLY_MACRO (VL_API_WANT_DHCP6_PD_REPLY_EVENTS_REPLY); +} + +void +dhcp6_clients_enable_disable (u8 enable) +{ + vlib_main_t *vm = vlib_get_main (); + + if (enable) + udp_register_dst_port (vm, UDP_DST_PORT_dhcpv6_to_client, + dhcpv6_pd_client_node.index, 0 /* is_ip6 */ ); + else + udp_unregister_dst_port (vm, UDP_DST_PORT_dhcpv6_to_client, + 0 /* is_ip6 */ ); +} + +void + vl_api_dhcp6_clients_enable_disable_t_handler + (vl_api_dhcp6_clients_enable_disable_t * mp) +{ + vl_api_dhcp6_clients_enable_disable_reply_t *rmp; + int rv = 0; + + dhcp6_clients_enable_disable (mp->enable); + + REPLY_MACRO (VL_API_WANT_DHCP6_PD_REPLY_EVENTS_REPLY); +} + +static void +genereate_client_duid (void) +{ + client_duid.duid_type = htons (DHCPV6_DUID_LLT); + client_duid.hardware_type = htons (1); + u32 time_since_2000 = (u32) time (0) - 946684800; + client_duid.time = htonl (time_since_2000); + + vnet_main_t *vnm = vnet_get_main (); + vnet_interface_main_t *im = &vnm->interface_main; + vnet_hw_interface_t *hi; + ethernet_interface_t *eth_if = 0; + + /* *INDENT-OFF* */ + pool_foreach (hi, im->hw_interfaces, + ({ + eth_if = ethernet_get_interface (ðernet_main, hi->hw_if_index); + if (eth_if) + break; + })); + /* *INDENT-ON* */ + + if (eth_if) + clib_memcpy (client_duid.lla, eth_if->address, 6); + else + { + clib_warning ("Failed to find any Ethernet interface, " + "setting DHCPv6 DUID link-layer address to random value"); + u32 seed = random_default_seed (); + random_u32 (&seed); + client_duid.lla[0] = 0xc2; /* locally administered unicast */ + client_duid.lla[1] = 0x18; + client_duid.lla[2] = 0x44; + client_duid.lla[3] = random_u32 (&seed); + client_duid.lla[4] = random_u32 (&seed); + client_duid.lla[5] = random_u32 (&seed); + } +} + +static clib_error_t * +dhcp6_pd_client_init (vlib_main_t * vm) +{ + dhcp6_pd_client_main_t *cm = &dhcp6_pd_client_main; + + cm->vlib_main = vm; + cm->vnet_main = vnet_get_main (); + + cm->publisher_node = ~0; + + cm->seed = 0xdeaddabe; + + // TODO: should be stored in non-volatile memory + genereate_client_duid (); + + return 0; +} + +VLIB_INIT_FUNCTION (dhcp6_pd_client_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/dhcp/dhcp6_pd_client_dp.h b/src/vnet/dhcp/dhcp6_pd_client_dp.h new file mode 100644 index 00000000000..eec8fe838df --- /dev/null +++ b/src/vnet/dhcp/dhcp6_pd_client_dp.h @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2018 Cisco and/or its affiliates. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef included_vnet_dhcp6_pd_client_dp_h +#define included_vnet_dhcp6_pd_client_dp_h + +#include + +#define vl_typedefs /* define message structures */ +#include +#undef vl_typedefs + +typedef struct +{ + u32 preferred_lt; + u32 valid_lt; + ip6_address_t prefix; + u8 prefix_length; +} dhcp6_pd_send_client_message_params_prefix_t; + +typedef struct +{ + u32 sw_if_index; + u32 server_index; + u32 irt; + u32 mrt; + u32 mrc; + u32 mrd; + u8 msg_type; + u32 T1; + u32 T2; + dhcp6_pd_send_client_message_params_prefix_t *prefixes; +} dhcp6_pd_send_client_message_params_t; + +void dhcp6_pd_send_client_message (vlib_main_t * vm, u32 sw_if_index, u8 stop, + dhcp6_pd_send_client_message_params_t * + params); +void dhcp6_pd_set_publisher_node (uword node_index, uword event_type); +void dhcp6_clients_enable_disable (u8 enable); + +void + vl_api_want_dhcp6_pd_reply_events_t_handler + (vl_api_want_dhcp6_pd_reply_events_t * mp); +void + vl_api_dhcp6_pd_send_client_message_t_handler + (vl_api_dhcp6_pd_send_client_message_t * mp); +void + vl_api_dhcp6_clients_enable_disable_t_handler + (vl_api_dhcp6_clients_enable_disable_t * mp); + +extern vlib_node_registration_t dhcp6_pd_reply_process_node; + +enum +{ DHCP6_PD_DP_REPLY_REPORT, REPORT_MAX }; + +typedef struct _vnet_dhcp6_pd_reply_function_list_elt +{ + struct _vnet_dhcp6_pd_reply_function_list_elt + *next_dhcp6_pd_reply_event_function; + clib_error_t *(*fp) (vl_api_dhcp6_pd_reply_event_t * mp); +} _vnet_dhcp6_pd_reply_event_function_list_elt_t; + +typedef struct +{ + _vnet_dhcp6_pd_reply_event_function_list_elt_t *functions; +} dhcp6_pd_client_public_main_t; + +extern dhcp6_pd_client_public_main_t dhcp6_pd_client_public_main; + +#define VNET_DHCP6_PD_REPLY_EVENT_FUNCTION(f) \ + \ +static void __vnet_dhcp6_pd_reply_event_function_init_##f (void) \ + __attribute__((__constructor__)) ; \ + \ +static void __vnet_dhcp6_pd_reply_event_function_init_##f (void) \ +{ \ + dhcp6_pd_client_public_main_t * nm = &dhcp6_pd_client_public_main; \ + static _vnet_dhcp6_pd_reply_event_function_list_elt_t init_function; \ + init_function.next_dhcp6_pd_reply_event_function = nm->functions; \ + nm->functions = &init_function; \ + init_function.fp = (void *) &f; \ +} \ + \ +static void __vnet_dhcp6_pd_reply_event_function_deinit_##f (void) \ + __attribute__((__destructor__)) ; \ + \ +static void __vnet_dhcp6_pd_reply_event_function_deinit_##f (void) \ +{ \ + dhcp6_pd_client_public_main_t * nm = &dhcp6_pd_client_public_main; \ + _vnet_dhcp6_pd_reply_event_function_list_elt_t *next; \ + if (nm->functions->fp == (void *) &f) \ + { \ + nm->functions = \ + nm->functions->next_dhcp6_pd_reply_event_function; \ + return; \ + } \ + next = nm->functions; \ + while (next->next_dhcp6_pd_reply_event_function) \ + { \ + if (next->next_dhcp6_pd_reply_event_function->fp == (void *) &f) \ + { \ + next->next_dhcp6_pd_reply_event_function = \ + next->next_dhcp6_pd_reply_event_function->next_dhcp6_pd_reply_event_function; \ + return; \ + } \ + next = next->next_dhcp6_pd_reply_event_function; \ + } \ +} + +#endif /* included_vnet_dhcp6_pd_client_dp_h */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/vnet/dhcp/dhcp6_pd_doc.md b/src/vnet/dhcp/dhcp6_pd_doc.md new file mode 100644 index 00000000000..0d0e0865f1b --- /dev/null +++ b/src/vnet/dhcp/dhcp6_pd_doc.md @@ -0,0 +1,86 @@ +# DHCPv6 prefix delegation {#dhcp6_pd_doc} + +DHCPv6 prefix delegation client implementation is split between Control Plane and Data Plane. +Data Plane can also be used alone by external application (external Control Plane) using Data Plane Binary API. + +Number of different IA\_PDs managed by VPP is currently limited to 1 (and corresponding IAID has value 1). +Client ID is of type DUID-LLT (Link Layer address plus time) and is created on VPP startup from avaliable interfaces (or chosen at random for debugging purposes). +Server ID is only visible to Data Plane. Control Plane identifies servers by a 32-bit handle (server\_index) mapped to Server ID by Data Plane. + +## Control Plane + +DHCPv6 PD clients are configured per interface. +When configuring a PD client we have to choose a name of a prefix group for that client. +Each prefix obtained through this client will be flagged as belonging to specified prefix group. +The prefix groups are used as a filter by prefix consumers. + +To enable client on particular interface call Binary API function dhcp6\_pd\_client\_enable\_disable with param 'sw\_if\_index' set to that interface, +'prefix\_group' set to prefix group name and 'enable' set to true. +Format of corresponding Debug CLI command is: "dhcp6 pd client [disable]" + +To add/delete IPv6 address potentially using available prefix from specified prefix group call Binary API command ip6\_add\_del\_address\_using\_prefix with parameters: +> sw\_if\_index - software interface index of interface to add/delete address to/from +> prefix\_group - name of prefix group, prefix\_group[0] == '\0' means no prefix should be used +> address - address or suffix to be used with a prefix from selected group +> prefix\_length - subnet prefix for the address +> is\_add - 1 for add, 0 for remove +or Debug CLI command with format: "set ip6 addresses [prefix group ]
[del]" + +When no prefix is avaliable, no address is physically added, but is added once a prefix becomes avaliable. +Address is removed when all available prefixes are removed. +When a used prefix is removed and there is other available prefix, the address that used the prefix is reconfigured using the available prefix. + +There are three debug CLI commands (with no parameters) used to show the state of clients, prefixes and addresses: + show ip6 pd clients + show ip6 prefixes + show ip6 addresses + +### Example configuration + +set int state GigabitEthernet0/8/0 up +dhcp6 pd client GigabitEthernet0/8/0 prefix group my-dhcp6-pd-group +set ip6 address GigabitEthernet0/8/0 prefix group my-dhcp6-pd-group ::7/64 + +## Data Plane + +First API message to be called is dhcp6\_clients\_enable\_disable with enable parameter set to 1. +It enables DHCPv6 client subsystem to receive UDP messages containing DHCPv6 client port (sets the router to DHCPv6 client mode). +This is to ensure client subsystem gets the messages instead of DHCPv6 proxy subsystem. + +There is one common Binary API call for sending DHCPv6 client messages (dhcp6\_pd\_send\_client\_message) with these fields: +> msg\_type - message type (e.g. Solicit) +> sw\_if\_index - index of TX interface +> server\_index - used to dentify DHCPv6 server, + unique for each DHCPv6 server on the link, + value obrtained from dhcp6\_pd\_reply\_event API message, + use ~0 to send message to all DHCPv6 servers +> param irt - initial retransmission time +> param mrt - maximum retransmission time +> param mrc - maximum retransmission count +> param mrd - maximum retransmission duration for sending the message +> stop - if non-zero then stop resending the message, otherwise start sending the message +> T1 - value of T1 in IA\_PD option +> T2 - value of T2 in IA\_PD option +> prefixes - list of prefixes in IA\_PD option + +The message is automatically resent by Data Plane based on parameters 'irt', 'mrt', 'mrc' and 'mrd'. +To stop the resending call the same function (same msg\_type is sufficient) with 'stop' set to 1. + +To subscribe for notifications of DHCPv6 messages from server call Binary API function +want\_dhcp6\_pd\_reply\_events with enable\_disable set to 1 +Notification (dhcp6\_pd\_reply\_event) fileds are: +> sw\_if\_index - index of RX interface +> server\_index - used to dentify DHCPv6 server, unique for each DHCPv6 server on the link +> msg\_type - message type +> T1 - value of T1 in IA\_PD option +> T2 - value of T2 in IA\_PD option +> inner\_status\_code - value of status code inside IA\_PD option +> status\_code - value of status code +> preference - value of preference option in reply message +> prefixes - list of prefixes in IA\_PD option + +Prefix is a struct with with these fields: +> prefix - prefix bytes +> prefix\_length - prefix length +> valid\_time - valid lifetime +> preferred\_time - preferred lifetime diff --git a/src/vnet/dhcp/dhcp6_proxy_node.c b/src/vnet/dhcp/dhcp6_proxy_node.c index 2a1ac12e535..7d157ad35a1 100644 --- a/src/vnet/dhcp/dhcp6_proxy_node.c +++ b/src/vnet/dhcp/dhcp6_proxy_node.c @@ -200,7 +200,7 @@ dhcpv6_proxy_to_server_input (vlib_main_t * vm, clib_memcpy (client_src_mac, e_h0->src_address, 6); - switch (h0->u.msg_type) + switch (h0->msg_type) { case DHCPV6_MSG_SOLICIT: case DHCPV6_MSG_REQUEST: @@ -270,7 +270,7 @@ dhcpv6_proxy_to_server_input (vlib_main_t * vm, r1->hop_count++; r1->hop_count = - (h0->u.msg_type != DHCPV6_MSG_RELAY_FORW) ? 0 : r1->hop_count; + (h0->msg_type != DHCPV6_MSG_RELAY_FORW) ? 0 : r1->hop_count; if (PREDICT_FALSE (r1->hop_count >= HOP_COUNT_LIMIT)) { @@ -282,7 +282,7 @@ dhcpv6_proxy_to_server_input (vlib_main_t * vm, /* If relay-fwd and src address is site or global unicast address */ - if (h0->u.msg_type == DHCPV6_MSG_RELAY_FORW && + if (h0->msg_type == DHCPV6_MSG_RELAY_FORW && ((ip0->src_address.as_u8[0] & 0xe0) == 0x20 || (ip0->src_address.as_u8[0] & 0xfe) == 0xfc)) { @@ -336,7 +336,7 @@ dhcpv6_proxy_to_server_input (vlib_main_t * vm, id1->int_idx = clib_host_to_net_u32 (rx_sw_if_index); u1->length = 0; - if (h0->u.msg_type != DHCPV6_MSG_RELAY_FORW) + if (h0->msg_type != DHCPV6_MSG_RELAY_FORW) { cmac = (dhcpv6_client_mac_t *) (((uword) ip1) + b0->current_length); @@ -423,7 +423,7 @@ dhcpv6_proxy_to_server_input (vlib_main_t * vm, next0 = DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_LOOKUP; - is_solicit = (DHCPV6_MSG_SOLICIT == h0->u.msg_type); + is_solicit = (DHCPV6_MSG_SOLICIT == h0->msg_type); /* * If we have multiple servers configured and this is the @@ -840,12 +840,6 @@ dhcp6_proxy_init (vlib_main_t * vm) all_dhcpv6_server_relay_agent_address.as_u64[1] = clib_host_to_net_u64 (0x00010002); - udp_register_dst_port (vm, UDP_DST_PORT_dhcpv6_to_client, - dhcpv6_proxy_to_client_node.index, 0 /* is_ip6 */ ); - - udp_register_dst_port (vm, UDP_DST_PORT_dhcpv6_to_server, - dhcpv6_proxy_to_server_node.index, 0 /* is_ip6 */ ); - return 0; } @@ -856,6 +850,7 @@ dhcp6_proxy_set_server (ip46_address_t * addr, ip46_address_t * src_addr, u32 rx_table_id, u32 server_table_id, int is_del) { + vlib_main_t *vm = vlib_get_main (); u32 rx_fib_index = 0; int rc = 0; @@ -886,6 +881,11 @@ dhcp6_proxy_set_server (ip46_address_t * addr, &all_dhcp_servers, MFIB_SOURCE_DHCP); mfib_table_unlock (rx_fib_index, FIB_PROTOCOL_IP6, MFIB_SOURCE_DHCP); + + udp_unregister_dst_port (vm, UDP_DST_PORT_dhcpv6_to_client, + 0 /* is_ip6 */ ); + udp_unregister_dst_port (vm, UDP_DST_PORT_dhcpv6_to_server, + 0 /* is_ip6 */ ); } } else @@ -919,6 +919,13 @@ dhcp6_proxy_set_server (ip46_address_t * addr, MFIB_RPF_ID_NONE, MFIB_ENTRY_FLAG_ACCEPT_ALL_ITF); mfib_table_lock (rx_fib_index, FIB_PROTOCOL_IP6, MFIB_SOURCE_DHCP); + + udp_register_dst_port (vm, UDP_DST_PORT_dhcpv6_to_client, + dhcpv6_proxy_to_client_node.index, + 0 /* is_ip6 */ ); + udp_register_dst_port (vm, UDP_DST_PORT_dhcpv6_to_server, + dhcpv6_proxy_to_server_node.index, + 0 /* is_ip6 */ ); } } diff --git a/src/vnet/dhcp/dhcp_api.c b/src/vnet/dhcp/dhcp_api.c index 1dacf1178c2..5aed4c0d21e 100644 --- a/src/vnet/dhcp/dhcp_api.c +++ b/src/vnet/dhcp/dhcp_api.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -49,7 +50,10 @@ _(DHCP_PROXY_CONFIG,dhcp_proxy_config) \ _(DHCP_PROXY_DUMP,dhcp_proxy_dump) \ _(DHCP_PROXY_SET_VSS,dhcp_proxy_set_vss) \ _(DHCP_CLIENT_CONFIG, dhcp_client_config) \ -_(DHCP_CLIENT_DUMP, dhcp_client_dump) +_(DHCP_CLIENT_DUMP, dhcp_client_dump) \ +_(WANT_DHCP6_PD_REPLY_EVENTS, want_dhcp6_pd_reply_events) \ +_(DHCP6_PD_SEND_CLIENT_MESSAGE, dhcp6_pd_send_client_message) \ +_(DHCP6_CLIENTS_ENABLE_DISABLE, dhcp6_clients_enable_disable) static void @@ -383,6 +387,9 @@ dhcp_api_hookup (vlib_main_t * vm) */ setup_message_id_table (am); + dhcp6_pd_set_publisher_node (dhcp6_pd_reply_process_node.index, + DHCP6_PD_DP_REPLY_REPORT); + return 0; } diff --git a/src/vnet/ip/ip6_neighbor.c b/src/vnet/ip/ip6_neighbor.c index 3daea6bafbe..a76decf3431 100644 --- a/src/vnet/ip/ip6_neighbor.c +++ b/src/vnet/ip/ip6_neighbor.c @@ -236,6 +236,29 @@ static ip6_address_t ip6a_zero; /* ip6 address 0 */ static void wc_nd_signal_report (wc_nd_report_t * r); static void ra_signal_report (ra_report_t * r); +ip6_address_t +ip6_neighbor_get_link_local_address (u32 sw_if_index) +{ + static ip6_address_t empty_address = { {0} }; + ip6_neighbor_main_t *nm = &ip6_neighbor_main; + ip6_radv_t *radv_info; + u32 ri; + + ri = nm->if_radv_pool_index_by_sw_if_index[sw_if_index]; + if (ri == ~0) + { + clib_warning ("IPv6 is not enabled for sw_if_index %d", sw_if_index); + return empty_address; + } + radv_info = pool_elt_at_index (nm->if_radv_pool, ri); + if (radv_info == NULL) + { + clib_warning ("Internal error"); + return empty_address; + } + return radv_info->link_local_address; +} + /** * @brief publish wildcard arp event * @param sw_if_index The interface on which the ARP entires are acted diff --git a/src/vnet/ip/ip6_neighbor.h b/src/vnet/ip/ip6_neighbor.h index 27d8cb27eae..b346563c83b 100644 --- a/src/vnet/ip/ip6_neighbor.h +++ b/src/vnet/ip/ip6_neighbor.h @@ -44,6 +44,8 @@ typedef struct fib_node_index_t fib_entry_index; } ip6_neighbor_t; +extern ip6_address_t ip6_neighbor_get_link_local_address (u32 sw_if_index); + extern ip6_neighbor_t *ip6_neighbors_pool (void); extern ip6_neighbor_t *ip6_neighbors_entries (u32 sw_if_index); diff --git a/src/vnet/vnet_all_api_h.h b/src/vnet/vnet_all_api_h.h index 9d416e92e4e..d272fbc052d 100644 --- a/src/vnet/vnet_all_api_h.h +++ b/src/vnet/vnet_all_api_h.h @@ -73,6 +73,7 @@ #include #include #include +#include /* * fd.io coding-style-patch-verification: ON diff --git a/test/test_dhcp6.py b/test/test_dhcp6.py new file mode 100644 index 00000000000..6acc3447c07 --- /dev/null +++ b/test/test_dhcp6.py @@ -0,0 +1,449 @@ +from scapy.layers.dhcp6 import DHCP6_Advertise, DHCP6OptClientId, \ + DHCP6OptStatusCode, DHCP6OptPref, DHCP6OptIA_PD, DHCP6OptIAPrefix, \ + DHCP6OptServerId, DHCP6_Solicit, DHCP6_Reply, DHCP6_Request, DHCP6_Renew, \ + DHCP6_Rebind, DUID_LLT, DHCP6_Release, DHCP6OptElapsedTime +from scapy.layers.inet6 import IPv6, Ether, UDP +from scapy.utils6 import in6_mactoifaceid +from scapy.utils import inet_ntop, inet_pton +from socket import AF_INET6 +from framework import VppTestCase +from time import time + + +def ip6_normalize(ip6): + return inet_ntop(AF_INET6, inet_pton(AF_INET6, ip6)) + + +def mk_ll_addr(mac): + euid = in6_mactoifaceid(mac) + addr = "fe80::" + euid + return addr + + +class TestDHCPv6PD(VppTestCase): + """ DHCPv6 PD Data Plane Test Case """ + + @classmethod + def setUpClass(cls): + super(TestDHCPv6PD, cls).setUpClass() + + def setUp(self): + super(TestDHCPv6PD, self).setUp() + + self.create_pg_interfaces(range(1)) + self.interfaces = list(self.pg_interfaces) + for i in self.interfaces: + i.admin_up() + i.config_ip6() + + time_since_2000 = int(time()) - 946684800 + self.server_duid = DUID_LLT(timeval=time_since_2000, + lladdr=self.pg0.remote_mac) + + def tearDown(self): + for i in self.interfaces: + i.unconfig_ip6() + i.admin_down() + super(TestDHCPv6PD, self).tearDown() + + def test_dhcp_send_solicit_receive_advertise(self): + """ Verify DHCPv6 PD Solicit packet and received Advertise envent """ + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + prefix_bin = '\00\01\00\02\00\03' + '\00' * 10 + prefix = {'prefix': prefix_bin, + 'prefix_length': 50, + 'preferred_time': 60, + 'valid_time': 120} + self.vapi.dhcp6_pd_send_client_message(1, self.pg0.sw_if_index, + T1=20, T2=40, prefixes=[prefix]) + rx_list = self.pg0.get_capture(1) + self.assertEqual(len(rx_list), 1) + packet = rx_list[0] + + self.assertTrue(packet.haslayer(IPv6)) + self.assertTrue(packet[IPv6].haslayer(DHCP6_Solicit)) + + client_duid = packet[DHCP6OptClientId].duid + trid = packet[DHCP6_Solicit].trid + + dst = ip6_normalize(packet[IPv6].dst) + dst2 = ip6_normalize("ff02::1:2") + self.assert_equal(dst, dst2) + src = ip6_normalize(packet[IPv6].src) + src2 = ip6_normalize(self.pg0.local_ip6_ll) + self.assert_equal(src, src2) + ia_pd = packet[DHCP6OptIA_PD] + self.assert_equal(ia_pd.T1, 20) + self.assert_equal(ia_pd.T2, 40) + self.assert_equal(len(ia_pd.iapdopt), 1) + prefix = ia_pd.iapdopt[0] + self.assert_equal(prefix.prefix, '1:2:3::') + self.assert_equal(prefix.plen, 50) + self.assert_equal(prefix.preflft, 60) + self.assert_equal(prefix.validlft, 120) + + self.vapi.want_dhcp6_pd_reply_events() + self.vapi.dhcp6_clients_enable_disable() + + ia_pd_opts = DHCP6OptIAPrefix(prefix='7:8::', plen=56, preflft=60, + validlft=120) + p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / + IPv6(src=mk_ll_addr(self.pg0.remote_mac), + dst=self.pg0.local_ip6_ll) / + UDP(sport=547, dport=546) / + DHCP6_Advertise(trid=trid) / + DHCP6OptServerId(duid=self.server_duid) / + DHCP6OptClientId(duid=client_duid) / + DHCP6OptPref(prefval=7) / + DHCP6OptStatusCode(statuscode=1) / + DHCP6OptIA_PD(iaid=1, T1=20, T2=40, iapdopt=ia_pd_opts) + ) + self.pg0.add_stream([p]) + self.pg_start() + + ev = self.vapi.wait_for_event(10, "dhcp6_pd_reply_event") + + self.assert_equal(ev.preference, 7) + self.assert_equal(ev.status_code, 1) + self.assert_equal(ev.T1, 20) + self.assert_equal(ev.T2, 40) + + reported_prefix = ev.prefixes[0] + prefix = inet_pton(AF_INET6, ia_pd_opts.getfieldval("prefix")) + self.assert_equal(reported_prefix.prefix, prefix) + self.assert_equal(reported_prefix.prefix_length, + ia_pd_opts.getfieldval("plen")) + self.assert_equal(reported_prefix.preferred_time, + ia_pd_opts.getfieldval("preflft")) + self.assert_equal(reported_prefix.valid_time, + ia_pd_opts.getfieldval("validlft")) + + +class TestDHCPv6PDControlPlane(VppTestCase): + """ DHCPv6 PD Control Plane Test Case """ + + @classmethod + def setUpClass(cls): + super(TestDHCPv6PDControlPlane, cls).setUpClass() + + def setUp(self): + super(TestDHCPv6PDControlPlane, self).setUp() + + self.create_pg_interfaces(range(2)) + self.interfaces = list(self.pg_interfaces) + for i in self.interfaces: + i.admin_up() + + time_since_2000 = int(time()) - 946684800 + self.server_duid = DUID_LLT(timeval=time_since_2000, + lladdr=self.pg0.remote_mac) + self.client_duid = None + self.T1 = 1 + self.T2 = 2 + + fib = self.vapi.ip6_fib_dump() + self.initial_addresses = set(self.get_interface_addresses(fib, + self.pg1)) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + self.prefix_group = 'my-pd-prefix-group' + + self.vapi.dhcp6_pd_client_enable_disable( + self.pg0.sw_if_index, + prefix_group=self.prefix_group) + + def tearDown(self): + self.vapi.dhcp6_pd_client_enable_disable(self.pg0.sw_if_index, + enable=0) + + for i in self.interfaces: + i.admin_down() + + super(TestDHCPv6PDControlPlane, self).tearDown() + + @staticmethod + def get_interface_addresses(fib, pg): + lst = [] + for entry in fib: + if entry.address_length == 128: + path = entry.path[0] + if path.sw_if_index == pg.sw_if_index: + lst.append(entry.address) + return lst + + def get_addresses(self): + fib = self.vapi.ip6_fib_dump() + addresses = set(self.get_interface_addresses(fib, self.pg1)) + return addresses.difference(self.initial_addresses) + + def validate_duid_llt(self, duid): + DUID_LLT(duid) + + def validate_packet(self, packet, msg_type, is_resend=False): + try: + self.assertTrue(packet.haslayer(msg_type)) + client_duid = packet[DHCP6OptClientId].duid + if self.client_duid is None: + self.client_duid = client_duid + self.validate_duid_llt(client_duid) + else: + self.assertEqual(self.client_duid, client_duid) + if msg_type != DHCP6_Solicit and msg_type != DHCP6_Rebind: + server_duid = packet[DHCP6OptServerId].duid + self.assertEqual(server_duid, self.server_duid) + if is_resend: + self.assertEqual(self.trid, packet[msg_type].trid) + else: + self.trid = packet[msg_type].trid + ip = packet[IPv6] + udp = packet[UDP] + self.assertEqual(ip.dst, 'ff02::1:2') + self.assertEqual(udp.sport, 546) + self.assertEqual(udp.dport, 547) + dhcpv6 = packet[msg_type] + elapsed_time = dhcpv6[DHCP6OptElapsedTime] + if (is_resend): + self.assertNotEqual(elapsed_time.elapsedtime, 0) + else: + self.assertEqual(elapsed_time.elapsedtime, 0) + except: + packet.show() + raise + + def wait_for_packet(self, msg_type, timeout=None, is_resend=False): + if timeout is None: + timeout = 3 + rx_list = self.pg0.get_capture(1, timeout=timeout) + packet = rx_list[0] + self.validate_packet(packet, msg_type, is_resend=is_resend) + + def wait_for_solicit(self, timeout=None, is_resend=False): + self.wait_for_packet(DHCP6_Solicit, timeout, is_resend=is_resend) + + def wait_for_request(self, timeout=None, is_resend=False): + self.wait_for_packet(DHCP6_Request, timeout, is_resend=is_resend) + + def wait_for_renew(self, timeout=None, is_resend=False): + self.wait_for_packet(DHCP6_Renew, timeout, is_resend=is_resend) + + def wait_for_rebind(self, timeout=None, is_resend=False): + self.wait_for_packet(DHCP6_Rebind, timeout, is_resend=is_resend) + + def wait_for_release(self, timeout=None, is_resend=False): + self.wait_for_packet(DHCP6_Release, timeout, is_resend=is_resend) + + def send_packet(self, msg_type, t1=None, t2=None, iapdopt=None): + if t1 is None: + t1 = self.T1 + if t2 is None: + t2 = self.T2 + if iapdopt is None: + opt_ia_pd = DHCP6OptIA_PD(iaid=1, T1=t1, T2=t2) + else: + opt_ia_pd = DHCP6OptIA_PD(iaid=1, T1=t1, T2=t2, iapdopt=iapdopt) + p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / + IPv6(src=mk_ll_addr(self.pg0.remote_mac), + dst=self.pg0.local_ip6_ll) / + UDP(sport=547, dport=546) / + msg_type(trid=self.trid) / + DHCP6OptServerId(duid=self.server_duid) / + DHCP6OptClientId(duid=self.client_duid) / + opt_ia_pd + ) + self.pg0.add_stream([p]) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + def send_advertise(self, t1=None, t2=None, iapdopt=None): + self.send_packet(DHCP6_Advertise, t1, t2, iapdopt) + + def send_reply(self, t1=None, t2=None, iapdopt=None): + self.send_packet(DHCP6_Reply, t1, t2, iapdopt) + + def test_T1_and_T2_timeouts(self): + """ Test T1 and T2 timeouts """ + + self.wait_for_solicit() + self.send_advertise() + self.wait_for_request() + self.send_reply() + + self.sleep(1) + + self.wait_for_renew() + + self.pg_enable_capture(self.pg_interfaces) + + self.sleep(1) + + self.wait_for_rebind() + + def test_prefixes(self): + """ Test handling of prefixes """ + address_bin_1 = None + address_bin_2 = None + try: + address_bin_1 = '\x00' * 6 + '\x00\x02' + '\x00' * 6 + '\x04\x05' + address_prefix_length_1 = 60 + self.vapi.ip6_add_del_address_using_prefix(self.pg1.sw_if_index, + address_bin_1, + address_prefix_length_1, + self.prefix_group) + + ia_pd_opts = DHCP6OptIAPrefix(prefix='7:8::', plen=56, preflft=2, + validlft=3) + + self.wait_for_solicit() + self.send_advertise(t1=20, t2=40, iapdopt=ia_pd_opts) + self.wait_for_request() + self.send_reply(t1=20, t2=40, iapdopt=ia_pd_opts) + self.sleep(0.1) + + # check FIB for new address + new_addresses = self.get_addresses() + self.assertEqual(len(new_addresses), 1) + addr = list(new_addresses)[0] + self.assertEqual(inet_ntop(AF_INET6, addr), '7:8:0:2::405') + + self.sleep(1) + + address_bin_2 = '\x00' * 6 + '\x00\x76' + '\x00' * 6 + '\x04\x06' + address_prefix_length_2 = 62 + self.vapi.ip6_add_del_address_using_prefix(self.pg1.sw_if_index, + address_bin_2, + address_prefix_length_2, + self.prefix_group) + + self.sleep(1) + + # check FIB contains 2 addresses + fib = self.vapi.ip6_fib_dump() + addresses = set(self.get_interface_addresses(fib, self.pg1)) + new_addresses = addresses.difference(self.initial_addresses) + self.assertEqual(len(new_addresses), 2) + addr1 = list(new_addresses)[0] + addr2 = list(new_addresses)[1] + if inet_ntop(AF_INET6, addr1) == '7:8:0:76::406': + addr1, addr2 = addr2, addr1 + self.assertEqual(inet_ntop(AF_INET6, addr1), '7:8:0:2::405') + self.assertEqual(inet_ntop(AF_INET6, addr2), '7:8:0:76::406') + + self.sleep(1) + + # check that the addresses are deleted + fib = self.vapi.ip6_fib_dump() + addresses = set(self.get_interface_addresses(fib, self.pg1)) + new_addresses = addresses.difference(self.initial_addresses) + self.assertEqual(len(new_addresses), 0) + + finally: + if address_bin_1 is not None: + self.vapi.ip6_add_del_address_using_prefix( + self.pg1.sw_if_index, address_bin_1, + address_prefix_length_1, self.prefix_group, is_add=0) + if address_bin_2 is not None: + self.vapi.ip6_add_del_address_using_prefix( + self.pg1.sw_if_index, address_bin_2, + address_prefix_length_2, self.prefix_group, is_add=0) + + def test_sending_client_messages_solicit(self): + """ VPP receives messages from DHCPv6 client """ + + self.wait_for_solicit() + self.send_packet(DHCP6_Solicit) + self.send_packet(DHCP6_Request) + self.send_packet(DHCP6_Renew) + self.send_packet(DHCP6_Rebind) + self.sleep(1) + self.wait_for_solicit(is_resend=True) + + def test_sending_inapropriate_packets(self): + """ Server sends messages with inapropriate message types """ + + self.wait_for_solicit() + self.send_reply() + self.wait_for_solicit(is_resend=True) + self.send_advertise() + self.wait_for_request() + self.send_advertise() + self.wait_for_request(is_resend=True) + self.send_reply() + self.wait_for_renew() + + def test_no_prefix_available_in_advertise(self): + """ Advertise message contains NoPrefixAvail status code """ + + self.wait_for_solicit() + noavail = DHCP6OptStatusCode(statuscode=6) # NoPrefixAvail + self.send_advertise(iapdopt=noavail) + self.wait_for_solicit(is_resend=True) + + def test_preferred_greater_than_valit_lifetime(self): + """ Preferred lifetime is greater than valid lifetime """ + + try: + address_bin = '\x00' * 6 + '\x00\x02' + '\x00' * 6 + '\x04\x05' + address_prefix_length = 60 + self.vapi.ip6_add_del_address_using_prefix(self.pg1.sw_if_index, + address_bin, + address_prefix_length, + self.prefix_group) + + self.wait_for_solicit() + self.send_advertise() + self.wait_for_request() + ia_pd_opts = DHCP6OptIAPrefix(prefix='7:8::', plen=56, preflft=4, + validlft=3) + self.send_reply(iapdopt=ia_pd_opts) + + self.sleep(0.5) + + # check FIB contains no addresses + fib = self.vapi.ip6_fib_dump() + addresses = set(self.get_interface_addresses(fib, self.pg1)) + new_addresses = addresses.difference(self.initial_addresses) + self.assertEqual(len(new_addresses), 0) + + finally: + self.vapi.ip6_add_del_address_using_prefix(self.pg1.sw_if_index, + address_bin, + address_prefix_length, + self.prefix_group, + is_add=0) + + def test_T1_greater_than_T2(self): + """ T1 is greater than T2 """ + + try: + address_bin = '\x00' * 6 + '\x00\x02' + '\x00' * 6 + '\x04\x05' + address_prefix_length = 60 + self.vapi.ip6_add_del_address_using_prefix(self.pg1.sw_if_index, + address_bin, + address_prefix_length, + self.prefix_group) + + self.wait_for_solicit() + self.send_advertise() + self.wait_for_request() + ia_pd_opts = DHCP6OptIAPrefix(prefix='7:8::', plen=56, preflft=4, + validlft=8) + self.send_reply(t1=80, t2=40, iapdopt=ia_pd_opts) + + self.sleep(0.5) + + # check FIB contains no addresses + fib = self.vapi.ip6_fib_dump() + addresses = set(self.get_interface_addresses(fib, self.pg1)) + new_addresses = addresses.difference(self.initial_addresses) + self.assertEqual(len(new_addresses), 0) + + finally: + self.vapi.ip6_add_del_address_using_prefix(self.pg1.sw_if_index, + address_bin, + address_prefix_length, + self.prefix_group, + is_add=0) diff --git a/test/vpp_papi_provider.py b/test/vpp_papi_provider.py index d13508ba4e8..1a09d8c5e21 100644 --- a/test/vpp_papi_provider.py +++ b/test/vpp_papi_provider.py @@ -503,6 +503,50 @@ class VppPapiProvider(object): 'learn_limit': learn_limit, 'pid': os.getpid(), }) + def want_dhcp6_pd_reply_events(self, enable_disable=1): + return self.api(self.papi.want_dhcp6_pd_reply_events, + {'enable_disable': enable_disable, + 'pid': os.getpid()}) + + def dhcp6_clients_enable_disable(self, enable=1): + return self.api(self.papi.dhcp6_clients_enable_disable, + {'enable': enable}) + + def dhcp6_pd_send_client_message(self, msg_type, sw_if_index, T1, T2, + prefixes, server_index=0xFFFFFFFF, + irt=0, mrt=0, mrc=1, mrd=0, stop=0, + ): + return self.api(self.papi.dhcp6_pd_send_client_message, + {'sw_if_index': sw_if_index, + 'server_index': server_index, + 'irt': irt, + 'mrt': mrt, + 'mrc': mrc, + 'mrd': mrd, + 'stop': stop, + 'msg_type': msg_type, + 'T1': T1, + 'T2': T2, + 'n_prefixes': len(prefixes), + 'prefixes': prefixes}) + + def dhcp6_pd_client_enable_disable(self, sw_if_index, prefix_group='', + enable=1): + return self.api(self.papi.dhcp6_pd_client_enable_disable, + {'sw_if_index': sw_if_index, + 'prefix_group': prefix_group, + 'enable': enable}) + + def ip6_add_del_address_using_prefix(self, sw_if_index, address, + prefix_length, prefix_group, + is_add=1): + return self.api(self.papi.ip6_add_del_address_using_prefix, + {'sw_if_index': sw_if_index, + 'prefix_group': prefix_group, + 'address': address, + 'prefix_length': prefix_length, + 'is_add': is_add}) + def l2fib_add_del(self, mac, bd_id, sw_if_index, is_add=1, static_mac=0, filter_mac=0, bvi_mac=0): """Create/delete L2 FIB entry. -- 2.16.6