diff options
Diffstat (limited to 'src/plugins/dhcp')
31 files changed, 13878 insertions, 0 deletions
diff --git a/src/plugins/dhcp/CMakeLists.txt b/src/plugins/dhcp/CMakeLists.txt new file mode 100644 index 00000000000..b2dd630d461 --- /dev/null +++ b/src/plugins/dhcp/CMakeLists.txt @@ -0,0 +1,51 @@ +# 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. + +add_vpp_plugin(dhcp + SOURCES + client.c + dhcp_api.c + dhcp_client_detect.c + dhcp_proxy.c + dhcp4_proxy_node.c + dhcp6_client_common_dp.c + dhcp6_ia_na_client_dp.c + dhcp6_ia_na_client_cp.c + dhcp6_ia_na_client_cp_api.c + dhcp6_pd_client_cp.c + dhcp6_pd_client_cp_api.c + dhcp6_pd_client_dp.c + dhcp6_proxy_node.c + + MULTIARCH_SOURCES + dhcp_client_detect.c + + API_FILES + dhcp.api + dhcp6_pd_client_cp.api + dhcp6_ia_na_client_cp.api + + INSTALL_HEADERS + client.h + dhcp4_packet.h + dhcp6_packet.h + dhcp_proxy.h + dhcp6_proxy_error.def + dhcp4_proxy_error.def + dhcp6_client_common_dp.h + dhcp6_pd_client_dp.h + dhcp6_ia_na_client_dp.h + + API_TEST_SOURCES + dhcp_test.c +) diff --git a/src/plugins/dhcp/client.c b/src/plugins/dhcp/client.c new file mode 100644 index 00000000000..800da3e504a --- /dev/null +++ b/src/plugins/dhcp/client.c @@ -0,0 +1,1255 @@ +/* + * Copyright (c) 2015 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 <vlib/vlib.h> +#include <vlibmemory/api.h> +#include <dhcp/client.h> +#include <dhcp/dhcp_proxy.h> +#include <vnet/fib/fib_table.h> +#include <vnet/qos/qos_types.h> + +dhcp_client_main_t dhcp_client_main; +static u8 *format_dhcp_client_state (u8 * s, va_list * va); +static vlib_node_registration_t dhcp_client_process_node; + +#define foreach_dhcp_sent_packet_stat \ +_(DISCOVER, "DHCP discover packets sent") \ +_(OFFER, "DHCP offer packets sent") \ +_(REQUEST, "DHCP request packets sent") \ +_(ACK, "DHCP ack packets sent") + +#define foreach_dhcp_error_counter \ +_(NOT_FOR_US, "DHCP packets for other hosts, dropped") \ +_(NAK, "DHCP nak packets received") \ +_(NON_OFFER_DISCOVER, "DHCP non-offer packets in discover state") \ +_(ODDBALL, "DHCP non-ack, non-offer packets received") \ +_(BOUND, "DHCP bind success") + +typedef enum +{ +#define _(sym,str) DHCP_STAT_##sym, + foreach_dhcp_sent_packet_stat foreach_dhcp_error_counter +#undef _ + DHCP_STAT_UNKNOWN, + DHCP_STAT_N_STAT, +} sample_error_t; + +static char *dhcp_client_process_stat_strings[] = { +#define _(sym,string) string, + foreach_dhcp_sent_packet_stat foreach_dhcp_error_counter +#undef _ + "DHCP unknown packets sent", +}; + +static void +dhcp_client_acquire_address (dhcp_client_main_t * dcm, dhcp_client_t * c) +{ + /* + * Install any/all info gleaned from dhcp, right here + */ + ip4_add_del_interface_address (dcm->vlib_main, c->sw_if_index, + (void *) &c->leased_address, + c->subnet_mask_width, 0 /*is_del */ ); +} + +static void +dhcp_client_release_address (dhcp_client_main_t * dcm, dhcp_client_t * c) +{ + /* + * Remove any/all info gleaned from dhcp, right here. Caller(s) + * have not wiped out the info yet. + */ + + ip4_add_del_interface_address (dcm->vlib_main, c->sw_if_index, + (void *) &c->leased_address, + c->subnet_mask_width, 1 /*is_del */ ); +} + +static void +dhcp_client_proc_callback (uword * client_index) +{ + vlib_main_t *vm = vlib_get_main (); + ASSERT (vlib_get_thread_index () == 0); + vlib_process_signal_event (vm, dhcp_client_process_node.index, + EVENT_DHCP_CLIENT_WAKEUP, *client_index); +} + +static void +dhcp_client_addr_callback (dhcp_client_t * c) +{ + dhcp_client_main_t *dcm = &dhcp_client_main; + + /* disable the feature */ + vnet_feature_enable_disable ("ip4-unicast", + "ip4-dhcp-client-detect", + c->sw_if_index, 0 /* disable */ , 0, 0); + c->client_detect_feature_enabled = 0; + + /* if renewing the lease, the address and route have already been added */ + if (c->state == DHCP_BOUND) + return; + + /* add the address to the interface */ + dhcp_client_acquire_address (dcm, c); + + /* + * Configure default IP route: + */ + if (c->router_address.as_u32) + { + fib_prefix_t all_0s = { + .fp_len = 0, + .fp_addr.ip4.as_u32 = 0x0, + .fp_proto = FIB_PROTOCOL_IP4, + }; + ip46_address_t nh = { + .ip4 = c->router_address, + }; + + /* *INDENT-OFF* */ + fib_table_entry_path_add ( + fib_table_get_index_for_sw_if_index ( + FIB_PROTOCOL_IP4, + c->sw_if_index), + &all_0s, + FIB_SOURCE_DHCP, + FIB_ENTRY_FLAG_NONE, + DPO_PROTO_IP4, + &nh, c->sw_if_index, + ~0, 1, NULL, // no label stack + FIB_ROUTE_PATH_FLAG_NONE); + /* *INDENT-ON* */ + } + if (c->dhcp_server.as_u32) + { + ip46_address_t dst = { + .ip4 = c->dhcp_server, + }; + c->ai_ucast = adj_nbr_add_or_lock (FIB_PROTOCOL_IP4, + VNET_LINK_IP4, &dst, c->sw_if_index); + } + + /* + * Call the user's event callback to report DHCP information + */ + if (c->event_callback) + c->event_callback (c->client_index, c); +} + +/* + * dhcp_client_for_us - server-to-client callback. + * Called from proxy_node.c:dhcp_proxy_to_client_input(). + * This function first decides that the packet in question is + * actually for the dhcp client code in case we're also acting as + * a dhcp proxy. Ay caramba, what a folly! + */ +int +dhcp_client_for_us (u32 bi, vlib_buffer_t * b, + ip4_header_t * ip, + udp_header_t * udp, dhcp_header_t * dhcp) +{ + dhcp_client_main_t *dcm = &dhcp_client_main; + vlib_main_t *vm = dcm->vlib_main; + dhcp_client_t *c; + uword *p; + f64 now = vlib_time_now (dcm->vlib_main); + u8 dhcp_message_type = 0; + dhcp_option_t *o; + + /* + * Doing dhcp client on this interface? + * Presumably we will always receive dhcp clnt for-us pkts on + * the interface that's asking for an address. + */ + p = hash_get (dcm->client_by_sw_if_index, + vnet_buffer (b)->sw_if_index[VLIB_RX]); + if (p == 0) + return 0; /* no */ + + c = pool_elt_at_index (dcm->clients, p[0]); + + /* Mixing dhcp relay and dhcp proxy? DGMS... */ + if (c->state == DHCP_BOUND && c->retry_count == 0) + return 0; + + /* Packet not for us? Turf it... */ + if (memcmp (dhcp->client_hardware_address, c->client_hardware_address, + sizeof (c->client_hardware_address))) + { + vlib_node_increment_counter (vm, dhcp_client_process_node.index, + DHCP_STAT_NOT_FOR_US, 1); + return 0; + } + + /* parse through the packet, learn what we can */ + if (dhcp->your_ip_address.as_u32) + c->leased_address.as_u32 = dhcp->your_ip_address.as_u32; + + c->dhcp_server.as_u32 = dhcp->server_ip_address.as_u32; + + o = (dhcp_option_t *) dhcp->options; + + while (o->option != 0xFF /* end of options */ && + (u8 *) o < (b->data + b->current_data + b->current_length)) + { + switch (o->option) + { + case 53: /* dhcp message type */ + dhcp_message_type = o->data[0]; + break; + + case 51: /* lease time */ + { + u32 lease_time_in_seconds = + clib_host_to_net_u32 (o->data_as_u32[0]); + // for debug: lease_time_in_seconds = 20; /*$$$$*/ + c->lease_expires = now + (f64) lease_time_in_seconds; + c->lease_lifetime = lease_time_in_seconds; + /* Set a sensible default, in case we don't get opt 58 */ + c->lease_renewal_interval = lease_time_in_seconds / 2; + } + break; + + case 58: /* lease renew time in seconds */ + { + u32 lease_renew_time_in_seconds = + clib_host_to_net_u32 (o->data_as_u32[0]); + c->lease_renewal_interval = lease_renew_time_in_seconds; + } + break; + + case 54: /* dhcp server address */ + c->dhcp_server.as_u32 = o->data_as_u32[0]; + break; + + case 1: /* subnet mask */ + { + u32 subnet_mask = clib_host_to_net_u32 (o->data_as_u32[0]); + c->subnet_mask_width = count_set_bits (subnet_mask); + } + break; + case 3: /* router address */ + { + u32 router_address = o->data_as_u32[0]; + c->router_address.as_u32 = router_address; + } + break; + case 6: /* domain server address */ + { + vec_free (c->domain_server_address); + vec_validate (c->domain_server_address, + o->length / sizeof (ip4_address_t) - 1); + clib_memcpy (c->domain_server_address, o->data, o->length); + } + break; + case 12: /* hostname */ + { + /* Replace the existing hostname if necessary */ + vec_free (c->hostname); + vec_validate (c->hostname, o->length - 1); + clib_memcpy (c->hostname, o->data, o->length); + } + break; + + /* $$$$ Your message in this space, parse more options */ + default: + break; + } + + o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); + } + + switch (c->state) + { + case DHCP_DISCOVER: + if (dhcp_message_type != DHCP_PACKET_OFFER) + { + vlib_node_increment_counter (vm, dhcp_client_process_node.index, + DHCP_STAT_NON_OFFER_DISCOVER, 1); + c->next_transmit = now + 5.0; + break; + } + + /* Received an offer, go send a request */ + c->state = DHCP_REQUEST; + c->retry_count = 0; + c->next_transmit = 0; /* send right now... */ + /* Poke the client process, which will send the request */ + uword client_id = c - dcm->clients; + vl_api_rpc_call_main_thread (dhcp_client_proc_callback, + (u8 *) & client_id, sizeof (uword)); + break; + + case DHCP_BOUND: + case DHCP_REQUEST: + if (dhcp_message_type == DHCP_PACKET_NAK) + { + vlib_node_increment_counter (vm, dhcp_client_process_node.index, + DHCP_STAT_NAK, 1); + /* Probably never happens in bound state, but anyhow... */ + if (c->state == DHCP_BOUND) + { + ip4_add_del_interface_address (dcm->vlib_main, c->sw_if_index, + (void *) &c->leased_address, + c->subnet_mask_width, + 1 /*is_del */ ); + vnet_feature_enable_disable ("ip4-unicast", + "ip4-dhcp-client-detect", + c->sw_if_index, 1 /* enable */ , + 0, 0); + c->client_detect_feature_enabled = 1; + } + /* Wipe out any memory of the address we had... */ + c->state = DHCP_DISCOVER; + c->next_transmit = now; + c->retry_count = 0; + c->leased_address.as_u32 = 0; + c->subnet_mask_width = 0; + c->router_address.as_u32 = 0; + c->lease_renewal_interval = 0; + c->dhcp_server.as_u32 = 0; + vec_free (c->domain_server_address); + break; + } + + if (dhcp_message_type != DHCP_PACKET_ACK && + dhcp_message_type != DHCP_PACKET_OFFER) + { + vlib_node_increment_counter (vm, dhcp_client_process_node.index, + DHCP_STAT_NON_OFFER_DISCOVER, 1); + clib_warning ("sw_if_index %d state %U message type %d", + c->sw_if_index, format_dhcp_client_state, + c->state, dhcp_message_type); + c->next_transmit = now + 5.0; + break; + } + /* OK, we own the address (etc), add to the routing table(s) */ + vl_api_rpc_call_main_thread (dhcp_client_addr_callback, + (u8 *) c, sizeof (*c)); + + c->state = DHCP_BOUND; + c->retry_count = 0; + c->next_transmit = now + (f64) c->lease_renewal_interval; + c->lease_expires = now + (f64) c->lease_lifetime; + vlib_node_increment_counter (vm, dhcp_client_process_node.index, + DHCP_STAT_BOUND, 1); + break; + + default: + clib_warning ("client %d bogus state %d", c - dcm->clients, c->state); + break; + } + + /* drop the pkt, return 1 */ + vlib_buffer_free (vm, &bi, 1); + return 1; +} + +static void +send_dhcp_pkt (dhcp_client_main_t * dcm, dhcp_client_t * c, + dhcp_packet_type_t type, int is_broadcast) +{ + vlib_main_t *vm = dcm->vlib_main; + vnet_main_t *vnm = dcm->vnet_main; + vnet_hw_interface_t *hw = vnet_get_sup_hw_interface (vnm, c->sw_if_index); + vnet_sw_interface_t *sup_sw + = vnet_get_sup_sw_interface (vnm, c->sw_if_index); + vnet_sw_interface_t *sw = vnet_get_sw_interface (vnm, c->sw_if_index); + vlib_buffer_t *b; + u32 bi; + ip4_header_t *ip; + udp_header_t *udp; + dhcp_header_t *dhcp; + u32 *to_next; + vlib_frame_t *f; + dhcp_option_t *o; + u16 udp_length, ip_length; + u32 counter_index, node_index; + + /* Interface(s) down? */ + if ((hw->flags & VNET_HW_INTERFACE_FLAG_LINK_UP) == 0) + return; + if ((sup_sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0) + return; + if ((sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP) == 0) + return; + + if (vlib_buffer_alloc (vm, &bi, 1) != 1) + { + clib_warning ("buffer allocation failure"); + c->next_transmit = 0; + return; + } + + /* Build a dhcpv4 pkt from whole cloth */ + b = vlib_get_buffer (vm, bi); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (b); + + ASSERT (b->current_data == 0); + + vnet_buffer (b)->sw_if_index[VLIB_RX] = c->sw_if_index; + + if (is_broadcast) + { + node_index = ip4_rewrite_node.index; + vnet_buffer (b)->ip.adj_index[VLIB_TX] = c->ai_bcast; + } + else + { + ip_adjacency_t *adj = adj_get (c->ai_ucast); + + if (IP_LOOKUP_NEXT_ARP == adj->lookup_next_index) + node_index = ip4_arp_node.index; + else + node_index = ip4_rewrite_node.index; + vnet_buffer (b)->ip.adj_index[VLIB_TX] = c->ai_ucast; + } + + /* Enqueue the packet right now */ + f = vlib_get_frame_to_node (vm, node_index); + to_next = vlib_frame_vector_args (f); + to_next[0] = bi; + f->n_vectors = 1; + vlib_put_frame_to_node (vm, node_index, f); + + /* build the headers */ + ip = vlib_buffer_get_current (b); + udp = (udp_header_t *) (ip + 1); + dhcp = (dhcp_header_t *) (udp + 1); + + /* $$$ optimize, maybe */ + clib_memset (ip, 0, sizeof (*ip) + sizeof (*udp) + sizeof (*dhcp)); + + ip->ip_version_and_header_length = 0x45; + ip->ttl = 128; + ip->protocol = IP_PROTOCOL_UDP; + + ip->tos = c->dscp; + + if (ip->tos) + { + /* + * Setup the buffer's QoS settings so any QoS marker on the egress + * interface, that might set VLAN CoS bits, based on this DSCP setting + */ + vnet_buffer2 (b)->qos.source = QOS_SOURCE_IP; + vnet_buffer2 (b)->qos.bits = ip->tos; + b->flags |= VNET_BUFFER_F_QOS_DATA_VALID; + } + + if (is_broadcast) + { + /* src = 0.0.0.0, dst = 255.255.255.255 */ + ip->dst_address.as_u32 = ~0; + } + else + { + /* Renewing an active lease, plain old ip4 src/dst */ + ip->src_address.as_u32 = c->leased_address.as_u32; + ip->dst_address.as_u32 = c->dhcp_server.as_u32; + } + + udp->src_port = clib_host_to_net_u16 (UDP_DST_PORT_dhcp_to_client); + udp->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_dhcp_to_server); + + /* Send the interface MAC address */ + clib_memcpy (dhcp->client_hardware_address, + vnet_sw_interface_get_hw_address (vnm, c->sw_if_index), 6); + + /* And remember it for rx-packet-for-us checking */ + clib_memcpy (c->client_hardware_address, dhcp->client_hardware_address, + sizeof (c->client_hardware_address)); + + /* Lease renewal, set up client_ip_address */ + if (is_broadcast == 0) + dhcp->client_ip_address.as_u32 = c->leased_address.as_u32; + + dhcp->opcode = 1; /* request, all we send */ + dhcp->hardware_type = 1; /* ethernet */ + dhcp->hardware_address_length = 6; + dhcp->transaction_identifier = c->transaction_id; + dhcp->flags = + clib_host_to_net_u16 (is_broadcast && c->set_broadcast_flag ? + DHCP_FLAG_BROADCAST : 0); + dhcp->magic_cookie.as_u32 = DHCP_MAGIC; + + o = (dhcp_option_t *) dhcp->options; + + /* Send option 53, the DHCP message type */ + o->option = DHCP_PACKET_OPTION_MSG_TYPE; + o->length = 1; + o->data[0] = type; + o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); + + /* Send option 57, max msg length */ + if (0 /* not needed, apparently */ ) + { + o->option = 57; + o->length = 2; + { + u16 *o2 = (u16 *) o->data; + *o2 = clib_host_to_net_u16 (1152); + o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); + } + } + + /* + * If server ip address is available with non-zero value, + * option 54 (DHCP Server Identifier) is sent. + */ + if (c->dhcp_server.as_u32) + { + o->option = 54; + o->length = 4; + clib_memcpy (o->data, &c->dhcp_server.as_u32, 4); + o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); + } + + /* send option 50, requested IP address */ + if (c->leased_address.as_u32) + { + o->option = 50; + o->length = 4; + clib_memcpy (o->data, &c->leased_address.as_u32, 4); + o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); + } + + /* send option 12, host name */ + if (vec_len (c->hostname)) + { + o->option = 12; + o->length = vec_len (c->hostname); + clib_memcpy (o->data, c->hostname, vec_len (c->hostname)); + o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); + } + + /* send option 61, client_id */ + if (vec_len (c->client_identifier)) + { + o->option = 61; + o->length = vec_len (c->client_identifier); + clib_memcpy (o->data, c->client_identifier, + vec_len (c->client_identifier)); + o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); + } + + /* $$ maybe send the client s/w version if anyone cares */ + + /* + * send option 55, parameter request list + * The current list - see below, matches the Linux dhcp client's list + * Any specific dhcp server config and/or dhcp server may or may + * not yield specific options. + */ + o->option = 55; + o->length = vec_len (c->option_55_data); + clib_memcpy (o->data, c->option_55_data, vec_len (c->option_55_data)); + o = (dhcp_option_t *) (((uword) o) + (o->length + 2)); + + /* End of list */ + o->option = 0xff; + o->length = 0; + o++; + + b->current_length = ((u8 *) o) - b->data; + + /* fix ip length, checksum and udp length */ + ip_length = vlib_buffer_length_in_chain (vm, b); + + ip->length = clib_host_to_net_u16 (ip_length); + ip->checksum = ip4_header_checksum (ip); + + udp_length = ip_length - (sizeof (*ip)); + udp->length = clib_host_to_net_u16 (udp_length); + + switch (type) + { +#define _(a,b) case DHCP_PACKET_##a: {counter_index = DHCP_STAT_##a; break;} + foreach_dhcp_sent_packet_stat +#undef _ + default: + counter_index = DHCP_STAT_UNKNOWN; + break; + } + + vlib_node_increment_counter (vm, dhcp_client_process_node.index, + counter_index, 1); +} + +static int +dhcp_discover_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now) +{ + /* + * State machine "DISCOVER" state. Send a dhcp discover packet, + * eventually back off the retry rate. + */ + + if (c->client_detect_feature_enabled == 0) + { + vnet_feature_enable_disable ("ip4-unicast", + "ip4-dhcp-client-detect", + c->sw_if_index, 1 /* enable */ , 0, 0); + c->client_detect_feature_enabled = 1; + } + + send_dhcp_pkt (dcm, c, DHCP_PACKET_DISCOVER, 1 /* is_broadcast */ ); + + c->retry_count++; + if (c->retry_count > 10) + c->next_transmit = now + 5.0; + else + c->next_transmit = now + 1.0; + return 0; +} + +static int +dhcp_request_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now) +{ + /* + * State machine "REQUEST" state. Send a dhcp request packet, + * eventually drop back to the discover state. + */ + send_dhcp_pkt (dcm, c, DHCP_PACKET_REQUEST, 1 /* is_broadcast */ ); + + c->retry_count++; + if (c->retry_count > 7 /* lucky you */ ) + { + c->state = DHCP_DISCOVER; + c->next_transmit = now; + c->retry_count = 0; + return 1; + } + c->next_transmit = now + 1.0; + return 0; +} + +static int +dhcp_bound_state (dhcp_client_main_t * dcm, dhcp_client_t * c, f64 now) +{ + /* + * State machine "BOUND" state. Send a dhcp request packet to renew + * the lease. + * Eventually, when the lease expires, forget the dhcp data + * and go back to the stone age. + */ + + /* + * We disable the client detect feature when we bind a + * DHCP address. Turn it back on again on first renew attempt. + * Otherwise, if the DHCP server replies we'll never see it. + */ + if (c->client_detect_feature_enabled == 0) + { + vnet_feature_enable_disable ("ip4-unicast", + "ip4-dhcp-client-detect", + c->sw_if_index, 1 /* enable */ , 0, 0); + c->client_detect_feature_enabled = 1; + } + + send_dhcp_pkt (dcm, c, DHCP_PACKET_REQUEST, 0 /* is_broadcast */ ); + + c->retry_count++; + if (c->retry_count > 10) + c->next_transmit = now + 5.0; + else + c->next_transmit = now + 1.0; + + if (now > c->lease_expires) + { + /* Remove the default route */ + if (c->router_address.as_u32) + { + fib_prefix_t all_0s = { + .fp_len = 0, + .fp_addr.ip4.as_u32 = 0x0, + .fp_proto = FIB_PROTOCOL_IP4, + }; + ip46_address_t nh = { + .ip4 = c->router_address, + }; + + fib_table_entry_path_remove (fib_table_get_index_for_sw_if_index + (FIB_PROTOCOL_IP4, c->sw_if_index), + &all_0s, FIB_SOURCE_DHCP, + DPO_PROTO_IP4, &nh, c->sw_if_index, ~0, + 1, FIB_ROUTE_PATH_FLAG_NONE); + } + /* Remove the interface address */ + dhcp_client_release_address (dcm, c); + c->state = DHCP_DISCOVER; + c->next_transmit = now; + c->retry_count = 0; + /* Wipe out any memory of the address we had... */ + c->leased_address.as_u32 = 0; + c->subnet_mask_width = 0; + c->router_address.as_u32 = 0; + c->lease_renewal_interval = 0; + c->dhcp_server.as_u32 = 0; + return 1; + } + return 0; +} + +static f64 +dhcp_client_sm (f64 now, f64 timeout, uword pool_index) +{ + dhcp_client_main_t *dcm = &dhcp_client_main; + dhcp_client_t *c; + + /* deleted, pooched, yadda yadda yadda */ + if (pool_is_free_index (dcm->clients, pool_index)) + return timeout; + + c = pool_elt_at_index (dcm->clients, pool_index); + + /* Time for us to do something with this client? */ + if (now < c->next_transmit) + return timeout; + +again: + switch (c->state) + { + case DHCP_DISCOVER: /* send a discover */ + if (dhcp_discover_state (dcm, c, now)) + goto again; + break; + + case DHCP_REQUEST: /* send a request */ + if (dhcp_request_state (dcm, c, now)) + goto again; + break; + + case DHCP_BOUND: /* bound, renew needed? */ + if (dhcp_bound_state (dcm, c, now)) + goto again; + break; + + default: + clib_warning ("dhcp client %d bogus state %d", + c - dcm->clients, c->state); + break; + } + + if (c->next_transmit < now + timeout) + return c->next_transmit - now; + + return timeout; +} + +static uword +dhcp_client_process (vlib_main_t * vm, + vlib_node_runtime_t * rt, vlib_frame_t * f) +{ + f64 timeout = 100.0; + f64 now; + uword event_type; + uword *event_data = 0; + dhcp_client_main_t *dcm = &dhcp_client_main; + dhcp_client_t *c; + int i; + + while (1) + { + vlib_process_wait_for_event_or_clock (vm, timeout); + + event_type = vlib_process_get_events (vm, &event_data); + + now = vlib_time_now (vm); + + switch (event_type) + { + case EVENT_DHCP_CLIENT_WAKEUP: + for (i = 0; i < vec_len (event_data); i++) + timeout = dhcp_client_sm (now, timeout, event_data[i]); + break; + + case ~0: + /* *INDENT-OFF* */ + pool_foreach (c, dcm->clients, + ({ + timeout = dhcp_client_sm (now, timeout, + (uword) (c - dcm->clients)); + })); + /* *INDENT-ON* */ + if (pool_elts (dcm->clients) == 0) + timeout = 100.0; + break; + } + + vec_reset_length (event_data); + } + + /* NOTREACHED */ + return 0; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dhcp_client_process_node,static) = { + .function = dhcp_client_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "dhcp-client-process", + .process_log2_n_stack_bytes = 16, + .n_errors = ARRAY_LEN(dhcp_client_process_stat_strings), + .error_strings = dhcp_client_process_stat_strings, +}; +/* *INDENT-ON* */ + +static u8 * +format_dhcp_client_state (u8 * s, va_list * va) +{ + dhcp_client_state_t state = va_arg (*va, dhcp_client_state_t); + char *str = "BOGUS!"; + + switch (state) + { +#define _(a) \ + case a: \ + str = #a; \ + break; + foreach_dhcp_client_state; +#undef _ + default: + break; + } + + s = format (s, "%s", str); + return s; +} + +static u8 * +format_dhcp_client (u8 * s, va_list * va) +{ + dhcp_client_main_t *dcm = va_arg (*va, dhcp_client_main_t *); + dhcp_client_t *c = va_arg (*va, dhcp_client_t *); + int verbose = va_arg (*va, int); + ip4_address_t *addr; + + s = format (s, "[%d] %U state %U ", c - dcm->clients, + format_vnet_sw_if_index_name, dcm->vnet_main, c->sw_if_index, + format_dhcp_client_state, c->state); + + if (0 != c->dscp) + s = format (s, "dscp %d ", c->dscp); + + if (c->leased_address.as_u32) + { + s = format (s, "addr %U/%d gw %U", + format_ip4_address, &c->leased_address, + c->subnet_mask_width, format_ip4_address, + &c->router_address); + + vec_foreach (addr, c->domain_server_address) + s = format (s, " dns %U", format_ip4_address, addr); + } + else + { + s = format (s, "no address\n"); + } + + if (verbose) + { + s = + format (s, + "\n lease: lifetime:%d renewal-interval:%d expires:%.2f (now:%.2f)", + c->lease_lifetime, c->lease_renewal_interval, + c->lease_expires, vlib_time_now (dcm->vlib_main)); + s = + format (s, "\n retry-count:%d, next-xmt:%.2f", c->retry_count, + c->next_transmit); + s = + format (s, "\n adjacencies:[unicast:%d broadcast:%d]", c->ai_ucast, + c->ai_bcast); + } + return s; +} + +static clib_error_t * +show_dhcp_client_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + dhcp_client_main_t *dcm = &dhcp_client_main; + dhcp_client_t *c; + int verbose = 0; + u32 sw_if_index = ~0; + uword *p; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "intfc %U", + unformat_vnet_sw_interface, dcm->vnet_main, &sw_if_index)) + ; + else if (unformat (input, "verbose")) + verbose = 1; + else + break; + } + + if (sw_if_index != ~0) + { + p = hash_get (dcm->client_by_sw_if_index, sw_if_index); + if (p == 0) + return clib_error_return (0, "dhcp client not configured"); + c = pool_elt_at_index (dcm->clients, p[0]); + vlib_cli_output (vm, "%U", format_dhcp_client, dcm, c, verbose); + return 0; + } + + /* *INDENT-OFF* */ + pool_foreach (c, dcm->clients, + ({ + vlib_cli_output (vm, "%U", + format_dhcp_client, dcm, + c, verbose); + })); + /* *INDENT-ON* */ + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (show_dhcp_client_command, static) = { + .path = "show dhcp client", + .short_help = "show dhcp client [intfc <intfc>][verbose]", + .function = show_dhcp_client_command_fn, +}; +/* *INDENT-ON* */ + + +int +dhcp_client_add_del (dhcp_client_add_del_args_t * a) +{ + dhcp_client_main_t *dcm = &dhcp_client_main; + vlib_main_t *vm = dcm->vlib_main; + dhcp_client_t *c; + uword *p; + fib_prefix_t all_0s = { + .fp_len = 0, + .fp_addr.ip4.as_u32 = 0x0, + .fp_proto = FIB_PROTOCOL_IP4, + }; + + p = hash_get (dcm->client_by_sw_if_index, a->sw_if_index); + + if ((p && a->is_add) || (!p && a->is_add == 0)) + return VNET_API_ERROR_INVALID_VALUE; + + if (a->is_add) + { + dhcp_maybe_register_udp_ports (DHCP_PORT_REG_CLIENT); + pool_get (dcm->clients, c); + clib_memset (c, 0, sizeof (*c)); + c->state = DHCP_DISCOVER; + c->sw_if_index = a->sw_if_index; + c->client_index = a->client_index; + c->pid = a->pid; + c->event_callback = a->event_callback; + c->option_55_data = a->option_55_data; + c->hostname = a->hostname; + c->client_identifier = a->client_identifier; + c->set_broadcast_flag = a->set_broadcast_flag; + c->dscp = a->dscp; + c->ai_ucast = ADJ_INDEX_INVALID; + c->ai_bcast = adj_nbr_add_or_lock (FIB_PROTOCOL_IP4, + VNET_LINK_IP4, + &ADJ_BCAST_ADDR, c->sw_if_index); + + do + { + c->transaction_id = random_u32 (&dcm->seed); + } + while (c->transaction_id == 0); + + hash_set (dcm->client_by_sw_if_index, a->sw_if_index, c - dcm->clients); + + /* + * In order to accept any OFFER, whether broadcasted or unicasted, we + * need to configure the dhcp-client-detect feature as an input feature + * so the DHCP OFFER is sent to the ip4-local node. Without this a + * broadcasted OFFER hits the 255.255.255.255/32 address and a unicast + * hits 0.0.0.0/0 both of which default to drop and the latter may forward + * of box - not what we want. Nor to we want to change these route for + * all interfaces in this table + */ + vnet_feature_enable_disable ("ip4-unicast", + "ip4-dhcp-client-detect", + c->sw_if_index, 1 /* enable */ , 0, 0); + c->client_detect_feature_enabled = 1; + + vlib_process_signal_event (vm, dhcp_client_process_node.index, + EVENT_DHCP_CLIENT_WAKEUP, c - dcm->clients); + } + else + { + c = pool_elt_at_index (dcm->clients, p[0]); + + if (c->router_address.as_u32) + { + ip46_address_t nh = { + .ip4 = c->router_address, + }; + + fib_table_entry_path_remove (fib_table_get_index_for_sw_if_index + (FIB_PROTOCOL_IP4, c->sw_if_index), + &all_0s, FIB_SOURCE_DHCP, + DPO_PROTO_IP4, &nh, c->sw_if_index, ~0, + 1, FIB_ROUTE_PATH_FLAG_NONE); + } + dhcp_client_release_address (dcm, c); + + adj_unlock (c->ai_ucast); + adj_unlock (c->ai_bcast); + + vec_free (c->domain_server_address); + vec_free (c->option_55_data); + vec_free (c->hostname); + vec_free (c->client_identifier); + hash_unset (dcm->client_by_sw_if_index, c->sw_if_index); + pool_put (dcm->clients, c); + } + return 0; +} + +int +dhcp_client_config (u32 is_add, + u32 client_index, + vlib_main_t * vm, + u32 sw_if_index, + u8 * hostname, + u8 * client_id, + dhcp_event_cb_t event_callback, + u8 set_broadcast_flag, ip_dscp_t dscp, u32 pid) +{ + dhcp_client_add_del_args_t _a, *a = &_a; + int rv; + + clib_memset (a, 0, sizeof (*a)); + a->is_add = is_add; + a->sw_if_index = sw_if_index; + a->client_index = client_index; + a->pid = pid; + a->event_callback = event_callback; + a->set_broadcast_flag = set_broadcast_flag; + a->dscp = dscp; + vec_validate (a->hostname, strlen ((char *) hostname) - 1); + strncpy ((char *) a->hostname, (char *) hostname, vec_len (a->hostname)); + vec_validate (a->client_identifier, strlen ((char *) client_id) - 1); + strncpy ((char *) a->client_identifier, (char *) client_id, + vec_len (a->client_identifier)); + + /* + * Option 55 request list. These data precisely match + * the Ubuntu dhcp client. YMMV. + */ + + /* Subnet Mask */ + vec_add1 (a->option_55_data, 1); + /* Broadcast address */ + vec_add1 (a->option_55_data, 28); + /* time offset */ + vec_add1 (a->option_55_data, 2); + /* Router */ + vec_add1 (a->option_55_data, 3); + /* Domain Name */ + vec_add1 (a->option_55_data, 15); + /* DNS */ + vec_add1 (a->option_55_data, 6); + /* Domain search */ + vec_add1 (a->option_55_data, 119); + /* Host name */ + vec_add1 (a->option_55_data, 12); + /* NetBIOS name server */ + vec_add1 (a->option_55_data, 44); + /* NetBIOS Scope */ + vec_add1 (a->option_55_data, 47); + /* MTU */ + vec_add1 (a->option_55_data, 26); + /* Classless static route */ + vec_add1 (a->option_55_data, 121); + /* NTP servers */ + vec_add1 (a->option_55_data, 42); + + rv = dhcp_client_add_del (a); + + switch (rv) + { + case 0: + break; + + case VNET_API_ERROR_INVALID_VALUE: + + vec_free (a->hostname); + vec_free (a->client_identifier); + vec_free (a->option_55_data); + + if (is_add) + clib_warning ("dhcp client already enabled on intf_idx %d", + sw_if_index); + else + clib_warning ("dhcp client not enabled on on intf_idx %d", + sw_if_index); + break; + + default: + clib_warning ("dhcp_client_add_del returned %d", rv); + } + + return rv; +} + +void +dhcp_client_walk (dhcp_client_walk_cb_t cb, void *ctx) +{ + dhcp_client_main_t *dcm = &dhcp_client_main; + dhcp_client_t *c; + + /* *INDENT-OFF* */ + pool_foreach (c, dcm->clients, + ({ + if (!cb(c, ctx)) + break; + })); + /* *INDENT-ON* */ + +} + +static clib_error_t * +dhcp_client_set_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + + dhcp_client_main_t *dcm = &dhcp_client_main; + u32 sw_if_index; + u8 *hostname = 0; + u8 sw_if_index_set = 0; + u8 set_broadcast_flag = 1; + int is_add = 1; + dhcp_client_add_del_args_t _a, *a = &_a; + int rv; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "intfc %U", + unformat_vnet_sw_interface, dcm->vnet_main, &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (input, "hostname %v", &hostname)) + ; + else if (unformat (input, "del")) + is_add = 0; + else if (unformat (input, "broadcast", &set_broadcast_flag)) + is_add = 0; + else + break; + } + + if (sw_if_index_set == 0) + return clib_error_return (0, "interface not specified"); + + clib_memset (a, 0, sizeof (*a)); + a->is_add = is_add; + a->sw_if_index = sw_if_index; + a->hostname = hostname; + a->client_identifier = format (0, "vpe 1.0%c", 0); + a->set_broadcast_flag = set_broadcast_flag; + + /* + * Option 55 request list. These data precisely match + * the Ubuntu dhcp client. YMMV. + */ + + /* Subnet Mask */ + vec_add1 (a->option_55_data, 1); + /* Broadcast address */ + vec_add1 (a->option_55_data, 28); + /* time offset */ + vec_add1 (a->option_55_data, 2); + /* Router */ + vec_add1 (a->option_55_data, 3); + /* Domain Name */ + vec_add1 (a->option_55_data, 15); + /* DNS */ + vec_add1 (a->option_55_data, 6); + /* Domain search */ + vec_add1 (a->option_55_data, 119); + /* Host name */ + vec_add1 (a->option_55_data, 12); + /* NetBIOS name server */ + vec_add1 (a->option_55_data, 44); + /* NetBIOS Scope */ + vec_add1 (a->option_55_data, 47); + /* MTU */ + vec_add1 (a->option_55_data, 26); + /* Classless static route */ + vec_add1 (a->option_55_data, 121); + /* NTP servers */ + vec_add1 (a->option_55_data, 42); + + rv = dhcp_client_add_del (a); + + switch (rv) + { + case 0: + break; + + case VNET_API_ERROR_INVALID_VALUE: + + vec_free (a->hostname); + vec_free (a->client_identifier); + vec_free (a->option_55_data); + if (is_add) + return clib_error_return (0, "dhcp client already enabled on %U", + format_vnet_sw_if_index_name, + dcm->vnet_main, sw_if_index); + else + return clib_error_return (0, "dhcp client not enabled on %U", + format_vnet_sw_if_index_name, + dcm->vnet_main, sw_if_index); + break; + + default: + vlib_cli_output (vm, "dhcp_client_add_del returned %d", rv); + } + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcp_client_set_command, static) = { + .path = "set dhcp client", + .short_help = "set dhcp client [del] intfc <interface> [hostname <name>]", + .function = dhcp_client_set_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dhcp_client_init (vlib_main_t * vm) +{ + dhcp_client_main_t *dcm = &dhcp_client_main; + + dcm->vlib_main = vm; + dcm->vnet_main = vnet_get_main (); + dcm->seed = (u32) clib_cpu_time_now (); + return 0; +} + +VLIB_INIT_FUNCTION (dhcp_client_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/client.h b/src/plugins/dhcp/client.h new file mode 100644 index 00000000000..68176abadf6 --- /dev/null +++ b/src/plugins/dhcp/client.h @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2015 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. + */ +/* + * client.h: dhcp client + */ + +#ifndef included_dhcp_client_h +#define included_dhcp_client_h + +#include <vnet/ip/ip.h> +#include <dhcp/dhcp4_packet.h> + +#define foreach_dhcp_client_state \ +_(DHCP_DISCOVER) \ +_(DHCP_REQUEST) \ +_(DHCP_BOUND) + +typedef enum +{ +#define _(a) a, + foreach_dhcp_client_state +#undef _ +} dhcp_client_state_t; + +struct dhcp_client_t_; + +/** + * Callback function for DHCP complete events + */ +typedef void (*dhcp_event_cb_t) (u32 client_index, + const struct dhcp_client_t_ * client); + +typedef struct dhcp_client_t_ +{ + dhcp_client_state_t state; + + /* the interface in question */ + u32 sw_if_index; + + /* State machine retry counter */ + u32 retry_count; + + /* Send next pkt at this time */ + f64 next_transmit; + f64 lease_expires; + + /* DHCP transaction ID, a random number */ + u32 transaction_id; + + /* leased address, other learned info DHCP */ + ip4_address_t leased_address; /* from your_ip_address field */ + ip4_address_t dhcp_server; + u32 subnet_mask_width; /* option 1 */ + ip4_address_t router_address; /* option 3 */ + ip4_address_t *domain_server_address; /* option 6 */ + u32 lease_renewal_interval; /* option 51 */ + u32 lease_lifetime; /* option 59 */ + + /* Requested data (option 55) */ + u8 *option_55_data; + + /* hostname and software client identifiers */ + u8 *hostname; + u8 *client_identifier; /* software version, e.g. vpe 1.0 */ + + /* Information used for event callback */ + u32 client_index; + u32 pid; + + /* Set the broadcast Flag in the Discover/Request messages */ + u8 set_broadcast_flag; + /* Interface MAC address, so we can do an rx-packet-for-us check */ + u8 client_hardware_address[6]; + u8 client_detect_feature_enabled; + + /* the unicast adjacency for the DHCP server */ + adj_index_t ai_ucast; + /* the broadcast adjacency on the link */ + adj_index_t ai_bcast; + /* IP DSCP to set in sent packets */ + ip_dscp_t dscp; + + dhcp_event_cb_t event_callback; +} dhcp_client_t; + +typedef struct +{ + /* DHCP client pool */ + dhcp_client_t *clients; + uword *client_by_sw_if_index; + u32 seed; + + /* convenience */ + vlib_main_t *vlib_main; + vnet_main_t *vnet_main; +} dhcp_client_main_t; + +typedef struct +{ + int is_add; + u32 sw_if_index; + u8 set_broadcast_flag; + + /* vectors, consumed by dhcp client code */ + u8 *hostname; + u8 *client_identifier; + + /* Bytes containing requested option numbers */ + u8 *option_55_data; + + /* Information used for event callback */ + u32 client_index; + u32 pid; + ip_dscp_t dscp; + dhcp_event_cb_t event_callback; +} dhcp_client_add_del_args_t; + +extern dhcp_client_main_t dhcp_client_main; + +#define EVENT_DHCP_CLIENT_WAKEUP 1 + +int dhcp_client_for_us (u32 bi0, + vlib_buffer_t * b0, + ip4_header_t * ip0, + udp_header_t * u0, dhcp_header_t * dh0); + +/** + * Add/Delete DHCP clients + */ +extern int dhcp_client_config (u32 is_add, + u32 client_index, + vlib_main_t * vm, + u32 sw_if_index, + u8 * hostname, + u8 * client_id, + dhcp_event_cb_t event_callback, + u8 set_broadcast_flag, + ip_dscp_t dscp, u32 pid); + +/** + * callback function for clients walking the DHCP client configurations + * + * @param client The client being visitsed + * @param data The data passed during the call to 'walk' + * @return !0 to continue walking 0 to stop. + */ +typedef int (*dhcp_client_walk_cb_t) (const dhcp_client_t * client, + void *data); + +/** + * Walk (visit each) DHCP client configuration + * + * @param cb The callback function invoked as each client is visited + * @param ctx Context data passed back to the client in the invocation of + * the callback. + */ +extern void dhcp_client_walk (dhcp_client_walk_cb_t cb, void *ctx); + +#endif /* included_dhcp_client_h */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp.api b/src/plugins/dhcp/dhcp.api new file mode 100644 index 00000000000..a91874be82c --- /dev/null +++ b/src/plugins/dhcp/dhcp.api @@ -0,0 +1,510 @@ +/* + * Copyright (c) 2015-2016 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 = "3.0.1"; + +import "vnet/interface_types.api"; +import "vnet/ip/ip_types.api"; +import "vnet/ethernet/ethernet_types.api"; + +enum vss_type { + VSS_TYPE_API_ASCII = 0, + VSS_TYPE_API_VPN_ID = 1, + VSS_TYPE_API_INVALID = 123, + VSS_TYPE_API_DEFAULT = 255, +}; + +enum dhcp_client_state { + DHCP_CLIENT_STATE_API_DISCOVER, + DHCP_CLIENT_STATE_API_REQUEST, + DHCP_CLIENT_STATE_API_BOUND, +}; + +enum dhcpv6_msg_type +{ + DHCPV6_MSG_API_SOLICIT = 1, + DHCPV6_MSG_API_ADVERTISE = 2, + DHCPV6_MSG_API_REQUEST = 3, + DHCPV6_MSG_API_CONFIRM = 4, + DHCPV6_MSG_API_RENEW = 5, + DHCPV6_MSG_API_REBIND = 6, + DHCPV6_MSG_API_REPLY = 7, + DHCPV6_MSG_API_RELEASE = 8, + DHCPV6_MSG_API_DECLINE = 9, + DHCPV6_MSG_API_RECONFIGURE = 10, + DHCPV6_MSG_API_INFORMATION_REQUEST = 11, + DHCPV6_MSG_API_RELAY_FORW = 12, + DHCPV6_MSG_API_RELAY_REPL = 13, +}; + +/** \brief Get the plugin version + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ + +define dhcp_plugin_get_version +{ + u32 client_index; + u32 context; +}; + +/** \brief Reply to get the plugin version + @param context - returned sender context, to match reply w/ request + @param major - Incremented every time a known breaking behavior change is introduced + @param minor - Incremented with small changes, may be used to avoid buggy versions +*/ + +define dhcp_plugin_get_version_reply +{ + u32 context; + u32 major; + u32 minor; +}; + +/** \brief Control ping from client to api server request + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request +*/ +define dhcp_plugin_control_ping +{ + u32 client_index; + u32 context; +}; + +/** \brief Control ping from the client to the server response + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param retval - return code for the request + @param vpe_pid - the pid of the vpe, returned by the server +*/ +define dhcp_plugin_control_ping_reply +{ + u32 context; + i32 retval; + u32 client_index; + u32 vpe_pid; +}; + +/** \brief DHCP Proxy config add / del request + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param rx_vrf_id - Rx/interface vrf id + @param server_vrf_id - server vrf id + @param is_add - add the config if non-zero, else delete + @param insert_circuit_id - option82 suboption 1 fib number + @param dhcp_server[] - server address + @param dhcp_src_address[] - <fix this, need details> +*/ +autoreply define dhcp_proxy_config +{ + u32 client_index; + u32 context; + u32 rx_vrf_id; + u32 server_vrf_id; + bool is_add; + vl_api_address_t dhcp_server; + vl_api_address_t dhcp_src_address; +}; + +/** \brief DHCP Proxy set / unset vss request + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param tbl_id - table id + @vss_type - 0: use ASCI vpn_id; 1: use oui/vpn_index; 255: global vpn + @vpn_ascii - null terminated ASCII VPN ID up to 128 characters + @param oui - first part of rfc2685 vpn id, 3 bytes oui + @param vpn_index - second part of rfc2685 vpn id, 4 bytes vpn index + @param is_ipv6 - ip6 if non-zero, else ip4 + @param is_add - set vss if non-zero, else delete +*/ +autoreply define dhcp_proxy_set_vss +{ + u32 client_index; + u32 context; + u32 tbl_id; + vl_api_vss_type_t vss_type; + string vpn_ascii_id[129]; + u32 oui; + u32 vpn_index; + bool is_ipv6; + bool is_add; +}; + +/** \brief DHCP Client config data + @param sw_if_index - index of the interface for DHCP client + @param hostname - hostname + @param id - Client ID - option 61 + @param want_dhcp_event - DHCP event sent to the sender + via dhcp_compl_event API message if non-zero + @param set_broadcast_flag - in the DHCP Discover to control + how the resulting OFFER is addressed. + @param dscp - DSCP value set in IP packets sent by the client + @param pid - sender's pid +*/ +typedef dhcp_client +{ + vl_api_interface_index_t sw_if_index; + string hostname[64]; + u8 id[64]; + bool want_dhcp_event; + bool set_broadcast_flag; + vl_api_ip_dscp_t dscp; + u32 pid; +}; + +/** \brief DHCP Client config add / del request + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param is_add - add the config if non-zero, else delete + @param client - client configuration data +*/ +autoreply define dhcp_client_config +{ + u32 client_index; + u32 context; + bool is_add; + vl_api_dhcp_client_t client; +}; + +/** \brief Struct representing domain server + @param address - IP address +*/ +typedef domain_server +{ + vl_api_address_t address; +}; + +/** \brief Data learned by the client during the DHCP process + @param sw_if_index - the interface on which the client is configured + @param state - the state of the lease + @param is_ipv6 - if non-zero the address is ipv6, else ipv4 + @param mask_width - The length of the subnet mask assigned + @param host_address - Host IP address + @param router_address - Router IP address + @param host_mac - Host MAC address +*/ +typedef dhcp_lease +{ + vl_api_interface_index_t sw_if_index; + vl_api_dhcp_client_state_t state; + bool is_ipv6; + string hostname[64]; + u8 mask_width; + vl_api_address_t host_address; + vl_api_address_t router_address; + vl_api_mac_address_t host_mac; + u8 count; + vl_api_domain_server_t domain_server[count]; +}; + +/** \brief Tell client about a DHCP completion event + @param client_index - opaque cookie to identify the sender + @param pid - client pid registered to receive notification + @param lease - Data learned during the DHCP process; +*/ +define dhcp_compl_event +{ + u32 client_index; + u32 pid; + vl_api_dhcp_lease_t lease; +}; + +service { + rpc dhcp_client_config returns dhcp_client_config_reply events dhcp_compl_event; +}; + +/** \brief Dump the DHCP client configurations + */ +define dhcp_client_dump +{ + u32 client_index; + u32 context; +}; + +/** \brief DHCP Client details returned from dump + * @param client - The configured client + * @param lease - The learned lease data + */ +define dhcp_client_details +{ + u32 context; + vl_api_dhcp_client_t client; + vl_api_dhcp_lease_t lease; +}; + +/** \brief Dump DHCP proxy table + @param client_index - opaque cookie to identify the sender + @param True for IPv6 proxy table +*/ +define dhcp_proxy_dump +{ + u32 client_index; + u32 context; + bool is_ip6; +}; + +typedef dhcp_server +{ + u32 server_vrf_id; + vl_api_address_t dhcp_server; +}; + +/** \brief Tell client about a DHCP completion event + @param client_index - opaque cookie to identify the sender +*/ +manual_endian manual_print define dhcp_proxy_details +{ + u32 context; + u32 rx_vrf_id; + u32 vss_oui; + u32 vss_fib_id; + vl_api_vss_type_t vss_type; + bool is_ipv6; + string vss_vpn_ascii_id[129]; + vl_api_address_t dhcp_src_address; + u8 count; + vl_api_dhcp_server_t servers[count]; +}; + +/** \brief Set DHCPv6 DUID-LL + @param client_index - opaque cookie to identify the sender + @param context - sender context, to match reply w/ request + @param duid_ll - DUID-LL binary string +*/ +autoreply define dhcp6_duid_ll_set +{ + u32 client_index; + u32 context; + u8 duid_ll[10]; +}; + +/** \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; + bool enable; +}; + +/** \brief Struct representing DHCPv6 address + @param address - address + @param valid_time - valid lifetime + @param preferred_time - preferred lifetime +*/ +typedef dhcp6_address_info +{ + vl_api_ip6_address_t address; + u32 valid_time; + u32 preferred_time; +}; + +/** \brief Struct representing DHCPv6 PD prefix + @param prefix - prefix + @param valid_time - valid lifetime + @param preferred_time - preferred lifetime +*/ +typedef dhcp6_pd_prefix_info +{ + vl_api_ip6_prefix_t prefix; + u32 valid_time; + u32 preferred_time; +}; + +/** \brief Send DHCPv6 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, also identifies IAID + @param server_index - used to dentify DHCPv6 server, + unique for each DHCPv6 server on the link, + value obrtained from dhcp6_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_NA option + @param T2 - value of T2 in IA_NA option + @param n_addresses - number of addresses in IA_NA option + @param addresses - list of addresses in IA_NA option +*/ +autoreply define dhcp6_send_client_message +{ + u32 client_index; + u32 context; + vl_api_interface_index_t sw_if_index; + u32 server_index; + u32 irt; + u32 mrt; + u32 mrc; + u32 mrd; + bool stop; + vl_api_dhcpv6_msg_type_t msg_type; + u32 T1; + u32 T2; + u32 n_addresses; + vl_api_dhcp6_address_info_t addresses[n_addresses]; +}; + +/** \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; + vl_api_interface_index_t sw_if_index; + u32 server_index; + u32 irt; + u32 mrt; + u32 mrc; + u32 mrd; + bool stop; + vl_api_dhcpv6_msg_type_t msg_type; + u32 T1; + u32 T2; + u32 n_prefixes; + vl_api_dhcp6_pd_prefix_info_t prefixes[n_prefixes]; +}; + +service { + rpc want_dhcp6_reply_events returns want_dhcp6_reply_events_reply + events dhcp6_reply_event; +}; + +service { + rpc want_dhcp6_pd_reply_events returns want_dhcp6_pd_reply_events_reply + events dhcp6_pd_reply_event; +}; + +/** \brief Register for DHCPv6 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_reply_events +{ + u32 client_index; + u32 context; + u8 enable_disable; + u32 pid; +}; + +/** \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; + bool enable_disable; + u32 pid; +}; + +/** \brief Tell client about a DHCPv6 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, also identifies IAID + @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_NA option + @param T2 - value of T2 in IA_NA option + @param inner_status_code - value of status code inside IA_NA option + @param status_code - value of status code + @param preference - value of preference option in reply message + @param n_addresses - number of addresses in IA_NA option + @param addresses - list of addresses in IA_NA option +*/ +define dhcp6_reply_event +{ + u32 client_index; + u32 pid; + vl_api_interface_index_t sw_if_index; + u32 server_index; + vl_api_dhcpv6_msg_type_t msg_type; + u32 T1; + u32 T2; + u16 inner_status_code; + u16 status_code; + u8 preference; + u32 n_addresses; + vl_api_dhcp6_address_info_t addresses[n_addresses]; +}; + +/** \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; + vl_api_interface_index_t sw_if_index; + u32 server_index; + vl_api_dhcpv6_msg_type_t 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") + * End: + */ diff --git a/src/plugins/dhcp/dhcp4_packet.h b/src/plugins/dhcp/dhcp4_packet.h new file mode 100644 index 00000000000..3076dd9529d --- /dev/null +++ b/src/plugins/dhcp/dhcp4_packet.h @@ -0,0 +1,80 @@ +#ifndef included_vnet_dhcp4_packet_h +#define included_vnet_dhcp4_packet_h + +/* + * DHCP packet format + * + * Copyright (c) 2013 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 <vnet/ip/ip4_packet.h> + +typedef struct +{ + u8 option; + u8 length; + union + { + u8 data[0]; + u32 data_as_u32[0]; + }; +} __attribute__ ((packed)) dhcp_option_t; + +typedef struct +{ + u8 opcode; /* 1 = request, 2 = reply */ + u8 hardware_type; /* 1 = ethernet */ + u8 hardware_address_length; + u8 hops; + u32 transaction_identifier; + u16 seconds; + u16 flags; +#define DHCP_FLAG_BROADCAST (1<<15) + ip4_address_t client_ip_address; + ip4_address_t your_ip_address; /* use this one */ + ip4_address_t server_ip_address; + ip4_address_t gateway_ip_address; /* use option 3, not this one */ + u8 client_hardware_address[16]; + u8 server_name[64]; + u8 boot_filename[128]; + ip4_address_t magic_cookie; + dhcp_option_t options[0]; +} dhcp_header_t; + +typedef enum +{ + DHCP_PACKET_DISCOVER = 1, + DHCP_PACKET_OFFER, + DHCP_PACKET_REQUEST, + DHCP_PACKET_ACK = 5, + DHCP_PACKET_NAK, +} dhcp_packet_type_t; + +typedef enum dhcp_packet_option_t_ +{ + DHCP_PACKET_OPTION_MSG_TYPE = 53, + DHCP_PACKET_OPTION_END = 0xff, +} dhcp_packet_option_t; + +/* charming antique: 99.130.83.99 is the dhcp magic cookie */ +#define DHCP_MAGIC (clib_host_to_net_u32(0x63825363)) + +#endif /* included_vnet_dhcp4_packet_h */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp4_proxy_error.def b/src/plugins/dhcp/dhcp4_proxy_error.def new file mode 100644 index 00000000000..adf04808fa3 --- /dev/null +++ b/src/plugins/dhcp/dhcp4_proxy_error.def @@ -0,0 +1,32 @@ +/* + * dhcp_proxy_error.def: dhcp proxy errors + * + * Copyright (c) 2013 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. + */ + +dhcp_proxy_error (NONE, "no error") +dhcp_proxy_error (NO_SERVER, "no dhcp server configured") +dhcp_proxy_error (RELAY_TO_SERVER, "DHCP packets relayed to the server") +dhcp_proxy_error (RELAY_TO_CLIENT, "DHCP packets relayed to clients") +dhcp_proxy_error (OPTION_82_ERROR, "DHCP failed to insert option 82") +dhcp_proxy_error (NO_OPTION_82, "DHCP option 82 missing") +dhcp_proxy_error (BAD_OPTION_82_ITF, "Bad DHCP option 82 interface value") +dhcp_proxy_error (BAD_OPTION_82_ADDR, "Bad DHCP option 82 address value") +dhcp_proxy_error (BAD_FIB_ID, "DHCP option 82 fib-id to fib-index map failure") +dhcp_proxy_error (NO_INTERFACE_ADDRESS, "DHCP no interface address") +dhcp_proxy_error (OPTION_82_VSS_NOT_PROCESSED, "DHCP VSS not processed by DHCP server") +dhcp_proxy_error (BAD_YIADDR, "DHCP packets with bad your_ip_address fields") +dhcp_proxy_error (BAD_SVR_FIB_OR_ADDRESS, "DHCP packets not from DHCP server or server FIB.") +dhcp_proxy_error (PKT_TOO_BIG, "DHCP packets which are too big.") + diff --git a/src/plugins/dhcp/dhcp4_proxy_node.c b/src/plugins/dhcp/dhcp4_proxy_node.c new file mode 100644 index 00000000000..10963c7ff47 --- /dev/null +++ b/src/plugins/dhcp/dhcp4_proxy_node.c @@ -0,0 +1,1090 @@ +/* + * proxy_node.c: dhcp proxy node processing + * + * Copyright (c) 2013 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 <vlib/vlib.h> +#include <vnet/pg/pg.h> +#include <dhcp/dhcp_proxy.h> +#include <dhcp/client.h> +#include <vnet/fib/ip4_fib.h> + +static char *dhcp_proxy_error_strings[] = { +#define dhcp_proxy_error(n,s) s, +#include <dhcp/dhcp4_proxy_error.def> +#undef dhcp_proxy_error +}; + +#define foreach_dhcp_proxy_to_server_input_next \ + _ (DROP, "error-drop") \ + _ (LOOKUP, "ip4-lookup") \ + _ (SEND_TO_CLIENT, "dhcp-proxy-to-client") + +typedef enum +{ +#define _(s,n) DHCP_PROXY_TO_SERVER_INPUT_NEXT_##s, + foreach_dhcp_proxy_to_server_input_next +#undef _ + DHCP_PROXY_TO_SERVER_INPUT_N_NEXT, +} dhcp_proxy_to_server_input_next_t; + +typedef struct +{ + /* 0 => to server, 1 => to client */ + int which; + ip4_address_t trace_ip4_address; + u32 error; + u32 sw_if_index; + u32 original_sw_if_index; +} dhcp_proxy_trace_t; + +#define VPP_DHCP_OPTION82_SUB1_SIZE 6 +#define VPP_DHCP_OPTION82_SUB5_SIZE 6 +#define VPP_DHCP_OPTION82_VSS_SIZE 12 +#define VPP_DHCP_OPTION82_SIZE (VPP_DHCP_OPTION82_SUB1_SIZE + \ + VPP_DHCP_OPTION82_SUB5_SIZE + \ + VPP_DHCP_OPTION82_VSS_SIZE +3) + +static vlib_node_registration_t dhcp_proxy_to_server_node; +static vlib_node_registration_t dhcp_proxy_to_client_node; + +static u8 * +format_dhcp_proxy_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 *); + dhcp_proxy_trace_t *t = va_arg (*args, dhcp_proxy_trace_t *); + + if (t->which == 0) + s = format (s, "DHCP proxy: sent to server %U\n", + format_ip4_address, &t->trace_ip4_address, t->error); + else + s = format (s, "DHCP proxy: broadcast to client from %U\n", + format_ip4_address, &t->trace_ip4_address); + + if (t->error != (u32) ~ 0) + s = format (s, " error: %s\n", dhcp_proxy_error_strings[t->error]); + + s = format (s, " original_sw_if_index: %d, sw_if_index: %d\n", + t->original_sw_if_index, t->sw_if_index); + + return s; +} + +static u8 * +format_dhcp_proxy_header_with_length (u8 * s, va_list * args) +{ + dhcp_header_t *h = va_arg (*args, dhcp_header_t *); + u32 max_header_bytes = va_arg (*args, u32); + u32 header_bytes; + + header_bytes = sizeof (h[0]); + if (max_header_bytes != 0 && header_bytes > max_header_bytes) + return format (s, "dhcp header truncated"); + + s = format (s, "DHCP Proxy"); + + return s; +} + +static uword +dhcp_proxy_to_server_input (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + u32 n_left_from, next_index, *from, *to_next; + dhcp_proxy_main_t *dpm = &dhcp_proxy_main; + from = vlib_frame_vector_args (from_frame); + n_left_from = from_frame->n_vectors; + u32 pkts_to_server = 0, pkts_to_client = 0, pkts_no_server = 0; + u32 pkts_no_interface_address = 0; + u32 pkts_too_big = 0; + ip4_main_t *im = &ip4_main; + + next_index = node->cached_next_index; + + 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) + { + u32 bi0; + vlib_buffer_t *b0; + udp_header_t *u0; + dhcp_header_t *h0; + ip4_header_t *ip0; + u32 next0; + u32 old0, new0; + ip_csum_t sum0; + u32 error0 = (u32) ~ 0; + u32 sw_if_index = 0; + u32 original_sw_if_index = 0; + u32 fib_index; + dhcp_proxy_t *proxy; + dhcp_server_t *server; + u32 rx_sw_if_index; + dhcp_option_t *o, *end; + u32 len = 0; + u8 is_discover = 0; + int space_left; + + bi0 = from[0]; + from += 1; + n_left_from -= 1; + + b0 = vlib_get_buffer (vm, bi0); + + h0 = vlib_buffer_get_current (b0); + + /* + * udp_local hands us the DHCP header, need udp hdr, + * ip hdr to relay to server + */ + vlib_buffer_advance (b0, -(sizeof (*u0))); + u0 = vlib_buffer_get_current (b0); + + /* This blows. Return traffic has src_port = 67, dst_port = 67 */ + if (u0->src_port == + clib_net_to_host_u16 (UDP_DST_PORT_dhcp_to_server)) + { + vlib_buffer_advance (b0, sizeof (*u0)); + next0 = DHCP_PROXY_TO_SERVER_INPUT_NEXT_SEND_TO_CLIENT; + error0 = 0; + pkts_to_client++; + goto do_enqueue; + } + + rx_sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + fib_index = im->fib_index_by_sw_if_index[rx_sw_if_index]; + proxy = dhcp_get_proxy (dpm, fib_index, FIB_PROTOCOL_IP4); + + if (PREDICT_FALSE (NULL == proxy)) + { + error0 = DHCP_PROXY_ERROR_NO_SERVER; + next0 = DHCP_PROXY_TO_SERVER_INPUT_NEXT_DROP; + pkts_no_server++; + goto do_trace; + } + + if (!vlib_buffer_chain_linearize (vm, b0)) + { + error0 = DHCP_PROXY_ERROR_PKT_TOO_BIG; + next0 = DHCP_PROXY_TO_SERVER_INPUT_NEXT_DROP; + pkts_too_big++; + goto do_trace; + } + space_left = vlib_buffer_space_left_at_end (vm, b0); + /* cant parse chains... + * and we need some space for option 82*/ + if ((b0->flags & VLIB_BUFFER_NEXT_PRESENT) != 0 || + space_left < VPP_DHCP_OPTION82_SIZE) + { + error0 = DHCP_PROXY_ERROR_PKT_TOO_BIG; + next0 = DHCP_PROXY_TO_SERVER_INPUT_NEXT_DROP; + pkts_too_big++; + goto do_trace; + } + + server = &proxy->dhcp_servers[0]; + vlib_buffer_advance (b0, -(sizeof (*ip0))); + ip0 = vlib_buffer_get_current (b0); + + /* disable UDP checksum */ + u0->checksum = 0; + sum0 = ip0->checksum; + old0 = ip0->dst_address.as_u32; + new0 = server->dhcp_server.ip4.as_u32; + ip0->dst_address.as_u32 = server->dhcp_server.ip4.as_u32; + sum0 = ip_csum_update (sum0, old0, new0, + ip4_header_t /* structure */ , + dst_address /* changed member */ ); + ip0->checksum = ip_csum_fold (sum0); + + sum0 = ip0->checksum; + old0 = ip0->src_address.as_u32; + new0 = proxy->dhcp_src_address.ip4.as_u32; + ip0->src_address.as_u32 = new0; + sum0 = ip_csum_update (sum0, old0, new0, + ip4_header_t /* structure */ , + src_address /* changed member */ ); + ip0->checksum = ip_csum_fold (sum0); + + /* Send to DHCP server via the configured FIB */ + vnet_buffer (b0)->sw_if_index[VLIB_TX] = server->server_fib_index; + + h0->gateway_ip_address = proxy->dhcp_src_address.ip4; + pkts_to_server++; + + o = h0->options; + end = (void *) vlib_buffer_get_tail (b0); + + /* TLVs are not performance-friendly... */ + while (o->option != DHCP_PACKET_OPTION_END && o < end) + { + if (DHCP_PACKET_OPTION_MSG_TYPE == o->option) + { + if (DHCP_PACKET_DISCOVER == o->data[0]) + { + is_discover = 1; + } + } + o = (dhcp_option_t *) (o->data + o->length); + } + + if (o->option == DHCP_PACKET_OPTION_END && o <= end) + { + vnet_main_t *vnm = vnet_get_main (); + u16 old_l0, new_l0; + ip4_address_t _ia0, *ia0 = &_ia0; + dhcp_vss_t *vss; + vnet_sw_interface_t *swif; + + original_sw_if_index = sw_if_index = + vnet_buffer (b0)->sw_if_index[VLIB_RX]; + swif = vnet_get_sw_interface (vnm, sw_if_index); + if (swif->flags & VNET_SW_INTERFACE_FLAG_UNNUMBERED) + sw_if_index = swif->unnumbered_sw_if_index; + + /* + * Get the first ip4 address on the [client-side] + * RX interface, if not unnumbered. otherwise use + * the loopback interface's ip address. + */ + ia0 = ip4_interface_first_address (&ip4_main, sw_if_index, 0); + + if (ia0 == 0) + { + error0 = DHCP_PROXY_ERROR_NO_INTERFACE_ADDRESS; + next0 = DHCP_PROXY_TO_SERVER_INPUT_NEXT_DROP; + pkts_no_interface_address++; + goto do_trace; + } + + /* Add option 82 */ + o->option = 82; /* option 82 */ + o->length = 12; /* 12 octets to follow */ + o->data[0] = 1; /* suboption 1, circuit ID (=FIB id) */ + o->data[1] = 4; /* length of suboption */ + u32 *o_ifid = (u32 *) & o->data[2]; + *o_ifid = clib_host_to_net_u32 (original_sw_if_index); + o->data[6] = 5; /* suboption 5 (client RX intfc address) */ + o->data[7] = 4; /* length 4 */ + u32 *o_addr = (u32 *) & o->data[8]; + *o_addr = ia0->as_u32; + o->data[12] = DHCP_PACKET_OPTION_END; + + vss = dhcp_get_vss_info (dpm, fib_index, FIB_PROTOCOL_IP4); + if (vss) + { + u32 id_len; /* length of VPN ID */ + + if (vss->vss_type == VSS_TYPE_VPN_ID) + { + id_len = sizeof (vss->vpn_id); /* vpn_id is 7 bytes */ + memcpy (&o->data[15], vss->vpn_id, id_len); + } + else if (vss->vss_type == VSS_TYPE_ASCII) + { + id_len = vec_len (vss->vpn_ascii_id); + memcpy (&o->data[15], vss->vpn_ascii_id, id_len); + } + else /* must be VSS_TYPE_DEFAULT, no VPN ID */ + id_len = 0; + + o->data[12] = 151; /* vss suboption */ + o->data[13] = id_len + 1; /* length: vss_type + id_len */ + o->data[14] = vss->vss_type; /* vss option type */ + o->data[15 + id_len] = 152; /* vss control suboption */ + o->data[16 + id_len] = 0; /* length */ + o->data[17 + id_len] = DHCP_PACKET_OPTION_END; /* "end-of-options" (0xFF) */ + /* 5 bytes for suboption headers 151+len, 152+len and 0xFF */ + o->length += id_len + 5; + } + + len = o->length + 3; + b0->current_length += len; + /* Fix IP header length and checksum */ + old_l0 = ip0->length; + new_l0 = clib_net_to_host_u16 (old_l0); + new_l0 += len; + new_l0 = clib_host_to_net_u16 (new_l0); + ip0->length = new_l0; + sum0 = ip0->checksum; + sum0 = ip_csum_update (sum0, old_l0, new_l0, ip4_header_t, + length /* changed member */ ); + ip0->checksum = ip_csum_fold (sum0); + + /* Fix UDP length */ + new_l0 = clib_net_to_host_u16 (u0->length); + new_l0 += len; + u0->length = clib_host_to_net_u16 (new_l0); + } + else + { + vlib_node_increment_counter + (vm, dhcp_proxy_to_server_node.index, + DHCP_PROXY_ERROR_OPTION_82_ERROR, 1); + } + + next0 = DHCP_PROXY_TO_SERVER_INPUT_NEXT_LOOKUP; + + /* + * If we have multiple servers configured and this is the + * client's discover message, then send copies to each of + * those servers + */ + if (is_discover && vec_len (proxy->dhcp_servers) > 1) + { + u32 ii; + + for (ii = 1; ii < vec_len (proxy->dhcp_servers); ii++) + { + vlib_buffer_t *c0; + u32 ci0; + + c0 = vlib_buffer_copy (vm, b0); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (c0); + ci0 = vlib_get_buffer_index (vm, c0); + server = &proxy->dhcp_servers[ii]; + + ip0 = vlib_buffer_get_current (c0); + + sum0 = ip0->checksum; + old0 = ip0->dst_address.as_u32; + new0 = server->dhcp_server.ip4.as_u32; + ip0->dst_address.as_u32 = server->dhcp_server.ip4.as_u32; + sum0 = ip_csum_update (sum0, old0, new0, + ip4_header_t /* structure */ , + dst_address /* changed member */ ); + ip0->checksum = ip_csum_fold (sum0); + + to_next[0] = ci0; + to_next += 1; + n_left_to_next -= 1; + + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + ci0, next0); + + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_proxy_trace_t *tr; + + tr = vlib_add_trace (vm, node, c0, sizeof (*tr)); + tr->which = 0; /* to server */ + tr->error = error0; + tr->original_sw_if_index = original_sw_if_index; + tr->sw_if_index = sw_if_index; + if (next0 == DHCP_PROXY_TO_SERVER_INPUT_NEXT_LOOKUP) + tr->trace_ip4_address.as_u32 = + server->dhcp_server.ip4.as_u32; + } + + if (PREDICT_FALSE (0 == n_left_to_next)) + { + vlib_put_next_frame (vm, node, next_index, + n_left_to_next); + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + } + } + } + do_trace: + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_proxy_trace_t *tr = vlib_add_trace (vm, node, + b0, sizeof (*tr)); + tr->which = 0; /* to server */ + tr->error = error0; + tr->original_sw_if_index = original_sw_if_index; + tr->sw_if_index = sw_if_index; + if (next0 == DHCP_PROXY_TO_SERVER_INPUT_NEXT_LOOKUP) + tr->trace_ip4_address.as_u32 = + proxy->dhcp_servers[0].dhcp_server.ip4.as_u32; + } + + do_enqueue: + to_next[0] = bi0; + to_next += 1; + n_left_to_next -= 1; + + 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); + } + vlib_node_increment_counter (vm, dhcp_proxy_to_server_node.index, + DHCP_PROXY_ERROR_RELAY_TO_CLIENT, + pkts_to_client); + vlib_node_increment_counter (vm, dhcp_proxy_to_server_node.index, + DHCP_PROXY_ERROR_RELAY_TO_SERVER, + pkts_to_server); + vlib_node_increment_counter (vm, dhcp_proxy_to_server_node.index, + DHCP_PROXY_ERROR_NO_SERVER, pkts_no_server); + vlib_node_increment_counter (vm, dhcp_proxy_to_server_node.index, + DHCP_PROXY_ERROR_NO_INTERFACE_ADDRESS, + pkts_no_interface_address); + vlib_node_increment_counter (vm, dhcp_proxy_to_server_node.index, + DHCP_PROXY_ERROR_PKT_TOO_BIG, pkts_too_big); + return from_frame->n_vectors; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dhcp_proxy_to_server_node, static) = { + .function = dhcp_proxy_to_server_input, + .name = "dhcp-proxy-to-server", + /* Takes a vector of packets. */ + .vector_size = sizeof (u32), + + .n_errors = DHCP_PROXY_N_ERROR, + .error_strings = dhcp_proxy_error_strings, + + .n_next_nodes = DHCP_PROXY_TO_SERVER_INPUT_N_NEXT, + .next_nodes = { +#define _(s,n) [DHCP_PROXY_TO_SERVER_INPUT_NEXT_##s] = n, + foreach_dhcp_proxy_to_server_input_next +#undef _ + }, + + .format_buffer = format_dhcp_proxy_header_with_length, + .format_trace = format_dhcp_proxy_trace, +#if 0 + .unformat_buffer = unformat_dhcp_proxy_header, +#endif +}; +/* *INDENT-ON* */ + +static uword +dhcp_proxy_to_client_input (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + u32 n_left_from, *from; + ethernet_main_t *em = vnet_get_ethernet_main (); + dhcp_proxy_main_t *dpm = &dhcp_proxy_main; + vnet_main_t *vnm = vnet_get_main (); + ip4_main_t *im = &ip4_main; + + from = vlib_frame_vector_args (from_frame); + n_left_from = from_frame->n_vectors; + + while (n_left_from > 0) + { + u32 bi0; + vlib_buffer_t *b0; + udp_header_t *u0; + dhcp_header_t *h0; + ip4_header_t *ip0 = 0; + ip4_address_t *ia0 = 0; + u32 old0, new0; + ip_csum_t sum0; + ethernet_interface_t *ei0; + ethernet_header_t *mac0; + vnet_hw_interface_t *hi0; + vlib_frame_t *f0; + u32 *to_next0; + u32 sw_if_index = ~0; + vnet_sw_interface_t *si0; + u32 error0 = (u32) ~ 0; + vnet_sw_interface_t *swif; + u32 fib_index; + dhcp_proxy_t *proxy; + dhcp_server_t *server; + u32 original_sw_if_index = (u32) ~ 0; + ip4_address_t relay_addr = { + .as_u32 = 0, + }; + + bi0 = from[0]; + from += 1; + n_left_from -= 1; + + b0 = vlib_get_buffer (vm, bi0); + h0 = vlib_buffer_get_current (b0); + + /* + * udp_local hands us the DHCP header, need udp hdr, + * ip hdr to relay to client + */ + vlib_buffer_advance (b0, -(sizeof (*u0))); + u0 = vlib_buffer_get_current (b0); + + vlib_buffer_advance (b0, -(sizeof (*ip0))); + ip0 = vlib_buffer_get_current (b0); + + /* Consumed by dhcp client code? */ + if (dhcp_client_for_us (bi0, b0, ip0, u0, h0)) + continue; + + if (1 /* dpm->insert_option_82 */ ) + { + /* linearize needed to "unclone" and scan options */ + int rv = vlib_buffer_chain_linearize (vm, b0); + if ((b0->flags & VLIB_BUFFER_NEXT_PRESENT) != 0 || !rv) + { + error0 = DHCP_PROXY_ERROR_PKT_TOO_BIG; + goto drop_packet; + } + + dhcp_option_t *o = h0->options, *end = + (void *) vlib_buffer_get_tail (b0); + + /* Parse through TLVs looking for option 82. + The circuit-ID is the FIB number we need + to track down the client-facing interface */ + + while (o->option != DHCP_PACKET_OPTION_END && o < end) + { + if (o->option == 82) + { + u32 vss_exist = 0; + u32 vss_ctrl = 0; + dhcp_option_t *sub = (dhcp_option_t *) & o->data[0]; + dhcp_option_t *subend = + (dhcp_option_t *) (o->data + o->length); + while (sub->option != DHCP_PACKET_OPTION_END + && sub < subend) + { + /* If this is one of ours, it will have + total length 12, circuit-id suboption type, + and the sw_if_index */ + if (sub->option == 1 && sub->length == 4) + { + sw_if_index = ((sub->data[0] << 24) | + (sub->data[1] << 16) | + (sub->data[2] << 8) | + (sub->data[3])); + } + else if (sub->option == 5 && sub->length == 4) + { + relay_addr.as_u8[0] = sub->data[0]; + relay_addr.as_u8[1] = sub->data[1]; + relay_addr.as_u8[2] = sub->data[2]; + relay_addr.as_u8[3] = sub->data[3]; + } + else if (sub->option == 151 && + sub->length == 7 && sub->data[0] == 1) + vss_exist = 1; + else if (sub->option == 152 && sub->length == 0) + vss_ctrl = 1; + sub = (dhcp_option_t *) (sub->data + sub->length); + } + if (vss_ctrl && vss_exist) + vlib_node_increment_counter + (vm, dhcp_proxy_to_client_node.index, + DHCP_PROXY_ERROR_OPTION_82_VSS_NOT_PROCESSED, 1); + + } + o = (dhcp_option_t *) (o->data + o->length); + } + } + + if (sw_if_index == (u32) ~ 0) + { + error0 = DHCP_PROXY_ERROR_NO_OPTION_82; + + drop_packet: + vlib_node_increment_counter (vm, dhcp_proxy_to_client_node.index, + error0, 1); + f0 = vlib_get_frame_to_node (vm, dpm->error_drop_node_index); + to_next0 = vlib_frame_vector_args (f0); + to_next0[0] = bi0; + f0->n_vectors = 1; + vlib_put_frame_to_node (vm, dpm->error_drop_node_index, f0); + goto do_trace; + } + + if (relay_addr.as_u32 == 0) + { + error0 = DHCP_PROXY_ERROR_BAD_OPTION_82_ADDR; + goto drop_packet; + } + + if (sw_if_index >= vec_len (im->fib_index_by_sw_if_index)) + { + error0 = DHCP_PROXY_ERROR_BAD_OPTION_82_ITF; + goto drop_packet; + } + + fib_index = im->fib_index_by_sw_if_index[sw_if_index]; + proxy = dhcp_get_proxy (dpm, fib_index, FIB_PROTOCOL_IP4); + + if (PREDICT_FALSE (NULL == proxy)) + { + error0 = DHCP_PROXY_ERROR_NO_SERVER; + goto drop_packet; + } + + vec_foreach (server, proxy->dhcp_servers) + { + if (ip0->src_address.as_u32 == server->dhcp_server.ip4.as_u32) + { + goto server_found; + } + } + + error0 = DHCP_PROXY_ERROR_BAD_SVR_FIB_OR_ADDRESS; + goto drop_packet; + + server_found: + vnet_buffer (b0)->sw_if_index[VLIB_TX] = sw_if_index; + + swif = vnet_get_sw_interface (vnm, sw_if_index); + original_sw_if_index = sw_if_index; + if (swif->flags & VNET_SW_INTERFACE_FLAG_UNNUMBERED) + sw_if_index = swif->unnumbered_sw_if_index; + + ia0 = ip4_interface_first_address (&ip4_main, sw_if_index, 0); + if (ia0 == 0) + { + error0 = DHCP_PROXY_ERROR_NO_INTERFACE_ADDRESS; + goto drop_packet; + } + + if (relay_addr.as_u32 != ia0->as_u32) + { + error0 = DHCP_PROXY_ERROR_BAD_YIADDR; + goto drop_packet; + } + + u0->checksum = 0; + u0->dst_port = clib_net_to_host_u16 (UDP_DST_PORT_dhcp_to_client); + sum0 = ip0->checksum; + old0 = ip0->dst_address.as_u32; + new0 = 0xFFFFFFFF; + ip0->dst_address.as_u32 = new0; + sum0 = ip_csum_update (sum0, old0, new0, ip4_header_t /* structure */ , + dst_address /* offset of changed member */ ); + ip0->checksum = ip_csum_fold (sum0); + + sum0 = ip0->checksum; + old0 = ip0->src_address.as_u32; + new0 = ia0->as_u32; + ip0->src_address.as_u32 = new0; + sum0 = ip_csum_update (sum0, old0, new0, ip4_header_t /* structure */ , + src_address /* offset of changed member */ ); + ip0->checksum = ip_csum_fold (sum0); + + vlib_buffer_advance (b0, -(sizeof (ethernet_header_t))); + si0 = vnet_get_sw_interface (vnm, original_sw_if_index); + if (si0->type == VNET_SW_INTERFACE_TYPE_SUB) + vlib_buffer_advance (b0, -4 /* space for VLAN tag */ ); + + mac0 = vlib_buffer_get_current (b0); + + hi0 = vnet_get_sup_hw_interface (vnm, original_sw_if_index); + ei0 = pool_elt_at_index (em->interfaces, hi0->hw_instance); + clib_memcpy (mac0->src_address, ei0->address, sizeof (ei0->address)); + clib_memset (mac0->dst_address, 0xff, sizeof (mac0->dst_address)); + mac0->type = (si0->type == VNET_SW_INTERFACE_TYPE_SUB) ? + clib_net_to_host_u16 (0x8100) : clib_net_to_host_u16 (0x0800); + + if (si0->type == VNET_SW_INTERFACE_TYPE_SUB) + { + u32 *vlan_tag = (u32 *) (mac0 + 1); + u32 tmp; + tmp = (si0->sub.id << 16) | 0x0800; + *vlan_tag = clib_host_to_net_u32 (tmp); + } + + /* $$$ This needs to be rewritten, for sure */ + f0 = vlib_get_frame_to_node (vm, hi0->output_node_index); + to_next0 = vlib_frame_vector_args (f0); + to_next0[0] = bi0; + f0->n_vectors = 1; + vlib_put_frame_to_node (vm, hi0->output_node_index, f0); + + do_trace: + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_proxy_trace_t *tr = vlib_add_trace (vm, node, + b0, sizeof (*tr)); + tr->which = 1; /* to client */ + tr->trace_ip4_address.as_u32 = ia0 ? ia0->as_u32 : 0; + tr->error = error0; + tr->original_sw_if_index = original_sw_if_index; + tr->sw_if_index = sw_if_index; + } + } + + return from_frame->n_vectors; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dhcp_proxy_to_client_node, static) = { + .function = dhcp_proxy_to_client_input, + .name = "dhcp-proxy-to-client", + /* Takes a vector of packets. */ + .vector_size = sizeof (u32), + + .n_errors = DHCP_PROXY_N_ERROR, + .error_strings = dhcp_proxy_error_strings, + .format_buffer = format_dhcp_proxy_header_with_length, + .format_trace = format_dhcp_proxy_trace, +#if 0 + .unformat_buffer = unformat_dhcp_proxy_header, +#endif +}; +/* *INDENT-ON* */ + +void +dhcp_maybe_register_udp_ports (dhcp_port_reg_flags_t ports) +{ + dhcp_proxy_main_t *dm = &dhcp_proxy_main; + vlib_main_t *vm = dm->vlib_main; + int port_regs_diff = dm->udp_ports_registered ^ ports; + + if (!port_regs_diff) + return; + + if ((port_regs_diff & DHCP_PORT_REG_CLIENT) & ports) + udp_register_dst_port (vm, UDP_DST_PORT_dhcp_to_client, + dhcp_proxy_to_client_node.index, 1 /* is_ip4 */ ); + + if ((port_regs_diff & DHCP_PORT_REG_SERVER) & ports) + udp_register_dst_port (vm, UDP_DST_PORT_dhcp_to_server, + dhcp_proxy_to_server_node.index, 1 /* is_ip4 */ ); + + dm->udp_ports_registered |= ports; +} + +static clib_error_t * +dhcp4_proxy_init (vlib_main_t * vm) +{ + dhcp_proxy_main_t *dm = &dhcp_proxy_main; + vlib_node_t *error_drop_node; + + error_drop_node = vlib_get_node_by_name (vm, (u8 *) "error-drop"); + dm->error_drop_node_index = error_drop_node->index; + dm->vlib_main = vm; + + return 0; +} + + +VLIB_INIT_FUNCTION (dhcp4_proxy_init); + +int +dhcp4_proxy_set_server (ip46_address_t * addr, + ip46_address_t * src_addr, + u32 rx_table_id, u32 server_table_id, int is_del) +{ + u32 rx_fib_index = 0; + int rc = 0; + + const fib_prefix_t all_1s = { + .fp_len = 32, + .fp_addr.ip4.as_u32 = 0xffffffff, + .fp_proto = FIB_PROTOCOL_IP4, + }; + + if (ip46_address_is_zero (addr)) + return VNET_API_ERROR_INVALID_DST_ADDRESS; + + if (ip46_address_is_zero (src_addr)) + return VNET_API_ERROR_INVALID_SRC_ADDRESS; + + dhcp_maybe_register_udp_ports (DHCP_PORT_REG_CLIENT | DHCP_PORT_REG_SERVER); + + rx_fib_index = fib_table_find_or_create_and_lock (FIB_PROTOCOL_IP4, + rx_table_id, + FIB_SOURCE_DHCP); + + if (is_del) + { + if (dhcp_proxy_server_del (FIB_PROTOCOL_IP4, rx_fib_index, + addr, server_table_id)) + { + fib_table_entry_special_remove (rx_fib_index, + &all_1s, FIB_SOURCE_DHCP); + fib_table_unlock (rx_fib_index, FIB_PROTOCOL_IP4, FIB_SOURCE_DHCP); + } + } + else + { + if (dhcp_proxy_server_add (FIB_PROTOCOL_IP4, + addr, src_addr, + rx_fib_index, server_table_id)) + { + fib_table_entry_special_add (rx_fib_index, + &all_1s, + FIB_SOURCE_DHCP, FIB_ENTRY_FLAG_LOCAL); + fib_table_lock (rx_fib_index, FIB_PROTOCOL_IP4, FIB_SOURCE_DHCP); + } + } + fib_table_unlock (rx_fib_index, FIB_PROTOCOL_IP4, FIB_SOURCE_DHCP); + + return (rc); +} + +static clib_error_t * +dhcp4_proxy_set_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + ip46_address_t server_addr, src_addr; + u32 server_table_id = 0, rx_table_id = 0; + int is_del = 0; + int set_src = 0, set_server = 0; + + clib_memset (&server_addr, 0, sizeof (server_addr)); + clib_memset (&src_addr, 0, sizeof (src_addr)); + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "server %U", + unformat_ip4_address, &server_addr.ip4)) + set_server = 1; + else if (unformat (input, "server-fib-id %d", &server_table_id)) + ; + else if (unformat (input, "rx-fib-id %d", &rx_table_id)) + ; + else if (unformat (input, "src-address %U", + unformat_ip4_address, &src_addr.ip4)) + set_src = 1; + else if (unformat (input, "delete") || unformat (input, "del")) + is_del = 1; + else + break; + } + + if (is_del || (set_server && set_src)) + { + int rv; + + rv = dhcp4_proxy_set_server (&server_addr, &src_addr, rx_table_id, + server_table_id, is_del); + switch (rv) + { + case 0: + return 0; + + case VNET_API_ERROR_INVALID_DST_ADDRESS: + return clib_error_return (0, "Invalid server address"); + + case VNET_API_ERROR_INVALID_SRC_ADDRESS: + return clib_error_return (0, "Invalid src address"); + + case VNET_API_ERROR_NO_SUCH_ENTRY: + return clib_error_return + (0, "Fib id %d: no per-fib DHCP server configured", rx_table_id); + + default: + return clib_error_return (0, "BUG: rv %d", rv); + } + } + else + return clib_error_return (0, "parse error`%U'", + format_unformat_error, input); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcp_proxy_set_command, static) = { + .path = "set dhcp proxy", + .short_help = "set dhcp proxy [del] server <ip-addr> src-address <ip-addr> [server-fib-id <n>] [rx-fib-id <n>]", + .function = dhcp4_proxy_set_command_fn, +}; +/* *INDENT-ON* */ + +static u8 * +format_dhcp4_proxy_server (u8 * s, va_list * args) +{ + dhcp_proxy_t *proxy = va_arg (*args, dhcp_proxy_t *); + ip4_fib_t *rx_fib, *server_fib; + dhcp_server_t *server; + + if (proxy == 0) + { + s = format (s, "%=14s%=16s%s", "RX FIB", "Src Address", + "Servers FIB,Address"); + return s; + } + + rx_fib = ip4_fib_get (proxy->rx_fib_index); + + s = format (s, "%=14u%=16U", + rx_fib->table_id, + format_ip46_address, &proxy->dhcp_src_address, IP46_TYPE_ANY); + + vec_foreach (server, proxy->dhcp_servers) + { + server_fib = ip4_fib_get (server->server_fib_index); + s = format (s, "%u,%U ", + server_fib->table_id, + format_ip46_address, &server->dhcp_server, IP46_TYPE_ANY); + } + return s; +} + +static int +dhcp4_proxy_show_walk (dhcp_proxy_t * server, void *ctx) +{ + vlib_main_t *vm = ctx; + + vlib_cli_output (vm, "%U", format_dhcp4_proxy_server, server); + + return (1); +} + +static clib_error_t * +dhcp4_proxy_show_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + vlib_cli_output (vm, "%U", format_dhcp4_proxy_server, + NULL /* header line */ ); + + dhcp_proxy_walk (FIB_PROTOCOL_IP4, dhcp4_proxy_show_walk, vm); + + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcp_proxy_show_command, static) = { + .path = "show dhcp proxy", + .short_help = "Display dhcp proxy server info", + .function = dhcp4_proxy_show_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dhcp_option_82_vss_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + u8 is_del = 0, vss_type = VSS_TYPE_DEFAULT; + u32 oui = 0, fib_id = 0, tbl_id = ~0; + u8 *vpn_ascii_id = 0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "table %d", &tbl_id)) + ; + else if (unformat (input, "oui %d", &oui)) + vss_type = VSS_TYPE_VPN_ID; + else if (unformat (input, "vpn-id %d", &fib_id)) + vss_type = VSS_TYPE_VPN_ID; + else if (unformat (input, "vpn-ascii-id %s", &vpn_ascii_id)) + vss_type = VSS_TYPE_ASCII; + else if (unformat (input, "delete") || unformat (input, "del")) + is_del = 1; + else + break; + } + + if (tbl_id == ~0) + return clib_error_return (0, "no table ID specified."); + + int rv = dhcp_proxy_set_vss (FIB_PROTOCOL_IP4, tbl_id, vss_type, + vpn_ascii_id, oui, fib_id, is_del); + switch (rv) + { + case 0: + return 0; + case VNET_API_ERROR_NO_SUCH_ENTRY: + return clib_error_return (0, + "option 82 vss for table %d not found in in pool.", + tbl_id); + default: + return clib_error_return (0, "BUG: rv %d", rv); + + } +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcp_proxy_vss_command,static) = { + .path = "set dhcp option-82 vss", + .short_help = "set dhcp option-82 vss [del] table <table id> [oui <n> vpn-id <n> | vpn-ascii-id <text>]", + .function = dhcp_option_82_vss_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dhcp_vss_show_command_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + dhcp_vss_walk (FIB_PROTOCOL_IP4, dhcp_vss_show_walk, vm); + + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcp_proxy_vss_show_command, static) = { + .path = "show dhcp vss", + .short_help = "show dhcp VSS", + .function = dhcp_vss_show_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dhcp_option_82_address_show_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + vnet_main_t *vnm = vnet_get_main (); + u32 sw_if_index0 = 0, sw_if_index; + vnet_sw_interface_t *swif; + ip4_address_t *ia0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + + if (unformat (input, "%U", + unformat_vnet_sw_interface, vnm, &sw_if_index0)) + { + swif = vnet_get_sw_interface (vnm, sw_if_index0); + sw_if_index = (swif->flags & VNET_SW_INTERFACE_FLAG_UNNUMBERED) ? + swif->unnumbered_sw_if_index : sw_if_index0; + ia0 = ip4_interface_first_address (&ip4_main, sw_if_index, 0); + if (ia0) + { + vlib_cli_output (vm, "%=20s%=20s", "interface", + "source IP address"); + + vlib_cli_output (vm, "%=20U%=20U", + format_vnet_sw_if_index_name, + vnm, sw_if_index0, format_ip4_address, ia0); + } + else + vlib_cli_output (vm, "%=34s %=20U", + "No IPv4 address configured on", + format_vnet_sw_if_index_name, vnm, sw_if_index); + } + else + break; + } + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcp_proxy_address_show_command,static) = { + .path = "show dhcp option-82-address interface", + .short_help = "show dhcp option-82-address interface <interface>", + .function = dhcp_option_82_address_show_command_fn, +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp6_client_common_dp.c b/src/plugins/dhcp/dhcp6_client_common_dp.c new file mode 100644 index 00000000000..e42ec3f472c --- /dev/null +++ b/src/plugins/dhcp/dhcp6_client_common_dp.c @@ -0,0 +1,469 @@ +/* + * 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 <vnet/ethernet/ethernet.h> +#include <dhcp/dhcp6_packet.h> +#include <dhcp/dhcp6_client_common_dp.h> +#include <dhcp/dhcp6_ia_na_client_dp.h> +#include <dhcp/dhcp6_pd_client_dp.h> +#include <dhcp/dhcp6_packet.h> +#include <vnet/udp/udp.h> + +dhcp6_client_common_main_t dhcp6_client_common_main; +dhcpv6_duid_ll_string_t client_duid; + +u32 +server_index_get_or_create (u8 * data, u16 len) +{ + dhcp6_client_common_main_t *ccm = &dhcp6_client_common_main; + u32 i; + server_id_t *se; + server_id_t new_se; + + for (i = 0; i < vec_len (ccm->server_ids); i++) + { + se = &ccm->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 (ccm->server_ids, new_se); + + return vec_len (ccm->server_ids) - 1; +} + +static void +generate_client_duid (void) +{ + client_duid.duid_type = clib_host_to_net_u16 (DHCPV6_DUID_LL); + client_duid.hardware_type = clib_host_to_net_u16 (1); + + 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); + } +} + +#define foreach_dhcpv6_client \ + _(DROP, "error-drop") \ + _(LOOKUP, "ip6-lookup") + +typedef enum +{ +#define _(sym,str) DHCPV6_CLIENT_NEXT_##sym, + foreach_dhcpv6_client +#undef _ + DHCPV6_CLIENT_N_NEXT, +} dhcpv6_client_next_t; + +/** + * per-packet trace data + */ +typedef struct dhcpv6_client_trace_t_ +{ +} dhcpv6_client_trace_t; + +static u8 * +format_dhcpv6_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_client_trace_t *t = va_arg (*args, dhcpv6_client_trace_t *); + + s = format (s, "nothing"); + + return s; +} + +static uword +dhcpv6_client_node_fn (vlib_main_t * vm, vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + dhcp6_ia_na_client_main_t *icm = &dhcp6_ia_na_client_main; + dhcp6_pd_client_main_t *pcm = &dhcp6_pd_client_main; + + dhcpv6_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; + dhcp6_report_common_t report; + dhcp6_address_info_t *addresses = 0; + dhcp6_prefix_info_t *prefixes = 0; + u32 next0 = DHCPV6_CLIENT_NEXT_DROP; + u32 bi0; + u32 xid; + u32 sw_if_index; + u32 iaid; + u8 client_id_present = 0; + u8 discard = 0; + u8 is_pd_packet = 0; + + dhcp6_ia_na_client_state_t *ia_na_client_state = NULL; + dhcp6_pd_client_state_t *pd_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_payload_offset = + (u8 *) dhcpv60 - ((u8 *) ip0 + sizeof (*ip0)); + options_length = + clib_net_to_host_u16 (ip0->payload_length) - + dhcpv6_ip6_payload_offset - sizeof (*dhcpv60); + + clib_memset (&report, 0, sizeof (report)); + + sw_if_index = vnet_buffer (b0)->sw_if_index[VLIB_RX]; + if (sw_if_index >= vec_len (icm->client_state_by_sw_if_index)) + ia_na_client_state = 0; + else + ia_na_client_state = + &icm->client_state_by_sw_if_index[sw_if_index]; + if (sw_if_index >= vec_len (pcm->client_state_by_sw_if_index)) + pd_client_state = 0; + else + pd_client_state = &pcm->client_state_by_sw_if_index[sw_if_index]; + + xid = + (dhcpv60->xid[0] << 16) + (dhcpv60->xid[1] << 8) + + dhcpv60->xid[2]; + if (ia_na_client_state && ia_na_client_state->transaction_id == xid) + is_pd_packet = 0; + else if (pd_client_state && pd_client_state->transaction_id == xid) + is_pd_packet = 1; + else + { + 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 < + clib_net_to_host_u16 (option->length) + + sizeof (*option)) + { + clib_warning + ("remaining payload length < option length (%d < %d)", + options_length, + clib_net_to_host_u16 (option->length) + + sizeof (*option)); + break; + } + u16 oo = clib_net_to_host_u16 (option->option); + if (oo == DHCPV6_OPTION_IA_NA || oo == DHCPV6_OPTION_IA_PD) + { + u8 discard_option = 0; + dhcpv6_ia_header_t *ia_header = (void *) option; + iaid = clib_net_to_host_u32 (ia_header->iaid); + u32 T1 = clib_net_to_host_u32 (ia_header->t1); + u32 T2 = clib_net_to_host_u32 (ia_header->t2); + if (iaid != DHCPV6_CLIENT_IAID) + discard_option = 1; + if (T1 != 0 && T2 != 0 && T1 > T2) + discard_option = 1; + if (!discard_option) + { + report.T1 = T1; + report.T2 = T2; + } + dhcpv6_option_t *inner_option = + (void *) ia_header->data; + u16 inner_options_length = + clib_net_to_host_u16 (option->length) - + (sizeof (*ia_header) - sizeof (dhcpv6_option_t)); + while (inner_options_length > 0) + { + u16 inner_oo = + clib_net_to_host_u16 (inner_option->option); + if (discard_option) + ; + else if (inner_oo == DHCPV6_OPTION_IAADDR) + { + dhcpv6_ia_opt_addr_t *iaaddr = + (void *) inner_option; + u32 n_addresses = vec_len (addresses); + vec_validate (addresses, n_addresses); + dhcp6_address_info_t *address_info = + &addresses[n_addresses]; + address_info->preferred_time = + clib_net_to_host_u32 (iaaddr->preferred); + address_info->valid_time = + clib_net_to_host_u32 (iaaddr->valid); + address_info->address = iaaddr->addr; + } + else if (inner_oo == DHCPV6_OPTION_IAPREFIX) + { + dhcpv6_ia_opt_pd_t *iaprefix = + (void *) inner_option; + u32 n_prefixes = vec_len (prefixes); + vec_validate (prefixes, n_prefixes); + dhcp6_prefix_info_t *prefix_info = + &prefixes[n_prefixes]; + prefix_info->preferred_time = + clib_net_to_host_u32 (iaprefix->preferred); + prefix_info->valid_time = + clib_net_to_host_u32 (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 = + clib_net_to_host_u16 (sc->status_code); + } + inner_options_length -= + sizeof (*inner_option) + + clib_net_to_host_u16 (inner_option->length); + inner_option = + (void *) ((u8 *) inner_option + + sizeof (*inner_option) + + clib_net_to_host_u16 + (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 = clib_net_to_host_u16 (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 + { + u16 ol = clib_net_to_host_u16 (option->length); + if (ol - 2 /* 2 byte DUID type code */ > 128) + { + clib_warning + ("Server DUID (without type code) is longer than 128 octets"); + discard = 1; + } + else + { + report.server_index = + server_index_get_or_create (option->data, ol); + } + } + } + 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 = + clib_net_to_host_u16 (sc->status_code); + } + options_length -= + sizeof (*option) + clib_net_to_host_u16 (option->length); + option = + (void *) ((u8 *) option + sizeof (*option) + + clib_net_to_host_u16 (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) + { + if (!is_pd_packet) + { + address_report_t r; + r.body = report; + r.n_addresses = vec_len (addresses); + r.addresses = addresses; + dhcp6_publish_report (&r); + /* We just gave addresses to another process! */ + addresses = 0; + } + else + { + prefix_report_t r; + r.body = report; + r.n_prefixes = vec_len (prefixes); + r.prefixes = prefixes; + dhcp6_pd_publish_report (&r); + /* We just gave prefixes to another process! */ + prefixes = 0; + } + } + vec_free (addresses); + vec_free (prefixes); + + break; + default: + break; + } + + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcpv6_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_client_node, static) = { + .function = dhcpv6_client_node_fn, + .name = "dhcpv6-client", + .vector_size = sizeof (u32), + + .n_errors = 0, + + .n_next_nodes = DHCPV6_CLIENT_N_NEXT, + .next_nodes = { + #define _(s,n) [DHCPV6_CLIENT_NEXT_##s] = n, + foreach_dhcpv6_client + #undef _ + }, + + .format_trace = format_dhcpv6_client_trace, +}; +/* *INDENT-ON* */ + +void +dhcp6_clients_enable_disable (u8 enable) +{ + vlib_main_t *vm = vlib_get_main (); + + if (enable) + { + if (client_duid.duid_type == 0) + generate_client_duid (); + udp_register_dst_port (vm, UDP_DST_PORT_dhcpv6_to_client, + dhcpv6_client_node.index, 0 /* is_ip6 */ ); + } + else + udp_unregister_dst_port (vm, UDP_DST_PORT_dhcpv6_to_client, + 0 /* is_ip6 */ ); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp6_client_common_dp.h b/src/plugins/dhcp/dhcp6_client_common_dp.h new file mode 100644 index 00000000000..4010aa07564 --- /dev/null +++ b/src/plugins/dhcp/dhcp6_client_common_dp.h @@ -0,0 +1,94 @@ +/* + * 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_client_common_dp_h +#define included_vnet_dhcp6_client_common_dp_h + +#include <vlib/vlib.h> +#include <dhcp/dhcp6_packet.h> + +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; +} dhcp6_report_common_t; + +typedef struct +{ + u8 *data; + u16 len; +} server_id_t; + +typedef struct +{ + server_id_t *server_ids; +} dhcp6_client_common_main_t; + +extern dhcp6_client_common_main_t dhcp6_client_common_main; + +typedef union +{ + CLIB_PACKED (struct + { + u16 duid_type; + u16 hardware_type; + u8 lla[6]; + }); + char bin_string[10]; +} dhcpv6_duid_ll_string_t; + +extern dhcpv6_duid_ll_string_t client_duid; +#define CLIENT_DUID_LENGTH sizeof (client_duid) +#define DHCPV6_CLIENT_IAID 1 + +void dhcp6_clients_enable_disable (u8 enable); +u32 server_index_get_or_create (u8 * data, u16 len); + +extern dhcpv6_duid_ll_string_t client_duid; + +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} +}; + +#endif /* included_vnet_dhcp6_client_common_dp_h */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp6_ia_na_client_cp.api b/src/plugins/dhcp/dhcp6_ia_na_client_cp.api new file mode 100644 index 00000000000..caa4fd4afc8 --- /dev/null +++ b/src/plugins/dhcp/dhcp6_ia_na_client_cp.api @@ -0,0 +1,38 @@ +/* + * 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.1"; + +import "vnet/interface_types.api"; + +/** \brief Enable/disable DHCPv6 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 enable - 1 to enable, 0 to disable +*/ +autoreply define dhcp6_client_enable_disable +{ + u32 client_index; + u32 context; + vl_api_interface_index_t sw_if_index; + bool enable; +}; + +/* + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp6_ia_na_client_cp.c b/src/plugins/dhcp/dhcp6_ia_na_client_cp.c new file mode 100644 index 00000000000..2d4135d7ebd --- /dev/null +++ b/src/plugins/dhcp/dhcp6_ia_na_client_cp.c @@ -0,0 +1,775 @@ +/* + * 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 <vnet/vnet.h> +#include <vlibmemory/api.h> +#include <vnet/vnet_msg_enum.h> +#include <dhcp/dhcp6_packet.h> +#include <dhcp/dhcp6_ia_na_client_dp.h> +#include <vnet/ip/ip.h> +#include <vnet/ip/ip6.h> +#include <float.h> +#include <math.h> + +typedef struct +{ + u32 sw_if_index; + ip6_address_t address; + u32 preferred_lt; + u32 valid_lt; + f64 due_time; +} address_info_t; + +typedef struct +{ + u8 enabled; + u32 server_index; + u32 T1; + u32 T2; + f64 T1_due_time; + f64 T2_due_time; + u32 address_count; + u8 rebinding; +} client_state_t; + +typedef struct +{ + address_info_t *address_pool; + client_state_t *client_state_by_sw_if_index; + 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_client_cp_main_t; + +static dhcp6_client_cp_main_t dhcp6_client_cp_main; + +enum +{ + RD_CP_EVENT_INTERRUPT, + RD_CP_EVENT_DISABLE, +}; + +static void +send_client_message_start_stop (u32 sw_if_index, u32 server_index, + u8 msg_type, address_info_t * address_list, + u8 start) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + dhcp6_send_client_message_params_t params = { 0, }; + dhcp6_send_client_message_params_address_t *addresses = 0, *addr; + 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 (address_list) != 0) + vec_validate (addresses, vec_len (address_list) - 1); + for (i = 0; i < vec_len (address_list); i++) + { + address_info_t *address = &address_list[i]; + addr = &addresses[i]; + addr->valid_lt = address->valid_lt; + addr->preferred_lt = address->preferred_lt; + addr->address = address->address; + } + params.addresses = addresses; + + dhcp6_send_client_message (rm->vlib_main, sw_if_index, !start, ¶ms); + + vec_free (params.addresses); +} + +static void interrupt_process (void); + +static u32 +ip6_enable (u32 sw_if_index) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + clib_error_t *rv; + + rv = enable_ip6_interface (rm->vlib_main, sw_if_index); + + return rv != 0; +} + +static u8 +ip6_addresses_equal (ip6_address_t * address1, ip6_address_t * address2) +{ + if (address1->as_u64[0] != address2->as_u64[0]) + return 0; + return address1->as_u64[1] == address2->as_u64[1]; +} + +static clib_error_t * +dhcp6_reply_event_handler (vl_api_dhcp6_reply_event_t * mp) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + vlib_main_t *vm = rm->vlib_main; + client_state_t *client_state; + ip6_address_t *address; + u32 sw_if_index; + u32 n_addresses; + vl_api_dhcp6_address_info_t *api_address; + 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_addresses = ntohl (mp->n_addresses); + + inner_status_code = ntohs (mp->inner_status_code); + status_code = ntohs (mp->status_code); + + if (mp->msg_type == DHCPV6_MSG_API_ADVERTISE + && client_state->server_index == ~0) + { + address_info_t *address_list = 0, *address_info; + + if (inner_status_code == DHCPV6_STATUS_NOADDRS_AVAIL) + { + clib_warning + ("Advertise message arrived with NoAddrsAvail status code"); + return 0; + } + + if (n_addresses > 0) + vec_validate (address_list, n_addresses - 1); + for (i = 0; i < n_addresses; i++) + { + api_address = &mp->addresses[i]; + address = (ip6_address_t *) api_address->address; + + address_info = &address_list[i]; + address_info->address = *address; + address_info->preferred_lt = 0; + address_info->valid_lt = 0; + } + + client_state->server_index = server_index; + + send_client_message_start_stop (sw_if_index, server_index, + DHCPV6_MSG_REQUEST, address_list, 1); + vec_free (address_list); + } + + if (mp->msg_type != DHCPV6_MSG_API_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 or Renew message"); + return 0; + } + + if (inner_status_code == DHCPV6_STATUS_NOADDRS_AVAIL) + { + clib_warning ("Reply message arrived with NoAddrsAvail status code"); + if (n_addresses > 0) + { + clib_warning + ("Invalid Reply message arrived: It contains NoAddrsAvail " + "status code but also contains addresses"); + 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_addresses; i++) + { + address_info_t *address_info = 0; + u32 valid_time; + u32 preferred_time; + + api_address = &mp->addresses[i]; + + address = (ip6_address_t *) api_address->address; + + if (ip6_address_is_link_local_unicast (address)) + continue; + + valid_time = ntohl (api_address->valid_time); + preferred_time = ntohl (api_address->preferred_time); + + if (preferred_time > valid_time) + continue; + + u8 address_already_present = 0; + /* *INDENT-OFF* */ + pool_foreach (address_info, rm->address_pool, + ({ + if (address_info->sw_if_index != sw_if_index) + ; + else if (!ip6_addresses_equal (&address_info->address, address)) + ; + else + { + address_already_present = 1; + goto address_pool_foreach_out; + } + })); + /* *INDENT-ON* */ + address_pool_foreach_out: + + if (address_already_present) + { + address_info->preferred_lt = preferred_time; + address_info->valid_lt = valid_time; + address_info->due_time = current_time + valid_time; + if (address_info->due_time > rm->max_valid_due_time) + rm->max_valid_due_time = address_info->due_time; + continue; + } + + if (valid_time == 0) + continue; + + pool_get (rm->address_pool, address_info); + address_info->sw_if_index = sw_if_index; + address_info->address = *address; + address_info->preferred_lt = preferred_time; + address_info->valid_lt = valid_time; + address_info->due_time = current_time + valid_time; + if (address_info->due_time > rm->max_valid_due_time) + rm->max_valid_due_time = address_info->due_time; + rm->client_state_by_sw_if_index[sw_if_index].address_count++; + + error = ip6_add_del_interface_address (vm, sw_if_index, + &address_info->address, 64, 0); + if (error) + clib_warning ("Failed to add interface address"); + } + + 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 address_info_t * +create_address_list (u32 sw_if_index) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + address_info_t *address_info, *address_list = 0;; + + /* *INDENT-OFF* */ + pool_foreach (address_info, rm->address_pool, + ({ + if (address_info->sw_if_index == sw_if_index) + { + u32 pos = vec_len (address_list); + vec_validate (address_list, pos); + clib_memcpy (&address_list[pos], address_info, sizeof (*address_info)); + } + })); + /* *INDENT-ON* */ + + return address_list; +} + +VNET_DHCP6_REPLY_EVENT_FUNCTION (dhcp6_reply_event_handler); + +static uword +dhcp6_client_cp_process (vlib_main_t * vm, vlib_node_runtime_t * rt, + vlib_frame_t * f) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + address_info_t *address_info; + client_state_t *client_state; + f64 sleep_time = 1e9; + clib_error_t *error; + 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 == RD_CP_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 (address_info, rm->address_pool, + ({ + if (address_info->due_time > current_time) + { + if (address_info->due_time < due_time) + due_time = address_info->due_time; + } + else + { + u32 sw_if_index = address_info->sw_if_index; + error = ip6_add_del_interface_address (vm, sw_if_index, + &address_info->address, + 64, 1); + if (error) + clib_warning ("Failed to delete interface address"); + pool_put (rm->address_pool, address_info); + /* make sure ip6 stays enabled */ + ip6_enable (sw_if_index); + client_state = &rm->client_state_by_sw_if_index[sw_if_index]; + if (--client_state->address_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; + address_info_t *address_list; + address_list = create_address_list (i); + cs->rebinding = 1; + send_client_message_start_stop (i, cs->server_index, + DHCPV6_MSG_RENEW, + address_list, 1); + vec_free (address_list); + } + } + else + { + cs->T2_due_time = DBL_MAX; + address_info_t *address_list; + address_list = create_address_list (i); + cs->rebinding = 1; + send_client_message_start_stop (i, ~0, + DHCPV6_MSG_REBIND, + address_list, 1); + vec_free (address_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_client_cp_process_node) = { + .function = dhcp6_client_cp_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "dhcp6-client-cp-process", +}; +/* *INDENT-ON* */ + +static void +interrupt_process (void) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + vlib_main_t *vm = rm->vlib_main; + + vlib_process_signal_event (vm, dhcp6_client_cp_process_node.index, + RD_CP_EVENT_INTERRUPT, 0); +} + +static void +disable_process (void) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + vlib_main_t *vm = rm->vlib_main; + + vlib_process_signal_event (vm, dhcp6_client_cp_process_node.index, + RD_CP_EVENT_DISABLE, 0); +} + +static void +enable_process (void) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_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 clib_error_t * +dhcp6_addresses_show_command_function (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + dhcp6_client_cp_main_t *dm = &dhcp6_client_cp_main; + clib_error_t *error = 0; + address_info_t *address_info; + f64 current_time = vlib_time_now (vm); + + /* *INDENT-OFF* */ + pool_foreach (address_info, dm->address_pool, + ({ + vlib_cli_output (vm, "address: %U, " + "preferred lifetime: %u, valid lifetime: %u " + "(%f remaining)", + format_ip6_address, &address_info->address, + address_info->preferred_lt, address_info->valid_lt, + address_info->due_time - current_time); + })); + /* *INDENT-ON* */ + + return error; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcp6_addresses_show_command, static) = { + .path = "show dhcp6 addresses", + .short_help = "show dhcp6 addresses", + .function = dhcp6_addresses_show_command_function, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dhcp6_clients_show_command_function (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + clib_error_t *error = 0; + client_state_t *cs; + f64 current_time = vlib_time_now (vm); + 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 = ""; + if (cs->T1) + vlib_cli_output (vm, + "sw_if_index: %u, T1: %u (%s), " + "T2: %u (%s), server index: %d%s", i, + cs->T1, buf1, cs->T2, buf2, + cs->server_index, rebinding); + else + vlib_cli_output (vm, "sw_if_index: %u%s", i, rebinding); + } + } + + return error; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcp6_clients_show_command, static) = { + .path = "show dhcp6 clients", + .short_help = "show dhcp6 clients", + .function = dhcp6_clients_show_command_function, +}; +/* *INDENT-ON* */ + +int +dhcp6_client_enable_disable (u32 sw_if_index, u8 enable) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + vnet_main_t *vnm = rm->vnet_main; + vlib_main_t *vm = rm->vlib_main; + client_state_t *client_state; + client_state_t empty_config = { 0 }; + address_info_t *address_info; + clib_error_t *error; + + if (!vnet_sw_interface_is_api_valid (vnm, sw_if_index)) + { + clib_warning ("Invalid sw_if_index"); + return 1; + } + + 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) + client_state->enabled = 1; + client_state->server_index = ~0; + + if (!old_enabled && enable) + { + 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 (); + } + + /* *INDENT-OFF* */ + pool_foreach (address_info, rm->address_pool, + ({ + if (address_info->sw_if_index == 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]; + send_client_message_start_stop (sw_if_index, + client_state->server_index, + DHCPV6_MSG_RELEASE, address_info, + 1); + error = ip6_add_del_interface_address (vm, sw_if_index, + &address_info->address, + 64, 1); + if (error) + clib_warning ("Failed to delete interface address"); + pool_put (rm->address_pool, address_info); + } + })); + /* *INDENT-ON* */ + } + + if (!enable) + client_state->enabled = 0; + + return 0; +} + +static clib_error_t * +dhcp6_client_enable_disable_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + vnet_main_t *vnm = rm->vnet_main; + clib_error_t *error = 0; + u32 sw_if_index = ~0; + u8 enable = 1; + unformat_input_t _line_input, *line_input = &_line_input; + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat + (line_input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index)) + ; + else if (unformat (line_input, "disable")) + enable = 0; + else + { + error = clib_error_return (0, "unexpected input `%U'", + format_unformat_error, line_input); + goto done; + } + } + + unformat_free (line_input); + + if (sw_if_index != ~0) + { + if (dhcp6_client_enable_disable (sw_if_index, enable) != 0) + error = clib_error_return (0, "Invalid sw_if_index"); + } + else + error = clib_error_return (0, "Missing sw_if_index"); + +done: + return error; +} + +/*? + * This command is used to enable/disable DHCPv6 client + * on particular interface. + * + * @cliexpar + * @parblock + * Example of how to enable DHCPv6 client: + * @cliexcmd{dhcp6 client GigabitEthernet2/0/0} + * Example of how to disable DHCPv6 client: + * @cliexcmd{dhcp6 client GigabitEthernet2/0/0 disable} + * @endparblock +?*/ +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcp6_client_enable_disable_command, static) = { + .path = "dhcp6 client", + .short_help = "dhcp6 client <interface> [disable]", + .function = dhcp6_client_enable_disable_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dhcp_ia_na_client_cp_init (vlib_main_t * vm) +{ + dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main; + + rm->vlib_main = vm; + rm->vnet_main = vnet_get_main (); + rm->api_main = &api_main; + rm->node_index = dhcp6_client_cp_process_node.index; + + return NULL; +} + +VLIB_INIT_FUNCTION (dhcp_ia_na_client_cp_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp6_ia_na_client_cp_api.c b/src/plugins/dhcp/dhcp6_ia_na_client_cp_api.c new file mode 100644 index 00000000000..8e5898adb36 --- /dev/null +++ b/src/plugins/dhcp/dhcp6_ia_na_client_cp_api.c @@ -0,0 +1,80 @@ +/* + *------------------------------------------------------------------ + * dhcp_api.c - dhcp api + * + * Copyright (c) 2016 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 <vnet/vnet.h> +#include <vlibmemory/api.h> + +#include <dhcp/dhcp6_ia_na_client_dp.h> + +/* define message IDs */ +#include <vnet/format_fns.h> +#include <dhcp/dhcp6_ia_na_client_cp.api_enum.h> +#include <dhcp/dhcp6_ia_na_client_cp.api_types.h> + +/** + * Base message ID fot the plugin + */ +static u32 dhcp_base_msg_id; +#define REPLY_MSG_ID_BASE dhcp_base_msg_id + +#include <vlibapi/api_helper_macros.h> + +static void + vl_api_dhcp6_client_enable_disable_t_handler + (vl_api_dhcp6_client_enable_disable_t * mp) +{ + vl_api_dhcp6_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_client_enable_disable (sw_if_index, mp->enable); + + BAD_SW_IF_INDEX_LABEL; + + REPLY_MACRO (VL_API_DHCP6_CLIENT_ENABLE_DISABLE_REPLY); +} + +#define vl_msg_name_crc_list +#include <dhcp/dhcp6_ia_na_client_cp.api.c> +#undef vl_msg_name_crc_list + +static clib_error_t * +dhcp_ia_na_client_cp_api_init (vlib_main_t * vm) +{ + /* + * Set up the (msg_name, crc, message-id) table + */ + dhcp_base_msg_id = setup_message_id_table (); + + return 0; +} + +VLIB_INIT_FUNCTION (dhcp_ia_na_client_cp_api_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp6_ia_na_client_dp.c b/src/plugins/dhcp/dhcp6_ia_na_client_dp.c new file mode 100644 index 00000000000..6ea822fff48 --- /dev/null +++ b/src/plugins/dhcp/dhcp6_ia_na_client_dp.c @@ -0,0 +1,436 @@ +/* + * 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 <vlib/vlib.h> +#include <dhcp/dhcp6_packet.h> +#include <dhcp/dhcp_proxy.h> +#include <vnet/mfib/mfib_table.h> +#include <vnet/mfib/ip6_mfib.h> +#include <vnet/fib/fib.h> +#include <vnet/adj/adj_mcast.h> +#include <vnet/ip/ip6_neighbor.h> +#include <dhcp/dhcp6_ia_na_client_dp.h> +#include <dhcp/dhcp6_client_common_dp.h> +#include <vnet/ip/ip_types_api.h> + +dhcp6_ia_na_client_main_t dhcp6_ia_na_client_main; +dhcp6_ia_na_client_public_main_t dhcp6_ia_na_client_public_main; + +static void +signal_report (address_report_t * r) +{ + vlib_main_t *vm = vlib_get_main (); + dhcp6_ia_na_client_main_t *cm = &dhcp6_ia_na_client_main; + uword ni = cm->publisher_node; + uword et = cm->publisher_et; + + if (ni == (uword) ~ 0) + return; + address_report_t *q = + vlib_process_signal_event_data (vm, ni, et, 1, sizeof *q); + + *q = *r; +} + +int +dhcp6_publish_report (address_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_set_publisher_node (uword node_index, uword event_type) +{ + dhcp6_ia_na_client_main_t *cm = &dhcp6_ia_na_client_main; + cm->publisher_node = node_index; + cm->publisher_et = event_type; +} + +static void +stop_sending_client_message (vlib_main_t * vm, + dhcp6_ia_na_client_state_t * client_state) +{ + u32 bi0; + + client_state->keep_sending_client_message = 0; + vec_free (client_state->params.addresses); + 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 vlib_buffer_t * +create_buffer_for_client_message (vlib_main_t * vm, u32 sw_if_index, + dhcp6_ia_na_client_state_t * client_state, + u32 type) +{ + dhcp6_client_common_main_t *ccm = &dhcp6_client_common_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_addresses; + 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_addr_t *opt_addr; + 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 = + &ccm->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_NA); + 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_addresses = vec_len (client_state->params.addresses); + + ia_hdr->opt.length = + clib_host_to_net_u16 (sizeof (*ia_hdr) + + n_addresses * sizeof (*opt_addr) - + sizeof (ia_hdr->opt)); + + for (i = 0; i < n_addresses; i++) + { + dhcp6_send_client_message_params_address_t *addr = + &client_state->params.addresses[i]; + opt_addr = (dhcpv6_ia_opt_addr_t *) d; + opt_addr->opt.option = clib_host_to_net_u16 (DHCPV6_OPTION_IAADDR); + opt_addr->opt.length = + clib_host_to_net_u16 (sizeof (*opt_addr) - + sizeof (opt_addr->opt)); + opt_addr->addr = addr->address; + opt_addr->valid = clib_host_to_net_u32 (addr->valid_lt); + opt_addr->preferred = clib_host_to_net_u32 (addr->preferred_lt); + d += sizeof (*opt_addr); + } + } + 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_send_client_message (vlib_main_t * vm, + dhcp6_ia_na_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_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_client_message_process (vlib_main_t * vm, + vlib_node_runtime_t * rt, + vlib_frame_t * f0) +{ + dhcp6_ia_na_client_main_t *cm = &dhcp6_ia_na_client_main; + dhcp6_ia_na_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_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_client_message_process_node, static) = { + .function = send_dhcp6_client_message_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "send-dhcp6-client-message-process", +}; +/* *INDENT-ON* */ + +void +dhcp6_send_client_message (vlib_main_t * vm, u32 sw_if_index, u8 stop, + dhcp6_send_client_message_params_t * params) +{ + dhcp6_ia_na_client_main_t *cm = &dhcp6_ia_na_client_main; + dhcp6_ia_na_client_state_t *client_state = 0; + dhcp6_ia_na_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.addresses); + client_state->params = *params; + client_state->params.addresses = vec_dup (params->addresses); + 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_client_message_process_node.index, + 1, 0); + } +} + +static clib_error_t * +dhcp6_client_init (vlib_main_t * vm) +{ + dhcp6_ia_na_client_main_t *cm = &dhcp6_ia_na_client_main; + + cm->vlib_main = vm; + cm->vnet_main = vnet_get_main (); + + cm->publisher_node = ~0; + + cm->seed = 0xdeaccabe; + + return 0; +} + +VLIB_INIT_FUNCTION (dhcp6_client_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp6_ia_na_client_dp.h b/src/plugins/dhcp/dhcp6_ia_na_client_dp.h new file mode 100644 index 00000000000..88a6b75ecdd --- /dev/null +++ b/src/plugins/dhcp/dhcp6_ia_na_client_dp.h @@ -0,0 +1,165 @@ +/* + * 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_client_dp_h +#define included_vnet_dhcp6_client_dp_h + +#include <stdbool.h> + +#include <dhcp/dhcp6_client_common_dp.h> + +typedef struct +{ + u32 preferred_lt; + u32 valid_lt; + ip6_address_t address; +} dhcp6_send_client_message_params_address_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_send_client_message_params_address_t *addresses; +} dhcp6_send_client_message_params_t; + +typedef struct +{ + u8 entry_valid; + u8 keep_sending_client_message; /* when true then next fields are valid */ + dhcp6_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_ia_na_client_state_t; + +typedef struct +{ + dhcp6_ia_na_client_state_t *client_state_by_sw_if_index; + + uword publisher_node; + uword publisher_et; + + u32 seed; + + /* convenience */ + vlib_main_t *vlib_main; + vnet_main_t *vnet_main; +} dhcp6_ia_na_client_main_t; + +extern dhcp6_ia_na_client_main_t dhcp6_ia_na_client_main; + +typedef struct +{ + ip6_address_t address; + u32 valid_time; + u32 preferred_time; + u16 status_code; +} dhcp6_address_info_t; + +typedef struct +{ + dhcp6_report_common_t body; + u32 n_addresses; + dhcp6_address_info_t *addresses; +} address_report_t; + +void dhcp6_send_client_message (vlib_main_t * vm, u32 sw_if_index, u8 stop, + dhcp6_send_client_message_params_t * params); +void dhcp6_set_publisher_node (uword node_index, uword event_type); +int dhcp6_publish_report (address_report_t * r); +int dhcp6_client_enable_disable (u32 sw_if_index, u8 enable); + +extern vlib_node_registration_t dhcp6_reply_process_node; + +enum +{ DHCP6_DP_REPLY_REPORT, DHCP6_DP_REPORT_MAX }; + +#include <dhcp/dhcp.api_types.h> + +typedef struct _vnet_dhcp6_reply_function_list_elt +{ + struct _vnet_dhcp6_reply_function_list_elt *next_dhcp6_reply_event_function; + clib_error_t *(*fp) (vl_api_dhcp6_reply_event_t * mp); +} _vnet_dhcp6_reply_event_function_list_elt_t; + +typedef struct +{ + _vnet_dhcp6_reply_event_function_list_elt_t *functions; +} dhcp6_ia_na_client_public_main_t; + +extern dhcp6_ia_na_client_public_main_t dhcp6_ia_na_client_public_main; + +#define VNET_DHCP6_REPLY_EVENT_FUNCTION(f) \ + \ +static void __vnet_dhcp6_reply_event_function_init_##f (void) \ + __attribute__((__constructor__)) ; \ + \ +static void __vnet_dhcp6_reply_event_function_init_##f (void) \ +{ \ + dhcp6_ia_na_client_public_main_t * nm = &dhcp6_ia_na_client_public_main; \ + static _vnet_dhcp6_reply_event_function_list_elt_t init_function; \ + init_function.next_dhcp6_reply_event_function = nm->functions; \ + nm->functions = &init_function; \ + init_function.fp = (void *) &f; \ +} \ + \ +static void __vnet_dhcp6_reply_event_function_deinit_##f (void) \ + __attribute__((__destructor__)) ; \ + \ +static void __vnet_dhcp6_reply_event_function_deinit_##f (void) \ +{ \ + dhcp6_ia_na_client_public_main_t * nm = &dhcp6_ia_na_client_public_main; \ + _vnet_dhcp6_reply_event_function_list_elt_t *next; \ + if (nm->functions->fp == (void *) &f) \ + { \ + nm->functions = \ + nm->functions->next_dhcp6_reply_event_function; \ + return; \ + } \ + next = nm->functions; \ + while (next->next_dhcp6_reply_event_function) \ + { \ + if (next->next_dhcp6_reply_event_function->fp == (void *) &f) \ + { \ + next->next_dhcp6_reply_event_function = \ + next->next_dhcp6_reply_event_function->next_dhcp6_reply_event_function; \ + return; \ + } \ + next = next->next_dhcp6_reply_event_function; \ + } \ +} + +#endif /* included_vnet_dhcp6_client_dp_h */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp6_packet.h b/src/plugins/dhcp/dhcp6_packet.h new file mode 100644 index 00000000000..d5467952a64 --- /dev/null +++ b/src/plugins/dhcp/dhcp6_packet.h @@ -0,0 +1,271 @@ +#ifndef included_vnet_dhcp6_packet_h +#define included_vnet_dhcp6_packet_h + +/* + * DHCP packet format + * + * Copyright (c) 2013 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 <vnet/ip/ip6_packet.h> + +// #define DHCP_VRF_NAME_MAX_LEN L3VM_MAX_NAME_STR_LEN +// #define DHCPV6_MAX_VRF_NAME_LEN L3VM_MAX_NAME_STR_LEN +#define DHCP_MAX_RELAY_ADDR 16 +#define PROTO_UDP 17 +#define DHCPV6_CLIENT_PORT 546 +#define DHCPV6_SERVER_PORT 547 +#define HOP_COUNT_LIMIT 32 +#define DHCPV6_CISCO_ENT_NUM 9 + +/* + * DHCPv6 message types + */ +typedef enum dhcpv6_msg_type_ +{ + DHCPV6_MSG_SOLICIT = 1, + DHCPV6_MSG_ADVERTISE = 2, + DHCPV6_MSG_REQUEST = 3, + DHCPV6_MSG_CONFIRM = 4, + DHCPV6_MSG_RENEW = 5, + DHCPV6_MSG_REBIND = 6, + DHCPV6_MSG_REPLY = 7, + DHCPV6_MSG_RELEASE = 8, + DHCPV6_MSG_DECLINE = 9, + DHCPV6_MSG_RECONFIGURE = 10, + DHCPV6_MSG_INFORMATION_REQUEST = 11, + DHCPV6_MSG_RELAY_FORW = 12, + 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 +{ +#define _(a,b,c) DHCPV6_OPTION_##a = b, + dhcpv6_foreach_option +#undef _ + DHCPV6_OPTION_MAX +}; + +/* +* DHCPv6 status codes + */ +enum +{ + DHCPV6_STATUS_SUCCESS = 0, + DHCPV6_STATUS_UNSPEC_FAIL = 1, + DHCPV6_STATUS_NOADDRS_AVAIL = 2, + DHCPV6_STATUS_NO_BINDING = 3, + DHCPV6_STATUS_NOT_ONLINK = 4, + DHCPV6_STATUS_USE_MULTICAST = 5, + DHCPV6_STATUS_NOPREFIX_AVAIL = 6, +}; + +/* + * DHCPv6 DUID types + */ +enum +{ + DHCPV6_DUID_LLT = 1, /* DUID Based on Link-layer Address Plus Time */ + DHCPV6_DUID_EN = 2, /* DUID Based on Enterprise Number */ + DHCPV6_DUID_LL = 3, /* DUID Based on Link-layer Address */ +}; + +//Structure for DHCPv6 payload from client +typedef struct dhcpv6_hdr_ +{ + 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; + u32 pkt_len; + u32 dhcpv6_len; //DHCPv6 payload load +// if_ordinal iod; + u32 if_index; + u32 ctx_id; + char ctx_name[32+1]; + u8 dhcp_msg_type; +}) dhcpv6_relay_ctx_t; +/* *INDENT-ON* */ + +//Structure for DHCPv6 RELAY-FORWARD and DHCPv6 RELAY-REPLY pkts +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct dhcpv6_relay_hdr_ { + u8 msg_type; + u8 hop_count; + ip6_address_t link_addr; + ip6_address_t peer_addr; + u8 data[0]; +}) dhcpv6_relay_hdr_t; +/* *INDENT-ON* */ + +typedef enum dhcp_stats_action_type_ +{ + DHCP_STATS_ACTION_FORWARDED = 1, + DHCP_STATS_ACTION_RECEIVED, + DHCP_STATS_ACTION_DROPPED +} dhcp_stats_action_type_t; +//Generic counters for a packet +typedef struct dhcp_stats_counters_ +{ + u64 rx_pkts; //counter for received pkts + u64 tx_pkts; //counter for forwarded pkts + u64 drops; //counter for dropped pkts +} dhcp_stats_counters_t; + + +typedef enum dhcpv6_stats_drop_reason_ +{ + DHCPV6_RELAY_PKT_DROP_RELAYDISABLE = 1, + DHCPV6_RELAY_PKT_DROP_MAX_HOPS, + DHCPV6_RELAY_PKT_DROP_VALIDATION_FAIL, + DHCPV6_RELAY_PKT_DROP_UNKNOWN_OP_INTF, + DHCPV6_RELAY_PKT_DROP_BAD_CONTEXT, + DHCPV6_RELAY_PKT_DROP_OPT_INSERT_FAIL, + 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; + u16 length; + u8 data[0]; +}) 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; + u32 int_idx; +}) dhcpv6_int_id_t; +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + dhcpv6_option_t opt; + u8 vss_type; + u8 data[0]; +}) dhcpv6_vss_t; +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + dhcpv6_option_t opt; + u32 ent_num; + u32 rmt_id; +}) dhcpv6_rmt_id_t; +/* *INDENT-ON* */ + +/* *INDENT-OFF* */ +typedef CLIB_PACKED (struct { + dhcpv6_option_t opt; + u16 link_type; + u8 data[6]; // data[0]:data[5]: MAC address +}) 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; ip6_address_t addr; u32 preferred; + u32 valid; + }) dhcpv6_ia_opt_addr_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 */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp6_pd_client_cp.api b/src/plugins/dhcp/dhcp6_pd_client_cp.api new file mode 100644 index 00000000000..43ed868e81e --- /dev/null +++ b/src/plugins/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/plugins/dhcp/dhcp6_pd_client_cp.c b/src/plugins/dhcp/dhcp6_pd_client_cp.c new file mode 100644 index 00000000000..6f151430e58 --- /dev/null +++ b/src/plugins/dhcp/dhcp6_pd_client_cp.c @@ -0,0 +1,1324 @@ +/* + * 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 <vnet/vnet.h> +#include <vlibmemory/api.h> +#include <dhcp/dhcp6_packet.h> +#include <dhcp/dhcp6_pd_client_dp.h> +#include <vnet/ip/ip.h> +#include <vnet/ip/ip6.h> +#include <float.h> +#include <math.h> +#include <string.h> + +typedef struct +{ + u32 prefix_group_index; + uword opaque_data; // used by prefix publisher + 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_API_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.address; + prefix_length = api_prefix->prefix.len; + + 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_API_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 or 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.address; + prefix_length = api_prefix->prefix.len; + + 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.len; + + 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; + if (prefix_info->due_time > rm->max_valid_due_time) + rm->max_valid_due_time = prefix_info->due_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; + if (prefix_info->due_time > rm->max_valid_due_time) + rm->max_valid_due_time = prefix_info->due_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 = ((u64) 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; + } +} + +int +dhcp6_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; + vnet_main_t *vnm = vnet_get_main (); + ip6_address_info_t *address_info; + u32 prefix_group_index; + u32 n; + + if (!vnet_sw_interface_is_api_valid (vnm, sw_if_index)) + { + clib_warning ("Invalid sw_if_index"); + return VNET_API_ERROR_INVALID_VALUE; + } + + 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 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; + unformat_input_t _line_input, *line_input = &_line_input; + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat + (line_input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index)); + else if (unformat (line_input, "prefix group %s", &prefix_group)); + else + if (unformat (line_input, "%U/%d", unformat_ip6_address, + &address, &prefix_length)) + address_set = 1; + else if (unformat (line_input, "del")) + add = 0; + else + { + error = clib_error_return (0, "unexpected input `%U'", + format_unformat_error, line_input); + unformat_free (line_input); + goto done; + } + } + + unformat_free (line_input); + + 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 (dhcp6_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 <interface> [prefix group <string>] " + "<address> [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* */ + + + +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); + + clib_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; + unformat_input_t _line_input, *line_input = &_line_input; + + if (!unformat_user (input, unformat_line_input, line_input)) + return 0; + + while (unformat_check_input (line_input) != UNFORMAT_END_OF_INPUT) + { + if (unformat + (line_input, "%U", unformat_vnet_sw_interface, vnm, &sw_if_index)) + ; + else if (unformat (line_input, "prefix group %s", &prefix_group)); + else if (unformat (line_input, "disable")) + enable = 0; + else + { + error = clib_error_return (0, "unexpected input `%U'", + format_unformat_error, line_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); + unformat_free (line_input); + + 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 <interface> (prefix group <string> | disable)", + .function = dhcp6_pd_client_enable_disable_command_fn, +}; +/* *INDENT-ON* */ + +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; + + rm->vlib_main = vm; + rm->vnet_main = vnet_get_main (); + rm->api_main = &api_main; + rm->node_index = dhcp6_pd_client_cp_process_node.index; + + return (NULL); +} + +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/plugins/dhcp/dhcp6_pd_client_cp_api.c b/src/plugins/dhcp/dhcp6_pd_client_cp_api.c new file mode 100644 index 00000000000..4999fd7f623 --- /dev/null +++ b/src/plugins/dhcp/dhcp6_pd_client_cp_api.c @@ -0,0 +1,102 @@ +/* + * 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 <vnet/vnet.h> +#include <vlibmemory/api.h> + +#include <dhcp/dhcp6_pd_client_dp.h> + +/* define message IDs */ +#include <vnet/format_fns.h> +#include <dhcp/dhcp6_pd_client_cp.api_enum.h> +#include <dhcp/dhcp6_pd_client_cp.api_types.h> + +/** + * Base message ID fot the plugin + */ +static u32 dhcp_base_msg_id; +#define REPLY_MSG_ID_BASE dhcp_base_msg_id + +#include <vlibapi/api_helper_macros.h> + +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_DHCP6_PD_CLIENT_ENABLE_DISABLE_REPLY); +} + +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; + + VALIDATE_SW_IF_INDEX (mp); + + sw_if_index = ntohl (mp->sw_if_index); + + memcpy (address.as_u8, mp->address, 16); + prefix_length = mp->prefix_length; + + rv = dhcp6_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_IP6_ADD_DEL_ADDRESS_USING_PREFIX_REPLY); +} + +#define vl_msg_name_crc_list +#include <dhcp/dhcp6_pd_client_cp.api.c> +#undef vl_msg_name_crc_list + +static clib_error_t * +dhcp_pd_client_cp_api_init (vlib_main_t * vm) +{ + /* + * Set up the (msg_name, crc, message-id) table + */ + dhcp_base_msg_id = setup_message_id_table (); + + return 0; +} + +VLIB_INIT_FUNCTION (dhcp_pd_client_cp_api_init); + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp6_pd_client_dp.c b/src/plugins/dhcp/dhcp6_pd_client_dp.c new file mode 100644 index 00000000000..74a1f16000c --- /dev/null +++ b/src/plugins/dhcp/dhcp6_pd_client_dp.c @@ -0,0 +1,437 @@ +/* + * 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 <vlib/vlib.h> +#include <dhcp/dhcp6_packet.h> +#include <dhcp/dhcp_proxy.h> +#include <vnet/mfib/mfib_table.h> +#include <vnet/mfib/ip6_mfib.h> +#include <vnet/fib/fib.h> +#include <vnet/adj/adj_mcast.h> +#include <vnet/ip/ip6_neighbor.h> +#include <dhcp/dhcp6_pd_client_dp.h> +#include <dhcp/dhcp6_client_common_dp.h> +#include <vnet/ip/ip_types_api.h> + +dhcp6_pd_client_main_t dhcp6_pd_client_main; +dhcp6_pd_client_public_main_t dhcp6_pd_client_public_main; + +static void +signal_report (prefix_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; + prefix_report_t *q = + vlib_process_signal_event_data (vm, ni, et, 1, sizeof *q); + + *q = *r; +} + +int +dhcp6_pd_publish_report (prefix_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; +} + +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 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_client_common_main_t *ccm = &dhcp6_client_common_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 = + &ccm->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, static) = { + .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); + } +} + +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 = (u32) clib_cpu_time_now (); + + 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/plugins/dhcp/dhcp6_pd_client_dp.h b/src/plugins/dhcp/dhcp6_pd_client_dp.h new file mode 100644 index 00000000000..561ca8a5a5b --- /dev/null +++ b/src/plugins/dhcp/dhcp6_pd_client_dp.h @@ -0,0 +1,172 @@ +/* + * 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 <vlib/vlib.h> +#include <dhcp/dhcp6_client_common_dp.h> + +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; + +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 +{ + dhcp6_pd_client_state_t *client_state_by_sw_if_index; + + uword publisher_node; + uword publisher_et; + + u32 seed; + + /* convenience */ + vlib_main_t *vlib_main; + vnet_main_t *vnet_main; +} dhcp6_pd_client_main_t; + +extern dhcp6_pd_client_main_t dhcp6_pd_client_main; + +typedef struct +{ + ip6_address_t prefix; + u8 prefix_length; + u32 valid_time; + u32 preferred_time; + u16 status_code; +} dhcp6_prefix_info_t; + +typedef struct +{ + dhcp6_report_common_t body; + u32 n_prefixes; + dhcp6_prefix_info_t *prefixes; +} prefix_report_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); +int dhcp6_pd_publish_report (prefix_report_t * r); +int dhcp6_pd_client_enable_disable (u32 sw_if_index, + const u8 * prefix_group, u8 enable); +int dhcp6_cp_ip6_address_add_del (u32 sw_if_index, const u8 * prefix_group, + ip6_address_t address, u8 prefix_length, + u8 is_add); + +extern vlib_node_registration_t dhcp6_pd_reply_process_node; + +enum +{ DHCP6_PD_DP_REPLY_REPORT, DHCP6_PD_DP_REPORT_MAX }; + +#include <dhcp/dhcp.api_types.h> + +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/plugins/dhcp/dhcp6_pd_doc.md b/src/plugins/dhcp/dhcp6_pd_doc.md new file mode 100644 index 00000000000..0d0e0865f1b --- /dev/null +++ b/src/plugins/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 <interface> [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 <interface> [prefix group <n>] <address> [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/plugins/dhcp/dhcp6_proxy_error.def b/src/plugins/dhcp/dhcp6_proxy_error.def new file mode 100644 index 00000000000..55fa731766c --- /dev/null +++ b/src/plugins/dhcp/dhcp6_proxy_error.def @@ -0,0 +1,29 @@ +/* + * dhcp_proxy_error.def: dhcp proxy errors + * + * Copyright (c) 2013 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. + */ + +dhcpv6_proxy_error (NONE, "no error") +dhcpv6_proxy_error (NO_SERVER, "no dhcpv6 server configured") +dhcpv6_proxy_error (RELAY_TO_SERVER, "DHCPV6 packets relayed to the server") +dhcpv6_proxy_error (RELAY_TO_CLIENT, "DHCPV6 packets relayed to clients") +dhcpv6_proxy_error (NO_INTERFACE_ADDRESS, "DHCPV6 no interface address") +dhcpv6_proxy_error (WRONG_MESSAGE_TYPE, "DHCPV6 wrong message type.") +dhcpv6_proxy_error (NO_SRC_ADDRESS, "DHCPV6 no srouce IPv6 address configured.") +dhcpv6_proxy_error (NO_CIRCUIT_ID_OPTION, "DHCPv6 reply packets without circuit ID option") +dhcpv6_proxy_error (NO_RELAY_MESSAGE_OPTION, "DHCPv6 reply packets without relay message option") +dhcpv6_proxy_error (BAD_SVR_FIB_OR_ADDRESS, "DHCPv6 packets not from DHCPv6 server or server FIB.") +dhcpv6_proxy_error (PKT_TOO_BIG, "DHCPv6 packets which are too big.") +dhcpv6_proxy_error (WRONG_INTERFACE_ID_OPTION, "DHCPv6 reply to invalid interface.") diff --git a/src/plugins/dhcp/dhcp6_proxy_node.c b/src/plugins/dhcp/dhcp6_proxy_node.c new file mode 100644 index 00000000000..929b49e2b41 --- /dev/null +++ b/src/plugins/dhcp/dhcp6_proxy_node.c @@ -0,0 +1,1186 @@ +/* + * dhcp6_proxy_node.c: dhcpv6 proxy node processing + * + * Copyright (c) 2013 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 <vlib/vlib.h> +#include <vnet/pg/pg.h> +#include <dhcp/dhcp_proxy.h> +#include <dhcp/dhcp6_packet.h> +#include <vnet/mfib/mfib_table.h> +#include <vnet/mfib/ip6_mfib.h> +#include <vnet/fib/fib.h> + +static char *dhcpv6_proxy_error_strings[] = { +#define dhcpv6_proxy_error(n,s) s, +#include <dhcp/dhcp6_proxy_error.def> +#undef dhcpv6_proxy_error +}; + +#define foreach_dhcpv6_proxy_to_server_input_next \ + _ (DROP, "error-drop") \ + _ (LOOKUP, "ip6-lookup") \ + _ (SEND_TO_CLIENT, "dhcpv6-proxy-to-client") + + +typedef enum +{ +#define _(s,n) DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_##s, + foreach_dhcpv6_proxy_to_server_input_next +#undef _ + DHCPV6_PROXY_TO_SERVER_INPUT_N_NEXT, +} dhcpv6_proxy_to_server_input_next_t; + +typedef struct +{ + /* 0 => to server, 1 => to client */ + int which; + u8 packet_data[64]; + u32 error; + u32 sw_if_index; + u32 original_sw_if_index; +} dhcpv6_proxy_trace_t; + +static vlib_node_registration_t dhcpv6_proxy_to_server_node; +static vlib_node_registration_t dhcpv6_proxy_to_client_node; + +/* all DHCP servers address */ +static ip6_address_t all_dhcpv6_server_address; +static ip6_address_t all_dhcpv6_server_relay_agent_address; + +static u8 * +format_dhcpv6_proxy_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_proxy_trace_t *t = va_arg (*args, dhcpv6_proxy_trace_t *); + + if (t->which == 0) + s = format (s, "DHCPV6 proxy: sent to server %U", + format_ip6_address, &t->packet_data, sizeof (ip6_address_t)); + else + s = format (s, "DHCPV6 proxy: sent to client from %U", + format_ip6_address, &t->packet_data, sizeof (ip6_address_t)); + if (t->error != (u32) ~ 0) + s = format (s, " error: %s\n", dhcpv6_proxy_error_strings[t->error]); + + s = format (s, " original_sw_if_index: %d, sw_if_index: %d\n", + t->original_sw_if_index, t->sw_if_index); + + return s; +} + +static u8 * +format_dhcpv6_proxy_header_with_length (u8 * s, va_list * args) +{ + dhcpv6_header_t *h = va_arg (*args, dhcpv6_header_t *); + u32 max_header_bytes = va_arg (*args, u32); + u32 header_bytes; + + header_bytes = sizeof (h[0]); + if (max_header_bytes != 0 && header_bytes > max_header_bytes) + return format (s, "dhcpv6 header truncated"); + + s = format (s, "DHCPV6 Proxy"); + + return s; +} + +/* get first interface address */ +static ip6_address_t * +ip6_interface_first_global_or_site_address (ip6_main_t * im, u32 sw_if_index) +{ + ip_lookup_main_t *lm = &im->lookup_main; + ip_interface_address_t *ia = 0; + ip6_address_t *result = 0; + + /* *INDENT-OFF* */ + foreach_ip_interface_address (lm, ia, sw_if_index, + 1 /* honor unnumbered */, + ({ + ip6_address_t * a = ip_interface_address_get_address (lm, ia); + if ((a->as_u8[0] & 0xe0) == 0x20 || + (a->as_u8[0] & 0xfe) == 0xfc) { + result = a; + break; + } + })); + /* *INDENT-ON* */ + return result; +} + +static inline void +copy_ip6_address (ip6_address_t * dst, ip6_address_t * src) +{ + dst->as_u64[0] = src->as_u64[0]; + dst->as_u64[1] = src->as_u64[1]; +} + +static uword +dhcpv6_proxy_to_server_input (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + u32 n_left_from, next_index, *from, *to_next; + dhcp_proxy_main_t *dpm = &dhcp_proxy_main; + from = vlib_frame_vector_args (from_frame); + n_left_from = from_frame->n_vectors; + u32 pkts_to_server = 0, pkts_to_client = 0, pkts_no_server = 0; + u32 pkts_no_interface_address = 0, pkts_no_exceeding_max_hop = 0; + u32 pkts_no_src_address = 0; + u32 pkts_wrong_msg_type = 0; + u32 pkts_too_big = 0; + ip6_main_t *im = &ip6_main; + ip6_address_t *src; + int bogus_length; + dhcp_proxy_t *proxy; + dhcp_server_t *server; + u32 rx_fib_idx = 0, server_fib_idx = 0; + + next_index = node->cached_next_index; + + 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) + { + vnet_main_t *vnm = vnet_get_main (); + u32 sw_if_index = 0; + u32 rx_sw_if_index = 0; + vnet_sw_interface_t *swif; + u32 bi0; + vlib_buffer_t *b0; + udp_header_t *u0, *u1; + dhcpv6_header_t *h0; // client msg hdr + ip6_header_t *ip0, *ip1; + ip6_address_t _ia0, *ia0 = &_ia0; + u32 next0; + u32 error0 = (u32) ~ 0; + dhcpv6_option_t *fwd_opt; + dhcpv6_relay_hdr_t *r1; + u16 len; + dhcpv6_int_id_t *id1; + dhcpv6_vss_t *vss1; + dhcpv6_client_mac_t *cmac; // client mac + ethernet_header_t *e_h0; + u8 client_src_mac[6]; + dhcp_vss_t *vss; + u8 is_solicit = 0; + + bi0 = from[0]; + from += 1; + n_left_from -= 1; + + b0 = vlib_get_buffer (vm, bi0); + + h0 = vlib_buffer_get_current (b0); + + /* + * udp_local hands us the DHCPV6 header. + */ + u0 = (void *) h0 - (sizeof (*u0)); + ip0 = (void *) u0 - (sizeof (*ip0)); + e_h0 = (void *) ip0 - ethernet_buffer_header_size (b0); + + clib_memcpy (client_src_mac, e_h0->src_address, 6); + + switch (h0->msg_type) + { + case DHCPV6_MSG_SOLICIT: + case DHCPV6_MSG_REQUEST: + case DHCPV6_MSG_CONFIRM: + case DHCPV6_MSG_RENEW: + case DHCPV6_MSG_REBIND: + case DHCPV6_MSG_RELEASE: + case DHCPV6_MSG_DECLINE: + case DHCPV6_MSG_INFORMATION_REQUEST: + case DHCPV6_MSG_RELAY_FORW: + /* send to server */ + break; + case DHCPV6_MSG_RELAY_REPL: + /* send to client */ + next0 = DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_SEND_TO_CLIENT; + error0 = 0; + pkts_to_client++; + goto do_enqueue; + default: + /* drop the packet */ + pkts_wrong_msg_type++; + error0 = DHCPV6_PROXY_ERROR_WRONG_MESSAGE_TYPE; + next0 = DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_DROP; + goto do_trace; + + } + + /* Send to DHCPV6 server via the configured FIB */ + rx_sw_if_index = sw_if_index = + vnet_buffer (b0)->sw_if_index[VLIB_RX]; + rx_fib_idx = im->mfib_index_by_sw_if_index[rx_sw_if_index]; + proxy = dhcp_get_proxy (dpm, rx_fib_idx, FIB_PROTOCOL_IP6); + + if (PREDICT_FALSE (NULL == proxy)) + { + error0 = DHCPV6_PROXY_ERROR_NO_SERVER; + next0 = DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_DROP; + pkts_no_server++; + goto do_trace; + } + + server = &proxy->dhcp_servers[0]; + server_fib_idx = server->server_fib_index; + vnet_buffer (b0)->sw_if_index[VLIB_TX] = server_fib_idx; + + + /* relay-option header pointer */ + vlib_buffer_advance (b0, -(sizeof (*fwd_opt))); + fwd_opt = vlib_buffer_get_current (b0); + /* relay message header pointer */ + vlib_buffer_advance (b0, -(sizeof (*r1))); + r1 = vlib_buffer_get_current (b0); + + vlib_buffer_advance (b0, -(sizeof (*u1))); + u1 = vlib_buffer_get_current (b0); + + vlib_buffer_advance (b0, -(sizeof (*ip1))); + ip1 = vlib_buffer_get_current (b0); + + /* fill in all that rubbish... */ + len = clib_net_to_host_u16 (u0->length) - sizeof (udp_header_t); + copy_ip6_address (&r1->peer_addr, &ip0->src_address); + + r1->msg_type = DHCPV6_MSG_RELAY_FORW; + fwd_opt->length = clib_host_to_net_u16 (len); + fwd_opt->option = clib_host_to_net_u16 (DHCPV6_OPTION_RELAY_MSG); + + r1->hop_count++; + r1->hop_count = + (h0->msg_type != DHCPV6_MSG_RELAY_FORW) ? 0 : r1->hop_count; + + if (PREDICT_FALSE (r1->hop_count >= HOP_COUNT_LIMIT)) + { + error0 = DHCPV6_RELAY_PKT_DROP_MAX_HOPS; + next0 = DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_DROP; + pkts_no_exceeding_max_hop++; + goto do_trace; + } + + + /* If relay-fwd and src address is site or global unicast address */ + if (h0->msg_type == DHCPV6_MSG_RELAY_FORW && + ((ip0->src_address.as_u8[0] & 0xe0) == 0x20 || + (ip0->src_address.as_u8[0] & 0xfe) == 0xfc)) + { + /* Set link address to zero */ + r1->link_addr.as_u64[0] = 0; + r1->link_addr.as_u64[1] = 0; + goto link_address_set; + } + + /* if receiving interface is unnumbered, use receiving interface + * IP address as link address, otherwise use the loopback interface + * IP address as link address. + */ + + swif = vnet_get_sw_interface (vnm, rx_sw_if_index); + if (swif->flags & VNET_SW_INTERFACE_FLAG_UNNUMBERED) + sw_if_index = swif->unnumbered_sw_if_index; + + ia0 = + ip6_interface_first_global_or_site_address (&ip6_main, + sw_if_index); + if (ia0 == 0) + { + error0 = DHCPV6_PROXY_ERROR_NO_INTERFACE_ADDRESS; + next0 = DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_DROP; + pkts_no_interface_address++; + goto do_trace; + } + + copy_ip6_address (&r1->link_addr, ia0); + + link_address_set: + + if ((b0->current_length + sizeof (*id1) + sizeof (*vss1) + + sizeof (*cmac)) > vlib_buffer_get_default_data_size (vm)) + { + error0 = DHCPV6_PROXY_ERROR_PKT_TOO_BIG; + next0 = DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_DROP; + pkts_too_big++; + goto do_trace; + } + + id1 = (dhcpv6_int_id_t *) (((uword) ip1) + b0->current_length); + b0->current_length += (sizeof (*id1)); + + id1->opt.option = clib_host_to_net_u16 (DHCPV6_OPTION_INTERFACE_ID); + id1->opt.length = clib_host_to_net_u16 (sizeof (rx_sw_if_index)); + id1->int_idx = clib_host_to_net_u32 (rx_sw_if_index); + + u1->length = 0; + if (h0->msg_type != DHCPV6_MSG_RELAY_FORW) + { + cmac = + (dhcpv6_client_mac_t *) (((uword) ip1) + b0->current_length); + b0->current_length += (sizeof (*cmac)); + cmac->opt.length = clib_host_to_net_u16 (sizeof (*cmac) - + sizeof (cmac->opt)); + cmac->opt.option = + clib_host_to_net_u16 + (DHCPV6_OPTION_CLIENT_LINK_LAYER_ADDRESS); + cmac->link_type = clib_host_to_net_u16 (1); /* ethernet */ + clib_memcpy (cmac->data, client_src_mac, 6); + u1->length += sizeof (*cmac); + } + + vss = dhcp_get_vss_info (dpm, rx_fib_idx, FIB_PROTOCOL_IP6); + + if (vss) + { + u16 id_len; /* length of VPN ID */ + u16 type_len = sizeof (vss1->vss_type); + + vss1 = (dhcpv6_vss_t *) (((uword) ip1) + b0->current_length); + vss1->vss_type = vss->vss_type; + if (vss->vss_type == VSS_TYPE_VPN_ID) + { + id_len = sizeof (vss->vpn_id); /* vpn_id is 7 bytes */ + memcpy (vss1->data, vss->vpn_id, id_len); + } + else if (vss->vss_type == VSS_TYPE_ASCII) + { + id_len = vec_len (vss->vpn_ascii_id); + memcpy (vss1->data, vss->vpn_ascii_id, id_len); + } + else /* must be VSS_TYPE_DEFAULT, no VPN ID */ + id_len = 0; + + vss1->opt.option = clib_host_to_net_u16 (DHCPV6_OPTION_VSS); + vss1->opt.length = clib_host_to_net_u16 (type_len + id_len); + u1->length += type_len + id_len + sizeof (vss1->opt); + b0->current_length += type_len + id_len + sizeof (vss1->opt); + } + + pkts_to_server++; + u1->checksum = 0; + u1->src_port = clib_host_to_net_u16 (UDP_DST_PORT_dhcpv6_to_client); + u1->dst_port = clib_host_to_net_u16 (UDP_DST_PORT_dhcpv6_to_server); + + u1->length = + clib_host_to_net_u16 (clib_net_to_host_u16 (fwd_opt->length) + + sizeof (*r1) + sizeof (*fwd_opt) + + sizeof (*u1) + sizeof (*id1) + u1->length); + + clib_memset (ip1, 0, sizeof (*ip1)); + ip1->ip_version_traffic_class_and_flow_label = 0x60; + ip1->payload_length = u1->length; + ip1->protocol = PROTO_UDP; + ip1->hop_limit = HOP_COUNT_LIMIT; + src = ((server->dhcp_server.ip6.as_u64[0] || + server->dhcp_server.ip6.as_u64[1]) ? + &server->dhcp_server.ip6 : &all_dhcpv6_server_address); + copy_ip6_address (&ip1->dst_address, src); + + + ia0 = ip6_interface_first_global_or_site_address + (&ip6_main, vnet_buffer (b0)->sw_if_index[VLIB_RX]); + + src = (proxy->dhcp_src_address.ip6.as_u64[0] || + proxy->dhcp_src_address.ip6.as_u64[1]) ? + &proxy->dhcp_src_address.ip6 : ia0; + if (ia0 == 0) + { + error0 = DHCPV6_PROXY_ERROR_NO_SRC_ADDRESS; + next0 = DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_DROP; + pkts_no_src_address++; + goto do_trace; + } + + copy_ip6_address (&ip1->src_address, src); + + + u1->checksum = ip6_tcp_udp_icmp_compute_checksum (vm, b0, ip1, + &bogus_length); + ASSERT (bogus_length == 0); + + next0 = DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_LOOKUP; + + is_solicit = (DHCPV6_MSG_SOLICIT == h0->msg_type); + + /* + * If we have multiple servers configured and this is the + * client's discover message, then send copies to each of + * those servers + */ + if (is_solicit && vec_len (proxy->dhcp_servers) > 1) + { + u32 ii; + + for (ii = 1; ii < vec_len (proxy->dhcp_servers); ii++) + { + vlib_buffer_t *c0; + u32 ci0; + + c0 = vlib_buffer_copy (vm, b0); + VLIB_BUFFER_TRACE_TRAJECTORY_INIT (c0); + ci0 = vlib_get_buffer_index (vm, c0); + server = &proxy->dhcp_servers[ii]; + + ip0 = vlib_buffer_get_current (c0); + + src = ((server->dhcp_server.ip6.as_u64[0] || + server->dhcp_server.ip6.as_u64[1]) ? + &server->dhcp_server.ip6 : + &all_dhcpv6_server_address); + copy_ip6_address (&ip1->dst_address, src); + + to_next[0] = ci0; + to_next += 1; + n_left_to_next -= 1; + + vlib_validate_buffer_enqueue_x1 (vm, node, next_index, + to_next, n_left_to_next, + ci0, next0); + + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcpv6_proxy_trace_t *tr; + + tr = vlib_add_trace (vm, node, c0, sizeof (*tr)); + tr->which = 0; /* to server */ + tr->error = error0; + tr->original_sw_if_index = rx_sw_if_index; + tr->sw_if_index = sw_if_index; + if (next0 == DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_LOOKUP) + copy_ip6_address ((ip6_address_t *) & + tr->packet_data[0], + &server->dhcp_server.ip6); + } + + if (PREDICT_FALSE (0 == n_left_to_next)) + { + vlib_put_next_frame (vm, node, next_index, + n_left_to_next); + vlib_get_next_frame (vm, node, next_index, + to_next, n_left_to_next); + } + } + } + + do_trace: + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcpv6_proxy_trace_t *tr = vlib_add_trace (vm, node, + b0, sizeof (*tr)); + tr->which = 0; /* to server */ + tr->error = error0; + tr->original_sw_if_index = rx_sw_if_index; + tr->sw_if_index = sw_if_index; + if (DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_LOOKUP == next0) + copy_ip6_address ((ip6_address_t *) & tr->packet_data[0], + &server->dhcp_server.ip6); + } + + do_enqueue: + to_next[0] = bi0; + to_next += 1; + n_left_to_next -= 1; + + 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); + } + + vlib_node_increment_counter (vm, dhcpv6_proxy_to_server_node.index, + DHCPV6_PROXY_ERROR_RELAY_TO_CLIENT, + pkts_to_client); + vlib_node_increment_counter (vm, dhcpv6_proxy_to_server_node.index, + DHCPV6_PROXY_ERROR_RELAY_TO_SERVER, + pkts_to_server); + vlib_node_increment_counter (vm, dhcpv6_proxy_to_server_node.index, + DHCPV6_PROXY_ERROR_NO_INTERFACE_ADDRESS, + pkts_no_interface_address); + vlib_node_increment_counter (vm, dhcpv6_proxy_to_server_node.index, + DHCPV6_PROXY_ERROR_WRONG_MESSAGE_TYPE, + pkts_wrong_msg_type); + vlib_node_increment_counter (vm, dhcpv6_proxy_to_server_node.index, + DHCPV6_PROXY_ERROR_NO_SRC_ADDRESS, + pkts_no_src_address); + vlib_node_increment_counter (vm, dhcpv6_proxy_to_server_node.index, + DHCPV6_PROXY_ERROR_PKT_TOO_BIG, pkts_too_big); + return from_frame->n_vectors; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dhcpv6_proxy_to_server_node, static) = { + .function = dhcpv6_proxy_to_server_input, + .name = "dhcpv6-proxy-to-server", + /* Takes a vector of packets. */ + .vector_size = sizeof (u32), + + .n_errors = DHCPV6_PROXY_N_ERROR, + .error_strings = dhcpv6_proxy_error_strings, + + .n_next_nodes = DHCPV6_PROXY_TO_SERVER_INPUT_N_NEXT, + .next_nodes = { +#define _(s,n) [DHCPV6_PROXY_TO_SERVER_INPUT_NEXT_##s] = n, + foreach_dhcpv6_proxy_to_server_input_next +#undef _ + }, + + .format_buffer = format_dhcpv6_proxy_header_with_length, + .format_trace = format_dhcpv6_proxy_trace, +#if 0 + .unformat_buffer = unformat_dhcpv6_proxy_header, +#endif +}; +/* *INDENT-ON* */ + +static uword +dhcpv6_proxy_to_client_input (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * from_frame) +{ + + u32 n_left_from, *from; + ethernet_main_t *em = vnet_get_ethernet_main (); + dhcp_proxy_main_t *dm = &dhcp_proxy_main; + dhcp_proxy_t *proxy; + dhcp_server_t *server; + vnet_main_t *vnm = vnet_get_main (); + int bogus_length; + + from = vlib_frame_vector_args (from_frame); + n_left_from = from_frame->n_vectors; + + while (n_left_from > 0) + { + u32 bi0; + vlib_buffer_t *b0; + udp_header_t *u0, *u1 = 0; + dhcpv6_relay_hdr_t *h0; + ip6_header_t *ip1 = 0, *ip0; + ip6_address_t _ia0, *ia0 = &_ia0; + ip6_address_t client_address; + ethernet_interface_t *ei0; + ethernet_header_t *mac0; + vnet_hw_interface_t *hi0; + vlib_frame_t *f0; + u32 *to_next0; + u32 sw_if_index = ~0; + u32 original_sw_if_index = ~0; + vnet_sw_interface_t *si0; + u32 error0 = (u32) ~ 0; + vnet_sw_interface_t *swif; + dhcpv6_option_t *r0 = 0, *o; + u16 len = 0; + u8 interface_opt_flag = 0; + u8 relay_msg_opt_flag = 0; + ip6_main_t *im = &ip6_main; + u32 server_fib_idx, client_fib_idx; + + bi0 = from[0]; + from += 1; + n_left_from -= 1; + + b0 = vlib_get_buffer (vm, bi0); + h0 = vlib_buffer_get_current (b0); + + if (DHCPV6_MSG_RELAY_REPL != h0->msg_type) + { + error0 = DHCPV6_PROXY_ERROR_WRONG_MESSAGE_TYPE; + + drop_packet: + vlib_node_increment_counter (vm, dhcpv6_proxy_to_client_node.index, + error0, 1); + + f0 = vlib_get_frame_to_node (vm, dm->error_drop_node_index); + to_next0 = vlib_frame_vector_args (f0); + to_next0[0] = bi0; + f0->n_vectors = 1; + vlib_put_frame_to_node (vm, dm->error_drop_node_index, f0); + goto do_trace; + } + /* hop count seems not need to be checked */ + if (HOP_COUNT_LIMIT < h0->hop_count) + { + error0 = DHCPV6_RELAY_PKT_DROP_MAX_HOPS; + goto drop_packet; + } + u0 = (void *) h0 - (sizeof (*u0)); + ip0 = (void *) u0 - (sizeof (*ip0)); + + vlib_buffer_advance (b0, sizeof (*h0)); + o = vlib_buffer_get_current (b0); + + /* Parse through TLVs looking for option 18 (DHCPV6_OPTION_INTERFACE_ID) + _and_ option 9 (DHCPV6_OPTION_RELAY_MSG) option which must be there. + Currently assuming no other options need to be processed + The interface-ID is the FIB number we need + to track down the client-facing interface */ + + while ((u8 *) o < (b0->data + b0->current_data + b0->current_length)) + { + if (DHCPV6_OPTION_INTERFACE_ID == clib_net_to_host_u16 (o->option)) + { + interface_opt_flag = 1; + if (clib_net_to_host_u16 (o->length) == sizeof (sw_if_index)) + sw_if_index = + clib_net_to_host_u32 (((dhcpv6_int_id_t *) o)->int_idx); + if (sw_if_index >= vec_len (im->fib_index_by_sw_if_index)) + { + error0 = DHCPV6_PROXY_ERROR_WRONG_INTERFACE_ID_OPTION; + goto drop_packet; + } + } + if (DHCPV6_OPTION_RELAY_MSG == clib_net_to_host_u16 (o->option)) + { + relay_msg_opt_flag = 1; + r0 = vlib_buffer_get_current (b0); + } + if ((relay_msg_opt_flag == 1) && (interface_opt_flag == 1)) + break; + vlib_buffer_advance (b0, + sizeof (*o) + + clib_net_to_host_u16 (o->length)); + o = + (dhcpv6_option_t *) (((uword) o) + + clib_net_to_host_u16 (o->length) + + sizeof (*o)); + } + + if ((relay_msg_opt_flag == 0) || (r0 == 0)) + { + error0 = DHCPV6_PROXY_ERROR_NO_RELAY_MESSAGE_OPTION; + goto drop_packet; + } + + if ((u32) ~ 0 == sw_if_index) + { + error0 = DHCPV6_PROXY_ERROR_NO_CIRCUIT_ID_OPTION; + goto drop_packet; + } + + //Advance buffer to start of encapsulated DHCPv6 message + vlib_buffer_advance (b0, sizeof (*r0)); + + client_fib_idx = im->mfib_index_by_sw_if_index[sw_if_index]; + proxy = dhcp_get_proxy (dm, client_fib_idx, FIB_PROTOCOL_IP6); + + if (NULL == proxy) + { + error0 = DHCPV6_PROXY_ERROR_NO_SERVER; + goto drop_packet; + } + + server_fib_idx = im->fib_index_by_sw_if_index + [vnet_buffer (b0)->sw_if_index[VLIB_RX]]; + + vec_foreach (server, proxy->dhcp_servers) + { + if (server_fib_idx == server->server_fib_index && + ip0->src_address.as_u64[0] == server->dhcp_server.ip6.as_u64[0] && + ip0->src_address.as_u64[1] == server->dhcp_server.ip6.as_u64[1]) + { + goto server_found; + } + } + + //drop packet if not from server with configured address or FIB + error0 = DHCPV6_PROXY_ERROR_BAD_SVR_FIB_OR_ADDRESS; + goto drop_packet; + + server_found: + vnet_buffer (b0)->sw_if_index[VLIB_TX] = original_sw_if_index + = sw_if_index; + + swif = vnet_get_sw_interface (vnm, original_sw_if_index); + if (swif->flags & VNET_SW_INTERFACE_FLAG_UNNUMBERED) + sw_if_index = swif->unnumbered_sw_if_index; + + + /* + * udp_local hands us the DHCPV6 header, need udp hdr, + * ip hdr to relay to client + */ + vlib_buffer_advance (b0, -(sizeof (*u1))); + u1 = vlib_buffer_get_current (b0); + + vlib_buffer_advance (b0, -(sizeof (*ip1))); + ip1 = vlib_buffer_get_current (b0); + + copy_ip6_address (&client_address, &h0->peer_addr); + + ia0 = ip6_interface_first_address (&ip6_main, sw_if_index); + if (ia0 == 0) + { + error0 = DHCPV6_PROXY_ERROR_NO_INTERFACE_ADDRESS; + goto drop_packet; + } + + len = clib_net_to_host_u16 (r0->length); + clib_memset (ip1, 0, sizeof (*ip1)); + copy_ip6_address (&ip1->dst_address, &client_address); + u1->checksum = 0; + u1->src_port = clib_net_to_host_u16 (UDP_DST_PORT_dhcpv6_to_server); + u1->dst_port = clib_net_to_host_u16 (UDP_DST_PORT_dhcpv6_to_client); + u1->length = clib_host_to_net_u16 (len + sizeof (udp_header_t)); + + ip1->ip_version_traffic_class_and_flow_label = + ip0->ip_version_traffic_class_and_flow_label & 0x00000fff; + ip1->payload_length = u1->length; + ip1->protocol = PROTO_UDP; + ip1->hop_limit = HOP_COUNT_LIMIT; + copy_ip6_address (&ip1->src_address, ia0); + + u1->checksum = ip6_tcp_udp_icmp_compute_checksum (vm, b0, ip1, + &bogus_length); + ASSERT (bogus_length == 0); + + vlib_buffer_advance (b0, -(sizeof (ethernet_header_t))); + si0 = vnet_get_sw_interface (vnm, original_sw_if_index); + if (si0->type == VNET_SW_INTERFACE_TYPE_SUB) + vlib_buffer_advance (b0, -4 /* space for VLAN tag */ ); + + mac0 = vlib_buffer_get_current (b0); + + hi0 = vnet_get_sup_hw_interface (vnm, original_sw_if_index); + ei0 = pool_elt_at_index (em->interfaces, hi0->hw_instance); + clib_memcpy (mac0->src_address, ei0->address, sizeof (ei0->address)); + clib_memset (&mac0->dst_address, 0xff, sizeof (mac0->dst_address)); + mac0->type = (si0->type == VNET_SW_INTERFACE_TYPE_SUB) ? + clib_net_to_host_u16 (0x8100) : clib_net_to_host_u16 (0x86dd); + + if (si0->type == VNET_SW_INTERFACE_TYPE_SUB) + { + u32 *vlan_tag = (u32 *) (mac0 + 1); + u32 tmp; + tmp = (si0->sub.id << 16) | 0x0800; + *vlan_tag = clib_host_to_net_u32 (tmp); + } + + /* $$$ consider adding a dynamic next to the graph node, for performance */ + f0 = vlib_get_frame_to_node (vm, hi0->output_node_index); + to_next0 = vlib_frame_vector_args (f0); + to_next0[0] = bi0; + f0->n_vectors = 1; + vlib_put_frame_to_node (vm, hi0->output_node_index, f0); + + do_trace: + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcpv6_proxy_trace_t *tr = vlib_add_trace (vm, node, + b0, sizeof (*tr)); + tr->which = 1; /* to client */ + if (ia0) + copy_ip6_address ((ip6_address_t *) tr->packet_data, ia0); + tr->error = error0; + tr->original_sw_if_index = original_sw_if_index; + tr->sw_if_index = sw_if_index; + } + } + return from_frame->n_vectors; + +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dhcpv6_proxy_to_client_node, static) = { + .function = dhcpv6_proxy_to_client_input, + .name = "dhcpv6-proxy-to-client", + /* Takes a vector of packets. */ + .vector_size = sizeof (u32), + + .n_errors = DHCPV6_PROXY_N_ERROR, + .error_strings = dhcpv6_proxy_error_strings, + .format_buffer = format_dhcpv6_proxy_header_with_length, + .format_trace = format_dhcpv6_proxy_trace, +#if 0 + .unformat_buffer = unformat_dhcpv6_proxy_header, +#endif +}; +/* *INDENT-ON* */ + +static clib_error_t * +dhcp6_proxy_init (vlib_main_t * vm) +{ + dhcp_proxy_main_t *dm = &dhcp_proxy_main; + vlib_node_t *error_drop_node; + + error_drop_node = vlib_get_node_by_name (vm, (u8 *) "error-drop"); + dm->error_drop_node_index = error_drop_node->index; + + /* RFC says this is the dhcpv6 server address */ + all_dhcpv6_server_address.as_u64[0] = + clib_host_to_net_u64 (0xFF05000000000000); + all_dhcpv6_server_address.as_u64[1] = clib_host_to_net_u64 (0x00010003); + + /* RFC says this is the server and agent address */ + all_dhcpv6_server_relay_agent_address.as_u64[0] = + clib_host_to_net_u64 (0xFF02000000000000); + all_dhcpv6_server_relay_agent_address.as_u64[1] = + clib_host_to_net_u64 (0x00010002); + + return 0; +} + +VLIB_INIT_FUNCTION (dhcp6_proxy_init); + +int +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; + + const mfib_prefix_t all_dhcp_servers = { + .fp_len = 128, + .fp_proto = FIB_PROTOCOL_IP6, + .fp_grp_addr = { + .ip6 = all_dhcpv6_server_relay_agent_address, + } + }; + + if (ip46_address_is_zero (addr)) + return VNET_API_ERROR_INVALID_DST_ADDRESS; + + if (ip46_address_is_zero (src_addr)) + return VNET_API_ERROR_INVALID_SRC_ADDRESS; + + rx_fib_index = mfib_table_find_or_create_and_lock (FIB_PROTOCOL_IP6, + rx_table_id, + MFIB_SOURCE_DHCP); + + if (is_del) + { + if (dhcp_proxy_server_del (FIB_PROTOCOL_IP6, rx_fib_index, + addr, server_table_id)) + { + mfib_table_entry_delete (rx_fib_index, + &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 + { + const fib_route_path_t path_for_us = { + .frp_proto = DPO_PROTO_IP6, + .frp_addr = zero_addr, + .frp_sw_if_index = 0xffffffff, + .frp_fib_index = ~0, + .frp_weight = 1, + .frp_flags = FIB_ROUTE_PATH_LOCAL, + .frp_mitf_flags = MFIB_ITF_FLAG_FORWARD, + }; + if (dhcp_proxy_server_add (FIB_PROTOCOL_IP6, addr, src_addr, + rx_fib_index, server_table_id)) + { + mfib_table_entry_path_update (rx_fib_index, + &all_dhcp_servers, + MFIB_SOURCE_DHCP, &path_for_us); + /* + * Each interface that is enabled in this table, needs to be added + * as an accepting interface, but this is not easily doable in VPP. + * So we cheat. Add a flag to the entry that indicates accept form + * any interface. + * We will still only accept on v6 enabled interfaces, since the + * input feature ensures this. + */ + mfib_table_entry_update (rx_fib_index, + &all_dhcp_servers, + MFIB_SOURCE_DHCP, + 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 */ ); + } + } + + mfib_table_unlock (rx_fib_index, FIB_PROTOCOL_IP6, MFIB_SOURCE_DHCP); + + return (rc); +} + +static clib_error_t * +dhcpv6_proxy_set_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + ip46_address_t addr, src_addr; + int set_server = 0, set_src_address = 0; + u32 rx_table_id = 0, server_table_id = 0; + int is_del = 0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "server %U", unformat_ip6_address, &addr.ip6)) + set_server = 1; + else if (unformat (input, "src-address %U", + unformat_ip6_address, &src_addr.ip6)) + set_src_address = 1; + else if (unformat (input, "server-fib-id %d", &server_table_id)) + ; + else if (unformat (input, "rx-fib-id %d", &rx_table_id)) + ; + else if (unformat (input, "delete") || unformat (input, "del")) + is_del = 1; + else + break; + } + + if (is_del || (set_server && set_src_address)) + { + int rv; + + rv = dhcp6_proxy_set_server (&addr, &src_addr, rx_table_id, + server_table_id, is_del); + + //TODO: Complete the errors + switch (rv) + { + case 0: + return 0; + + case VNET_API_ERROR_INVALID_DST_ADDRESS: + return clib_error_return (0, "Invalid server address"); + + case VNET_API_ERROR_INVALID_SRC_ADDRESS: + return clib_error_return (0, "Invalid src address"); + + case VNET_API_ERROR_NO_SUCH_ENTRY: + return clib_error_return + (0, "Fib id %d: no per-fib DHCP server configured", rx_table_id); + + default: + return clib_error_return (0, "BUG: rv %d", rv); + } + } + else + return clib_error_return (0, "parse error`%U'", + format_unformat_error, input); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcpv6_proxy_set_command, static) = { + .path = "set dhcpv6 proxy", + .short_help = "set dhcpv6 proxy [del] server <ipv6-addr> src-address <ipv6-addr> " + "[server-fib-id <fib-id>] [rx-fib-id <fib-id>] ", + .function = dhcpv6_proxy_set_command_fn, +}; +/* *INDENT-ON* */ + +static u8 * +format_dhcp6_proxy_server (u8 * s, va_list * args) +{ + dhcp_proxy_t *proxy = va_arg (*args, dhcp_proxy_t *); + fib_table_t *server_fib; + dhcp_server_t *server; + ip6_mfib_t *rx_fib; + + if (proxy == 0) + { + s = format (s, "%=14s%=16s%s", "RX FIB", "Src Address", + "Servers FIB,Address"); + return s; + } + + rx_fib = ip6_mfib_get (proxy->rx_fib_index); + + s = format (s, "%=14u%=16U", + rx_fib->table_id, + format_ip46_address, &proxy->dhcp_src_address, IP46_TYPE_ANY); + + vec_foreach (server, proxy->dhcp_servers) + { + server_fib = fib_table_get (server->server_fib_index, FIB_PROTOCOL_IP6); + s = format (s, "%u,%U ", + server_fib->ft_table_id, + format_ip46_address, &server->dhcp_server, IP46_TYPE_ANY); + } + + return s; +} + +static int +dhcp6_proxy_show_walk (dhcp_proxy_t * proxy, void *ctx) +{ + vlib_main_t *vm = ctx; + + vlib_cli_output (vm, "%U", format_dhcp6_proxy_server, proxy); + + return (1); +} + +static clib_error_t * +dhcpv6_proxy_show_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + vlib_cli_output (vm, "%U", format_dhcp6_proxy_server, + NULL /* header line */ ); + + dhcp_proxy_walk (FIB_PROTOCOL_IP6, dhcp6_proxy_show_walk, vm); + + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcpv6_proxy_show_command, static) = { + .path = "show dhcpv6 proxy", + .short_help = "Display dhcpv6 proxy info", + .function = dhcpv6_proxy_show_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dhcpv6_vss_command_fn (vlib_main_t * vm, + unformat_input_t * input, vlib_cli_command_t * cmd) +{ + u8 is_del = 0, vss_type = VSS_TYPE_DEFAULT; + u8 *vpn_ascii_id = 0; + u32 oui = 0, fib_id = 0, tbl_id = ~0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + if (unformat (input, "table %d", &tbl_id)) + ; + else if (unformat (input, "oui %d", &oui)) + vss_type = VSS_TYPE_VPN_ID; + else if (unformat (input, "vpn-id %d", &fib_id)) + vss_type = VSS_TYPE_VPN_ID; + else if (unformat (input, "vpn-ascii-id %s", &vpn_ascii_id)) + vss_type = VSS_TYPE_ASCII; + else if (unformat (input, "delete") || unformat (input, "del")) + is_del = 1; + else + break; + } + + if (tbl_id == ~0) + return clib_error_return (0, "no table ID specified."); + + int rv = dhcp_proxy_set_vss (FIB_PROTOCOL_IP6, tbl_id, vss_type, + vpn_ascii_id, oui, fib_id, is_del); + switch (rv) + { + case 0: + return 0; + case VNET_API_ERROR_NO_SUCH_ENTRY: + return clib_error_return (0, "vss for table %d not found in pool.", + tbl_id); + default: + return clib_error_return (0, "BUG: rv %d", rv); + } +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcpv6_proxy_vss_command, static) = { + .path = "set dhcpv6 vss", + .short_help = "set dhcpv6 vss table <table-id> [oui <n> vpn-id <n> | vpn-ascii-id <text>]", + .function = dhcpv6_vss_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dhcpv6_vss_show_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + dhcp_vss_walk (FIB_PROTOCOL_IP6, dhcp_vss_show_walk, vm); + + return (NULL); +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcpv6_proxy_vss_show_command, static) = { + .path = "show dhcpv6 vss", + .short_help = "show dhcpv6 VSS", + .function = dhcpv6_vss_show_command_fn, +}; +/* *INDENT-ON* */ + +static clib_error_t * +dhcpv6_link_address_show_command_fn (vlib_main_t * vm, + unformat_input_t * input, + vlib_cli_command_t * cmd) +{ + vnet_main_t *vnm = vnet_get_main (); + u32 sw_if_index0 = 0, sw_if_index; + vnet_sw_interface_t *swif; + ip6_address_t *ia0; + + while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT) + { + + if (unformat (input, "%U", + unformat_vnet_sw_interface, vnm, &sw_if_index0)) + { + swif = vnet_get_sw_interface (vnm, sw_if_index0); + sw_if_index = (swif->flags & VNET_SW_INTERFACE_FLAG_UNNUMBERED) ? + swif->unnumbered_sw_if_index : sw_if_index0; + ia0 = ip6_interface_first_address (&ip6_main, sw_if_index); + if (ia0) + { + vlib_cli_output (vm, "%=20s%=48s", "interface", "link-address"); + + vlib_cli_output (vm, "%=20U%=48U", + format_vnet_sw_if_index_name, vnm, + sw_if_index0, format_ip6_address, ia0); + } + else + vlib_cli_output (vm, "%=34s%=20U", + "No IPv6 address configured on", + format_vnet_sw_if_index_name, vnm, sw_if_index); + } + else + break; + } + + return 0; +} + +/* *INDENT-OFF* */ +VLIB_CLI_COMMAND (dhcpv6_proxy_address_show_command, static) = { + .path = "show dhcpv6 link-address interface", + .short_help = "show dhcpv6 link-address interface <interface>", + .function = dhcpv6_link_address_show_command_fn, +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp_api.c b/src/plugins/dhcp/dhcp_api.c new file mode 100644 index 00000000000..61efaba09b0 --- /dev/null +++ b/src/plugins/dhcp/dhcp_api.c @@ -0,0 +1,871 @@ +/* + *------------------------------------------------------------------ + * dhcp_api.c - dhcp api + * + * Copyright (c) 2016 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 <vnet/vnet.h> +#include <vlibmemory/api.h> + +#include <vnet/interface.h> +#include <vnet/api_errno.h> +#include <dhcp/dhcp_proxy.h> +#include <dhcp/client.h> +#include <dhcp/dhcp6_pd_client_dp.h> +#include <dhcp/dhcp6_ia_na_client_dp.h> +#include <dhcp/dhcp6_client_common_dp.h> +#include <vnet/fib/fib_table.h> +#include <vnet/ip/ip_types_api.h> + +/* define message IDs */ +#include <vnet/format_fns.h> +#include <dhcp/dhcp.api_enum.h> +#include <dhcp/dhcp.api_types.h> + +/** + * Base message ID fot the plugin + */ +static u32 dhcp_base_msg_id; +#define REPLY_MSG_ID_BASE dhcp_base_msg_id + +#include <vlibapi/api_helper_macros.h> + +#define DHCP_PLUGIN_VERSION_MAJOR 1 +#define DHCP_PLUGIN_VERSION_MINOR 0 + +static void +vl_api_dhcp_plugin_get_version_t_handler (vl_api_dhcp_plugin_get_version_t * + mp) +{ + vl_api_dhcp_plugin_get_version_reply_t *rmp; + int msg_size = sizeof (*rmp); + vl_api_registration_t *reg; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (!reg) + return; + + rmp = vl_msg_api_alloc (msg_size); + clib_memset (rmp, 0, msg_size); + rmp->_vl_msg_id = + ntohs (VL_API_DHCP_PLUGIN_GET_VERSION_REPLY + REPLY_MSG_ID_BASE); + rmp->context = mp->context; + rmp->major = htonl (DHCP_PLUGIN_VERSION_MAJOR); + rmp->minor = htonl (DHCP_PLUGIN_VERSION_MINOR); + + vl_api_send_msg (reg, (u8 *) rmp); +} + +static void +vl_api_dhcp_plugin_control_ping_t_handler (vl_api_dhcp_plugin_control_ping_t * + mp) +{ + vl_api_dhcp_plugin_control_ping_reply_t *rmp; + int rv = 0; + + /* *INDENT-OFF* */ + REPLY_MACRO2 (VL_API_DHCP_PLUGIN_CONTROL_PING_REPLY, + ({ + rmp->vpe_pid = ntohl (getpid ()); + })); + /* *INDENT-ON* */ +} + +static void +vl_api_dhcp6_duid_ll_set_t_handler (vl_api_dhcp6_duid_ll_set_t * mp) +{ + vl_api_dhcp6_duid_ll_set_reply_t *rmp; + dhcpv6_duid_ll_string_t *duid; + int rv = 0; + + duid = (dhcpv6_duid_ll_string_t *) mp->duid_ll; + if (duid->duid_type != htonl (DHCPV6_DUID_LL)) + { + rv = VNET_API_ERROR_INVALID_VALUE; + goto reply; + } + clib_memcpy (&client_duid, &duid, sizeof (client_duid)); + +reply: + REPLY_MACRO (VL_API_DHCP6_DUID_LL_SET_REPLY); +} + +static void +vl_api_dhcp_proxy_set_vss_t_handler (vl_api_dhcp_proxy_set_vss_t * mp) +{ + vl_api_dhcp_proxy_set_vss_reply_t *rmp; + u8 *vpn_ascii_id; + int rv; + + mp->vpn_ascii_id[sizeof (mp->vpn_ascii_id) - 1] = 0; + vpn_ascii_id = format (0, "%s", mp->vpn_ascii_id); + rv = + dhcp_proxy_set_vss ((mp->is_ipv6 ? FIB_PROTOCOL_IP6 : FIB_PROTOCOL_IP4), + ntohl (mp->tbl_id), ntohl (mp->vss_type), + vpn_ascii_id, ntohl (mp->oui), ntohl (mp->vpn_index), + mp->is_add == 0); + + REPLY_MACRO (VL_API_DHCP_PROXY_SET_VSS_REPLY); +} + + +static void vl_api_dhcp_proxy_config_t_handler + (vl_api_dhcp_proxy_config_t * mp) +{ + vl_api_dhcp_proxy_set_vss_reply_t *rmp; + ip46_address_t src, server; + int rv = -1; + + if (mp->dhcp_src_address.af != mp->dhcp_server.af) + { + rv = VNET_API_ERROR_INVALID_ARGUMENT; + goto reply; + } + + ip_address_decode (&mp->dhcp_src_address, &src); + ip_address_decode (&mp->dhcp_server, &server); + + if (mp->dhcp_src_address.af == ADDRESS_IP4) + { + rv = dhcp4_proxy_set_server (&server, + &src, + (u32) ntohl (mp->rx_vrf_id), + (u32) ntohl (mp->server_vrf_id), + (int) (mp->is_add == 0)); + } + else + { + rv = dhcp6_proxy_set_server (&server, + &src, + (u32) ntohl (mp->rx_vrf_id), + (u32) ntohl (mp->server_vrf_id), + (int) (mp->is_add == 0)); + } + +reply: + REPLY_MACRO (VL_API_DHCP_PROXY_CONFIG_REPLY); +} + +static void +vl_api_dhcp_proxy_dump_t_handler (vl_api_dhcp_proxy_dump_t * mp) +{ + vl_api_registration_t *reg; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (!reg) + return;; + + dhcp_proxy_dump ((mp->is_ip6 == 1 ? + FIB_PROTOCOL_IP6 : FIB_PROTOCOL_IP4), reg, mp->context); +} + +void +dhcp_send_details (fib_protocol_t proto, + void *opaque, u32 context, dhcp_proxy_t * proxy) +{ + vl_api_dhcp_proxy_details_t *mp; + vl_api_registration_t *reg = opaque; + vl_api_dhcp_server_t *v_server; + dhcp_server_t *server; + fib_table_t *s_fib; + dhcp_vss_t *vss; + u32 count; + size_t n; + + count = vec_len (proxy->dhcp_servers); + n = sizeof (*mp) + (count * sizeof (vl_api_dhcp_server_t)); + mp = vl_msg_api_alloc (n); + if (!mp) + return; + clib_memset (mp, 0, n); + mp->_vl_msg_id = ntohs (VL_API_DHCP_PROXY_DETAILS + REPLY_MSG_ID_BASE); + mp->context = context; + mp->count = count; + + mp->is_ipv6 = (proto == FIB_PROTOCOL_IP6); + mp->rx_vrf_id = + htonl (dhcp_proxy_rx_table_get_table_id (proto, proxy->rx_fib_index)); + + vss = dhcp_get_vss_info (&dhcp_proxy_main, proxy->rx_fib_index, proto); + + if (vss) + { + mp->vss_type = ntohl (vss->vss_type); + if (vss->vss_type == VSS_TYPE_ASCII) + { + u32 id_len = vec_len (vss->vpn_ascii_id); + clib_memcpy (mp->vss_vpn_ascii_id, vss->vpn_ascii_id, id_len); + } + else if (vss->vss_type == VSS_TYPE_VPN_ID) + { + u32 oui = ((u32) vss->vpn_id[0] << 16) + ((u32) vss->vpn_id[1] << 8) + + ((u32) vss->vpn_id[2]); + u32 fib_id = ((u32) vss->vpn_id[3] << 24) + + ((u32) vss->vpn_id[4] << 16) + ((u32) vss->vpn_id[5] << 8) + + ((u32) vss->vpn_id[6]); + mp->vss_oui = htonl (oui); + mp->vss_fib_id = htonl (fib_id); + } + } + else + mp->vss_type = VSS_TYPE_INVALID; + + vec_foreach_index (count, proxy->dhcp_servers) + { + server = &proxy->dhcp_servers[count]; + v_server = &mp->servers[count]; + + s_fib = fib_table_get (server->server_fib_index, proto); + + v_server->server_vrf_id = htonl (s_fib->ft_table_id); + + if (mp->is_ipv6) + { + memcpy (&v_server->dhcp_server.un, &server->dhcp_server.ip6, 16); + } + else + { + /* put the address in the first bytes */ + memcpy (&v_server->dhcp_server.un, &server->dhcp_server.ip4, 4); + } + } + + if (mp->is_ipv6) + { + memcpy (&mp->dhcp_src_address.un, &proxy->dhcp_src_address.ip6, 16); + } + else + { + /* put the address in the first bytes */ + memcpy (&mp->dhcp_src_address.un, &proxy->dhcp_src_address.ip4, 4); + } + vl_api_send_msg (reg, (u8 *) mp); +} + +static void +dhcp_client_lease_encode (vl_api_dhcp_lease_t * lease, + const dhcp_client_t * client) +{ + size_t len; + u8 i; + + lease->is_ipv6 = 0; // only support IPv6 clients + lease->sw_if_index = ntohl (client->sw_if_index); + lease->state = ntohl (client->state); + len = clib_min (sizeof (lease->hostname) - 1, vec_len (client->hostname)); + clib_memcpy (&lease->hostname, client->hostname, len); + lease->hostname[len] = 0; + + lease->mask_width = client->subnet_mask_width; + clib_memcpy (&lease->host_address.un, (u8 *) & client->leased_address, + sizeof (ip4_address_t)); + clib_memcpy (&lease->router_address.un, (u8 *) & client->router_address, + sizeof (ip4_address_t)); + + lease->count = vec_len (client->domain_server_address); + for (i = 0; i < lease->count; i++) + clib_memcpy (&lease->domain_server[i].address, + (u8 *) & client->domain_server_address[i], + sizeof (ip4_address_t)); + + clib_memcpy (&lease->host_mac[0], client->client_hardware_address, 6); +} + +static void +dhcp_client_data_encode (vl_api_dhcp_client_t * vclient, + const dhcp_client_t * client) +{ + size_t len; + + vclient->sw_if_index = ntohl (client->sw_if_index); + len = clib_min (sizeof (vclient->hostname) - 1, vec_len (client->hostname)); + clib_memcpy (&vclient->hostname, client->hostname, len); + vclient->hostname[len] = 0; + + len = clib_min (sizeof (vclient->id) - 1, + vec_len (client->client_identifier)); + clib_memcpy (&vclient->id, client->client_identifier, len); + vclient->id[len] = 0; + + if (NULL != client->event_callback) + vclient->want_dhcp_event = 1; + else + vclient->want_dhcp_event = 0; + vclient->set_broadcast_flag = client->set_broadcast_flag; + vclient->dscp = ip_dscp_encode (client->dscp); + vclient->pid = client->pid; +} + +static void +dhcp_compl_event_callback (u32 client_index, const dhcp_client_t * client) +{ + vl_api_registration_t *reg; + vl_api_dhcp_compl_event_t *mp; + + reg = vl_api_client_index_to_registration (client_index); + if (!reg) + return; + + mp = vl_msg_api_alloc (sizeof (*mp)); + mp->client_index = client_index; + mp->pid = client->pid; + dhcp_client_lease_encode (&mp->lease, client); + + mp->_vl_msg_id = ntohs (VL_API_DHCP_COMPL_EVENT + REPLY_MSG_ID_BASE); + + vl_api_send_msg (reg, (u8 *) mp); +} + +static void vl_api_dhcp_client_config_t_handler + (vl_api_dhcp_client_config_t * mp) +{ + vlib_main_t *vm = vlib_get_main (); + vl_api_dhcp_client_config_reply_t *rmp; + u32 sw_if_index; + ip_dscp_t dscp; + int rv = 0; + + VALIDATE_SW_IF_INDEX (&(mp->client)); + + sw_if_index = ntohl (mp->client.sw_if_index); + dscp = ip_dscp_decode (mp->client.dscp); + + rv = dhcp_client_config (mp->is_add, + mp->client_index, + vm, + sw_if_index, + mp->client.hostname, + mp->client.id, + (mp->client.want_dhcp_event ? + dhcp_compl_event_callback : + NULL), + mp->client.set_broadcast_flag, + dscp, mp->client.pid); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_DHCP_CLIENT_CONFIG_REPLY); +} + +typedef struct dhcp_client_send_walk_ctx_t_ +{ + vl_api_registration_t *reg; + u32 context; +} dhcp_client_send_walk_ctx_t; + +static int +send_dhcp_client_entry (const dhcp_client_t * client, void *arg) +{ + dhcp_client_send_walk_ctx_t *ctx; + vl_api_dhcp_client_details_t *mp; + + ctx = arg; + + mp = vl_msg_api_alloc (sizeof (*mp)); + clib_memset (mp, 0, sizeof (*mp)); + + mp->_vl_msg_id = ntohs (VL_API_DHCP_CLIENT_DETAILS + REPLY_MSG_ID_BASE); + mp->context = ctx->context; + + dhcp_client_data_encode (&mp->client, client); + dhcp_client_lease_encode (&mp->lease, client); + + vl_api_send_msg (ctx->reg, (u8 *) mp); + + return (1); +} + +static void +vl_api_dhcp_client_dump_t_handler (vl_api_dhcp_client_dump_t * mp) +{ + vl_api_registration_t *reg; + + reg = vl_api_client_index_to_registration (mp->client_index); + if (!reg) + return; + + dhcp_client_send_walk_ctx_t ctx = { + .reg = reg, + .context = mp->context, + }; + dhcp_client_walk (send_dhcp_client_entry, &ctx); +} + +static 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_DHCP6_CLIENTS_ENABLE_DISABLE_REPLY); +} + +void + vl_api_want_dhcp6_reply_events_t_handler + (vl_api_want_dhcp6_reply_events_t * mp) +{ + vpe_api_main_t *am = &vpe_api_main; + vl_api_want_dhcp6_reply_events_reply_t *rmp; + int rv = 0; + + uword *p = + hash_get (am->dhcp6_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_reply_events_registrations, p[0]); + pool_put (am->dhcp6_reply_events_registrations, rp); + hash_unset (am->dhcp6_reply_events_registration_hash, + mp->client_index); + if (pool_elts (am->dhcp6_reply_events_registrations) == 0) + dhcp6_set_publisher_node (~0, DHCP6_DP_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_reply_events_registrations, rp); + rp->client_index = mp->client_index; + rp->client_pid = ntohl (mp->pid); + hash_set (am->dhcp6_reply_events_registration_hash, rp->client_index, + rp - am->dhcp6_reply_events_registrations); + dhcp6_set_publisher_node (dhcp6_reply_process_node.index, + DHCP6_DP_REPLY_REPORT); + +reply: + REPLY_MACRO (VL_API_WANT_DHCP6_REPLY_EVENTS_REPLY); +} + +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, DHCP6_PD_DP_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 + vl_api_dhcp6_send_client_message_t_handler + (vl_api_dhcp6_send_client_message_t * mp) +{ + vl_api_dhcp6_send_client_message_reply_t *rmp; + dhcp6_send_client_message_params_t params; + vlib_main_t *vm = vlib_get_main (); + u32 n_addresses; + u32 i; + int rv = 0; + + VALIDATE_SW_IF_INDEX (mp); + + BAD_SW_IF_INDEX_LABEL; + REPLY_MACRO (VL_API_DHCP6_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 = ntohl (mp->msg_type); + params.T1 = ntohl (mp->T1); + params.T2 = ntohl (mp->T2); + n_addresses = ntohl (mp->n_addresses); + params.addresses = 0; + if (n_addresses > 0) + vec_validate (params.addresses, n_addresses - 1); + for (i = 0; i < n_addresses; i++) + { + vl_api_dhcp6_address_info_t *ai = &mp->addresses[i]; + dhcp6_send_client_message_params_address_t *addr = ¶ms.addresses[i]; + addr->preferred_lt = ntohl (ai->preferred_time); + addr->valid_lt = ntohl (ai->valid_time); + ip6_address_decode (ai->address, &addr->address); + } + + dhcp6_send_client_message (vm, ntohl (mp->sw_if_index), mp->stop, ¶ms); +} + +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 = ntohl (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); + ip6_address_decode (pi->prefix.address, &pref->prefix); + pref->prefix_length = pi->prefix.len; + } + + dhcp6_pd_send_client_message (vm, ntohl (mp->sw_if_index), mp->stop, + ¶ms); +} + +static clib_error_t * +call_dhcp6_reply_event_callbacks (void *data, + _vnet_dhcp6_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_reply_event_function; + } + + return error; +} + +static uword +dhcp6_reply_process (vlib_main_t * vm, vlib_node_runtime_t * rt, + vlib_frame_t * f) +{ + /* These cross the longjmp boundary (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_DP_REPLY_REPORT; + void *event_data = vlib_process_get_event_data (vm, &event_type); + + int i; + if (event_type == DHCP6_DP_REPLY_REPORT) + { + address_report_t *events = event_data; + for (i = 0; i < vec_len (events); i++) + { + u32 event_size = + sizeof (vl_api_dhcp6_reply_event_t) + + vec_len (events[i].addresses) * + sizeof (vl_api_dhcp6_address_info_t); + vl_api_dhcp6_reply_event_t *event = clib_mem_alloc (event_size); + clib_memset (event, 0, event_size); + + event->sw_if_index = htonl (events[i].body.sw_if_index); + event->server_index = htonl (events[i].body.server_index); + event->msg_type = events[i].body.msg_type; + event->T1 = htonl (events[i].body.T1); + event->T2 = htonl (events[i].body.T2); + event->inner_status_code = + htons (events[i].body.inner_status_code); + event->status_code = htons (events[i].body.status_code); + event->preference = events[i].body.preference; + + event->n_addresses = htonl (vec_len (events[i].addresses)); + vl_api_dhcp6_address_info_t *address = + (typeof (address)) event->addresses; + u32 j; + for (j = 0; j < vec_len (events[i].addresses); j++) + { + dhcp6_address_info_t *info = &events[i].addresses[j]; + ip6_address_encode (&info->address, address->address); + address->valid_time = htonl (info->valid_time); + address->preferred_time = htonl (info->preferred_time); + address++; + } + vec_free (events[i].addresses); + + dhcp6_ia_na_client_public_main_t *dcpm = + &dhcp6_ia_na_client_public_main; + call_dhcp6_reply_event_callbacks (event, dcpm->functions); + + vpe_client_registration_t *reg; + /* *INDENT-OFF* */ + pool_foreach(reg, vpe_api_main.dhcp6_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_reply_event_t *msg = + vl_msg_api_alloc (event_size); + clib_memcpy (msg, event, event_size); + msg->_vl_msg_id = htons (VL_API_DHCP6_REPLY_EVENT + REPLY_MSG_ID_BASE); + 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_reply_process_node) = { + .function = dhcp6_reply_process, + .type = VLIB_NODE_TYPE_PROCESS, + .name = "dhcp6-reply-publisher-process", +}; +/* *INDENT-ON* */ + +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 boundary (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) + { + prefix_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); + clib_memset (event, 0, event_size); + + event->sw_if_index = htonl (events[i].body.sw_if_index); + event->server_index = htonl (events[i].body.server_index); + event->msg_type = events[i].body.msg_type; + event->T1 = htonl (events[i].body.T1); + event->T2 = htonl (events[i].body.T2); + event->inner_status_code = + htons (events[i].body.inner_status_code); + event->status_code = htons (events[i].body.status_code); + event->preference = events[i].body.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++) + { + dhcp6_prefix_info_t *info = &events[i].prefixes[j]; + ip6_address_encode (&info->prefix, prefix->prefix.address); + prefix->prefix.len = info->prefix_length; + prefix->valid_time = htonl (info->valid_time); + prefix->preferred_time = htonl (info->preferred_time); + prefix++; + } + vec_free (events[i].prefixes); + + 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 + REPLY_MSG_ID_BASE); + 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* */ + +/* + * dhcp_api_hookup + * Add vpe's API message handlers to the table. + * vlib has already mapped shared memory and + * added the client registration handlers. + * See .../vlib-api/vlibmemory/memclnt_vlib.c:memclnt_process() + */ +#include <dhcp/dhcp.api.c> + +static clib_error_t * +dhcp_api_hookup (vlib_main_t * vm) +{ + /* + * Set up the (msg_name, crc, message-id) table + */ + dhcp_base_msg_id = setup_message_id_table (); + + dhcp6_pd_set_publisher_node (dhcp6_pd_reply_process_node.index, + DHCP6_PD_DP_REPLY_REPORT); + dhcp6_set_publisher_node (dhcp6_reply_process_node.index, + DHCP6_DP_REPLY_REPORT); + + return 0; +} + +VLIB_API_INIT_FUNCTION (dhcp_api_hookup); + +#include <vlib/unix/plugin.h> +#include <vpp/app/version.h> + +/* *INDENT-OFF* */ +VLIB_PLUGIN_REGISTER () = { + .version = VPP_BUILD_VER, + .description = "Dynamic Host Configuration Protocol (DHCP)", +}; +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp_client_detect.c b/src/plugins/dhcp/dhcp_client_detect.c new file mode 100644 index 00000000000..31b89850802 --- /dev/null +++ b/src/plugins/dhcp/dhcp_client_detect.c @@ -0,0 +1,324 @@ +/* + * DHCP feature; applied as an input feature to select DHCP packets + * + * Copyright (c) 2013 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 <dhcp/client.h> +#include <vnet/udp/udp.h> + +#define foreach_dhcp_client_detect \ + _(EXTRACT, "Extract") + +typedef enum +{ +#define _(sym,str) DHCP_CLIENT_DETECT_ERROR_##sym, + foreach_dhcp_client_detect +#undef _ + DHCP_CLIENT_DETECT_N_ERROR, +} dhcp_client_detect_error_t; + +static char *dhcp_client_detect_error_strings[] = { +#define _(sym,string) string, + foreach_dhcp_client_detect +#undef _ +}; + +typedef enum +{ +#define _(sym,str) DHCP_CLIENT_DETECT_NEXT_##sym, + foreach_dhcp_client_detect +#undef _ + DHCP_CLIENT_DETECT_N_NEXT, +} dhcp_client_detect_next_t; + +/** + * per-packet trace data + */ +typedef struct dhcp_client_detect_trace_t_ +{ + /* per-pkt trace data */ + u8 extracted; +} dhcp_client_detect_trace_t; + +VLIB_NODE_FN (dhcp_client_detect_node) (vlib_main_t * vm, + vlib_node_runtime_t * node, + vlib_frame_t * frame) +{ + dhcp_client_detect_next_t next_index; + u16 dhcp_client_port_network_order; + u32 n_left_from, *from, *to_next; + u32 extractions; + + dhcp_client_port_network_order = + clib_net_to_host_u16 (UDP_DST_PORT_dhcp_to_client); + next_index = 0; + extractions = 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); + + /* + * This loop is optimised not so we can really quickly process DHCp + * offers... but so we can quickly sift them out when the interface + * is also receiving 'normal' packets + */ + while (n_left_from >= 8 && n_left_to_next >= 4) + { + udp_header_t *udp0, *udp1, *udp2, *udp3; + ip4_header_t *ip0, *ip1, *ip2, *ip3; + vlib_buffer_t *b0, *b1, *b2, *b3; + u32 next0, next1, next2, next3; + u32 bi0, bi1, bi2, bi3; + + next0 = next1 = next2 = next3 = ~0; + bi0 = to_next[0] = from[0]; + bi1 = to_next[1] = from[1]; + bi2 = to_next[2] = from[2]; + bi3 = to_next[3] = from[3]; + + /* Prefetch next iteration. */ + { + vlib_buffer_t *p2, *p3, *p4, *p5; + + p2 = vlib_get_buffer (vm, from[2]); + p3 = vlib_get_buffer (vm, from[3]); + p4 = vlib_get_buffer (vm, from[4]); + p5 = vlib_get_buffer (vm, from[5]); + + vlib_prefetch_buffer_header (p2, STORE); + vlib_prefetch_buffer_header (p3, STORE); + vlib_prefetch_buffer_header (p4, STORE); + vlib_prefetch_buffer_header (p5, STORE); + + CLIB_PREFETCH (p2->data, sizeof (ip0[0]) + sizeof (udp0[0]), + STORE); + CLIB_PREFETCH (p3->data, sizeof (ip0[0]) + sizeof (udp0[0]), + STORE); + CLIB_PREFETCH (p4->data, sizeof (ip0[0]) + sizeof (udp0[0]), + STORE); + CLIB_PREFETCH (p5->data, sizeof (ip0[0]) + sizeof (udp0[0]), + STORE); + } + + from += 4; + to_next += 4; + n_left_from -= 4; + n_left_to_next -= 4; + + b0 = vlib_get_buffer (vm, bi0); + b1 = vlib_get_buffer (vm, bi1); + b2 = vlib_get_buffer (vm, bi2); + b3 = vlib_get_buffer (vm, bi3); + ip0 = vlib_buffer_get_current (b0); + ip1 = vlib_buffer_get_current (b1); + ip2 = vlib_buffer_get_current (b2); + ip3 = vlib_buffer_get_current (b2); + + vnet_feature_next (&next0, b0); + vnet_feature_next (&next1, b1); + vnet_feature_next (&next2, b2); + vnet_feature_next (&next3, b3); + + if (ip0->protocol == IP_PROTOCOL_UDP) + { + udp0 = (udp_header_t *) (ip0 + 1); + + if (dhcp_client_port_network_order == udp0->dst_port) + { + next0 = DHCP_CLIENT_DETECT_NEXT_EXTRACT; + extractions++; + } + } + if (ip1->protocol == IP_PROTOCOL_UDP) + { + udp1 = (udp_header_t *) (ip1 + 1); + + if (dhcp_client_port_network_order == udp1->dst_port) + { + next1 = DHCP_CLIENT_DETECT_NEXT_EXTRACT; + extractions++; + } + } + if (ip2->protocol == IP_PROTOCOL_UDP) + { + udp2 = (udp_header_t *) (ip2 + 1); + + if (dhcp_client_port_network_order == udp2->dst_port) + { + next2 = DHCP_CLIENT_DETECT_NEXT_EXTRACT; + extractions++; + } + } + if (ip3->protocol == IP_PROTOCOL_UDP) + { + udp3 = (udp_header_t *) (ip3 + 1); + + if (dhcp_client_port_network_order == udp3->dst_port) + { + next3 = DHCP_CLIENT_DETECT_NEXT_EXTRACT; + extractions++; + } + } + + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_client_detect_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->extracted = (next0 == DHCP_CLIENT_DETECT_NEXT_EXTRACT); + } + if (PREDICT_FALSE (b1->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_client_detect_trace_t *t = + vlib_add_trace (vm, node, b1, sizeof (*t)); + t->extracted = (next1 == DHCP_CLIENT_DETECT_NEXT_EXTRACT); + } + if (PREDICT_FALSE (b2->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_client_detect_trace_t *t = + vlib_add_trace (vm, node, b2, sizeof (*t)); + t->extracted = (next2 == DHCP_CLIENT_DETECT_NEXT_EXTRACT); + } + if (PREDICT_FALSE (b3->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_client_detect_trace_t *t = + vlib_add_trace (vm, node, b3, sizeof (*t)); + t->extracted = (next3 == DHCP_CLIENT_DETECT_NEXT_EXTRACT); + } + + /* verify speculative enqueue, maybe switch current next frame */ + vlib_validate_buffer_enqueue_x4 (vm, node, next_index, + to_next, n_left_to_next, + bi0, bi1, bi2, bi3, + next0, next1, next2, next3); + } + + while (n_left_from > 0 && n_left_to_next > 0) + { + udp_header_t *udp0; + vlib_buffer_t *b0; + ip4_header_t *ip0; + u32 next0 = ~0; + u32 bi0; + + 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); + ip0 = vlib_buffer_get_current (b0); + + /* + * when this feature is applied on an interface that is already + * accepting packets (because e.g. the interface has other addresses + * assigned) we are looking for the preverbial needle in the haystack + * so assume the packet is not the one we are looking for. + */ + vnet_feature_next (&next0, b0); + + /* + * all we are looking for here is DHCP/BOOTP packet-to-client + * UDO port. + */ + if (ip0->protocol == IP_PROTOCOL_UDP) + { + udp0 = (udp_header_t *) (ip0 + 1); + + if (dhcp_client_port_network_order == udp0->dst_port) + { + next0 = DHCP_CLIENT_DETECT_NEXT_EXTRACT; + extractions++; + } + } + + if (PREDICT_FALSE (b0->flags & VLIB_BUFFER_IS_TRACED)) + { + dhcp_client_detect_trace_t *t = + vlib_add_trace (vm, node, b0, sizeof (*t)); + t->extracted = (next0 == DHCP_CLIENT_DETECT_NEXT_EXTRACT); + } + + /* 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); + } + + vlib_node_increment_counter (vm, node->node_index, + DHCP_CLIENT_DETECT_ERROR_EXTRACT, extractions); + + return frame->n_vectors; +} + +/* packet trace format function */ +static u8 * +format_dhcp_client_detect_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 *); + dhcp_client_detect_trace_t *t = + va_arg (*args, dhcp_client_detect_trace_t *); + + s = format (s, "dhcp-client-detect: %s", (t->extracted ? "yes" : "no")); + + return s; +} + +/* *INDENT-OFF* */ +VLIB_REGISTER_NODE (dhcp_client_detect_node) = { + .name = "ip4-dhcp-client-detect", + .vector_size = sizeof (u32), + .format_trace = format_dhcp_client_detect_trace, + .type = VLIB_NODE_TYPE_INTERNAL, + + .n_errors = ARRAY_LEN(dhcp_client_detect_error_strings), + .error_strings = dhcp_client_detect_error_strings, + + .n_next_nodes = DHCP_CLIENT_DETECT_N_NEXT, + .next_nodes = { + /* + * Jump straight to the UDP dispatch node thus avoiding + * the RPF checks in ip4-local that will fail + */ + [DHCP_CLIENT_DETECT_NEXT_EXTRACT] = "ip4-udp-lookup", + }, +}; + +VNET_FEATURE_INIT (ip4_dvr_reinject_feat_node, static) = +{ + .arc_name = "ip4-unicast", + .node_name = "ip4-dhcp-client-detect", + .runs_before = VNET_FEATURES ("ip4-not-enabled"), +}; + +/* *INDENT-ON* */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp_proxy.c b/src/plugins/dhcp/dhcp_proxy.c new file mode 100644 index 00000000000..1890c874b61 --- /dev/null +++ b/src/plugins/dhcp/dhcp_proxy.c @@ -0,0 +1,375 @@ +/* + * proxy_node.c: common dhcp v4 and v6 proxy node processing + * + * Copyright (c) 2013 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 <dhcp/dhcp_proxy.h> +#include <vnet/fib/fib_table.h> +#include <vnet/mfib/mfib_table.h> + +/** + * @brief Shard 4/6 instance of DHCP main + */ +dhcp_proxy_main_t dhcp_proxy_main; + +static void +dhcp_proxy_rx_table_lock (fib_protocol_t proto, u32 fib_index) +{ + if (FIB_PROTOCOL_IP4 == proto) + fib_table_lock (fib_index, proto, FIB_SOURCE_DHCP); + else + mfib_table_lock (fib_index, proto, MFIB_SOURCE_DHCP); +} + +static void +dhcp_proxy_rx_table_unlock (fib_protocol_t proto, u32 fib_index) +{ + if (FIB_PROTOCOL_IP4 == proto) + fib_table_unlock (fib_index, proto, FIB_SOURCE_DHCP); + else + mfib_table_unlock (fib_index, proto, MFIB_SOURCE_DHCP); +} + +u32 +dhcp_proxy_rx_table_get_table_id (fib_protocol_t proto, u32 fib_index) +{ + if (FIB_PROTOCOL_IP4 == proto) + { + fib_table_t *fib; + + fib = fib_table_get (fib_index, proto); + + return (fib->ft_table_id); + } + else + { + mfib_table_t *mfib; + + mfib = mfib_table_get (fib_index, proto); + + return (mfib->mft_table_id); + } +} + +void +dhcp_proxy_walk (fib_protocol_t proto, dhcp_proxy_walk_fn_t fn, void *ctx) +{ + dhcp_proxy_main_t *dpm = &dhcp_proxy_main; + dhcp_proxy_t *server; + u32 server_index, i; + + vec_foreach_index (i, dpm->dhcp_server_index_by_rx_fib_index[proto]) + { + server_index = dpm->dhcp_server_index_by_rx_fib_index[proto][i]; + if (~0 == server_index) + continue; + + server = pool_elt_at_index (dpm->dhcp_servers[proto], server_index); + + if (!fn (server, ctx)) + break; + } +} + +void +dhcp_vss_walk (fib_protocol_t proto, dhcp_vss_walk_fn_t fn, void *ctx) +{ + dhcp_proxy_main_t *dpm = &dhcp_proxy_main; + mfib_table_t *mfib; + dhcp_vss_t *vss; + u32 vss_index, i; + fib_table_t *fib; + + vec_foreach_index (i, dpm->vss_index_by_rx_fib_index[proto]) + { + vss_index = dpm->vss_index_by_rx_fib_index[proto][i]; + if (~0 == vss_index) + continue; + + vss = pool_elt_at_index (dpm->vss[proto], vss_index); + + if (FIB_PROTOCOL_IP4 == proto) + { + fib = fib_table_get (i, proto); + + if (!fn (vss, fib->ft_table_id, ctx)) + break; + } + else + { + mfib = mfib_table_get (i, proto); + + if (!fn (vss, mfib->mft_table_id, ctx)) + break; + } + } +} + +static u32 +dhcp_proxy_server_find (dhcp_proxy_t * proxy, + fib_protocol_t proto, + ip46_address_t * addr, u32 server_table_id) +{ + dhcp_server_t *server; + u32 ii, fib_index; + + vec_foreach_index (ii, proxy->dhcp_servers) + { + server = &proxy->dhcp_servers[ii]; + fib_index = fib_table_find (proto, server_table_id); + + if (ip46_address_is_equal (&server->dhcp_server, + addr) && + (server->server_fib_index == fib_index)) + { + return (ii); + } + } + return (~0); +} + +int +dhcp_proxy_server_del (fib_protocol_t proto, + u32 rx_fib_index, + ip46_address_t * addr, u32 server_table_id) +{ + dhcp_proxy_main_t *dpm = &dhcp_proxy_main; + dhcp_proxy_t *proxy = 0; + + proxy = dhcp_get_proxy (dpm, rx_fib_index, proto); + + if (NULL != proxy) + { + dhcp_server_t *server; + u32 index; + + index = dhcp_proxy_server_find (proxy, proto, addr, server_table_id); + + if (~0 != index) + { + server = &proxy->dhcp_servers[index]; + fib_table_unlock (server->server_fib_index, proto, FIB_SOURCE_DHCP); + + vec_del1 (proxy->dhcp_servers, index); + + if (0 == vec_len (proxy->dhcp_servers)) + { + /* no servers left, delete the proxy config */ + dpm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index] = + ~0; + vec_free (proxy->dhcp_servers); + pool_put (dpm->dhcp_servers[proto], proxy); + return (1); + } + } + } + + /* the proxy still exists */ + return (0); +} + +int +dhcp_proxy_server_add (fib_protocol_t proto, + ip46_address_t * addr, + ip46_address_t * src_address, + u32 rx_fib_index, u32 server_table_id) +{ + dhcp_proxy_main_t *dpm = &dhcp_proxy_main; + dhcp_proxy_t *proxy = 0; + int new = 0; + + proxy = dhcp_get_proxy (dpm, rx_fib_index, proto); + + if (NULL == proxy) + { + vec_validate_init_empty (dpm->dhcp_server_index_by_rx_fib_index[proto], + rx_fib_index, ~0); + + pool_get (dpm->dhcp_servers[proto], proxy); + clib_memset (proxy, 0, sizeof (*proxy)); + new = 1; + + dpm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index] = + proxy - dpm->dhcp_servers[proto]; + + proxy->dhcp_src_address = *src_address; + proxy->rx_fib_index = rx_fib_index; + } + else + { + if (~0 != dhcp_proxy_server_find (proxy, proto, addr, server_table_id)) + { + return (new); + } + } + + dhcp_server_t server = { + .dhcp_server = *addr, + .server_fib_index = fib_table_find_or_create_and_lock (proto, + server_table_id, + FIB_SOURCE_DHCP), + }; + + vec_add1 (proxy->dhcp_servers, server); + + return (new); +} + +typedef struct dhcp4_proxy_dump_walk_ctx_t_ +{ + fib_protocol_t proto; + void *opaque; + u32 context; +} dhcp_proxy_dump_walk_cxt_t; + +static int +dhcp_proxy_dump_walk (dhcp_proxy_t * proxy, void *arg) +{ + dhcp_proxy_dump_walk_cxt_t *ctx = arg; + + dhcp_send_details (ctx->proto, ctx->opaque, ctx->context, proxy); + + return (1); +} + +void +dhcp_proxy_dump (fib_protocol_t proto, void *opaque, u32 context) +{ + dhcp_proxy_dump_walk_cxt_t ctx = { + .proto = proto, + .opaque = opaque, + .context = context, + }; + dhcp_proxy_walk (proto, dhcp_proxy_dump_walk, &ctx); +} + +int +dhcp_vss_show_walk (dhcp_vss_t * vss, u32 rx_table_id, void *ctx) +{ + vlib_main_t *vm = ctx; + + if (vss->vss_type == VSS_TYPE_VPN_ID) + { + u32 oui = ((u32) vss->vpn_id[0] << 16) + ((u32) vss->vpn_id[1] << 8) + + ((u32) vss->vpn_id[2]); + u32 fib_id = ((u32) vss->vpn_id[3] << 24) + ((u32) vss->vpn_id[4] << 16) + + ((u32) vss->vpn_id[5] << 8) + ((u32) vss->vpn_id[6]); + vlib_cli_output (vm, " fib_table: %d oui: %d vpn_index: %d", + rx_table_id, oui, fib_id); + } + else if (vss->vss_type == VSS_TYPE_ASCII) + vlib_cli_output (vm, " fib_table: %d vpn_id: %s", + rx_table_id, vss->vpn_ascii_id); + else + vlib_cli_output (vm, " fib_table: %d default global vpn", rx_table_id); + + return (1); +} + +void +update_vss (dhcp_vss_t * v, + u8 vss_type, u8 * vpn_ascii_id, u32 oui, u32 vpn_index) +{ + v->vss_type = vss_type; + if (v->vpn_ascii_id) + { + if (v->vpn_ascii_id == (u8 *) ~ 0) + v->vpn_ascii_id = 0; + else + vec_free (v->vpn_ascii_id); + } + + if (vss_type == VSS_TYPE_ASCII) + v->vpn_ascii_id = vpn_ascii_id; + else if (vss_type == VSS_TYPE_VPN_ID) + { + v->vpn_id[0] = (oui >> 16) & 0xff; + v->vpn_id[1] = (oui >> 8) & 0xff; + v->vpn_id[2] = (oui >> 0) & 0xff; + v->vpn_id[3] = (vpn_index >> 24) & 0xff; + v->vpn_id[4] = (vpn_index >> 16) & 0xff; + v->vpn_id[5] = (vpn_index >> 8) & 0xff; + v->vpn_id[6] = (vpn_index >> 0) & 0xff; + } +} + +int +dhcp_proxy_set_vss (fib_protocol_t proto, + u32 tbl_id, + u8 vss_type, + u8 * vpn_ascii_id, u32 oui, u32 vpn_index, u8 is_del) +{ + dhcp_proxy_main_t *dm = &dhcp_proxy_main; + dhcp_vss_t *v = NULL; + u32 rx_fib_index; + int rc = 0; + + if (proto == FIB_PROTOCOL_IP4) + rx_fib_index = fib_table_find_or_create_and_lock (proto, tbl_id, + FIB_SOURCE_DHCP); + else + rx_fib_index = mfib_table_find_or_create_and_lock (proto, tbl_id, + MFIB_SOURCE_DHCP); + v = dhcp_get_vss_info (dm, rx_fib_index, proto); + + if (NULL != v) + { + if (is_del) + { + /* release the lock held on the table when the VSS + * info was created */ + dhcp_proxy_rx_table_unlock (proto, rx_fib_index); + + vec_free (v->vpn_ascii_id); + pool_put (dm->vss[proto], v); + dm->vss_index_by_rx_fib_index[proto][rx_fib_index] = ~0; + } + else + { + update_vss (v, vss_type, vpn_ascii_id, oui, vpn_index); + } + } + else + { + if (is_del) + rc = VNET_API_ERROR_NO_SUCH_ENTRY; + else + { + /* create a new entry */ + vec_validate_init_empty (dm->vss_index_by_rx_fib_index[proto], + rx_fib_index, ~0); + + /* hold a lock on the table whilst the VSS info exist */ + pool_get (dm->vss[proto], v); + update_vss (v, vss_type, vpn_ascii_id, oui, vpn_index); + dm->vss_index_by_rx_fib_index[proto][rx_fib_index] = + v - dm->vss[proto]; + dhcp_proxy_rx_table_lock (proto, rx_fib_index); + } + } + + /* Release the lock taken during the create_or_lock at the start */ + dhcp_proxy_rx_table_unlock (proto, rx_fib_index); + + return (rc); +} + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp_proxy.h b/src/plugins/dhcp/dhcp_proxy.h new file mode 100644 index 00000000000..2b120b5c5f4 --- /dev/null +++ b/src/plugins/dhcp/dhcp_proxy.h @@ -0,0 +1,306 @@ +/* + * dhcp_proxy.h: DHCP v4 & v6 proxy common functions/types + * + * Copyright (c) 2013 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_dhcp_proxy_h +#define included_dhcp_proxy_h + +#include <vnet/vnet.h> +#include <dhcp/dhcp4_packet.h> +#include <vnet/ethernet/ethernet.h> +#include <vnet/ip/ip.h> +#include <vnet/ip/ip4.h> +#include <vnet/ip/ip4_packet.h> +#include <vnet/pg/pg.h> +#include <vnet/ip/format.h> +#include <vnet/udp/udp.h> + +typedef enum +{ +#define dhcp_proxy_error(n,s) DHCP_PROXY_ERROR_##n, +#include <dhcp/dhcp4_proxy_error.def> +#undef dhcp_proxy_error + DHCP_PROXY_N_ERROR, +} dhcp_proxy_error_t; + +typedef enum +{ +#define dhcpv6_proxy_error(n,s) DHCPV6_PROXY_ERROR_##n, +#include <dhcp/dhcp6_proxy_error.def> +#undef dhcpv6_proxy_error + DHCPV6_PROXY_N_ERROR, +} dhcpv6_proxy_error_t; + +/* flags to indicate which DHCP ports should be or have been registered */ +typedef enum +{ + DHCP_PORT_REG_CLIENT = 0x1, + DHCP_PORT_REG_SERVER = 0x2, +} dhcp_port_reg_flags_t; + +/** + * @brief The Virtual Sub-net Selection information for a given RX FIB + */ +typedef struct dhcp_vss_t_ +{ + /** + * @brief VSS type as defined in RFC 6607: + * 0 for NVT ASCII VPN Identifier + * 1 for RFC 2685 VPN-ID of 7 octects - 3 bytes OUI & 4 bytes VPN index + * 255 for global default VPN + */ + u8 vss_type; +#define VSS_TYPE_ASCII 0 +#define VSS_TYPE_VPN_ID 1 +#define VSS_TYPE_INVALID 123 +#define VSS_TYPE_DEFAULT 255 + /** + * @brief Type 1 VPN-ID + */ + u8 vpn_id[7]; + /** + * @brief Type 0 ASCII VPN Identifier + */ + u8 *vpn_ascii_id; +} dhcp_vss_t; + +/** + * @brief A representation of a single DHCP Server within a given VRF config + */ +typedef struct dhcp_server_t_ +{ + /** + * @brief The address of the DHCP server to which to relay the client's + * messages + */ + ip46_address_t dhcp_server; + + /** + * @brief The FIB index (not the external Table-ID) in which the server + * is reachable. + */ + u32 server_fib_index; +} dhcp_server_t; + +/** + * @brief A DHCP proxy representation fpr per-client VRF config + */ +typedef struct dhcp_proxy_t_ +{ + /** + * @brief The set of DHCP servers to which messages are relayed. + * If multiple servers are configured then discover/solict messages + * are relayed to each. A cookie is maintained for the relay, and only + * one message is replayed to the client, based on the presence of the + * cookie. + * The expectation is there are only 1 or 2 servers, hence no fancy DB. + */ + dhcp_server_t *dhcp_servers; + + /** + * @brief Hash table of pending requets key'd on the clients MAC address + */ + uword *dhcp_pending; + + /** + * @brief A lock for the pending request DB. + */ + int lock; + + /** + * @brief The source address to use in relayed messaes + */ + ip46_address_t dhcp_src_address; + + /** + * @brief The FIB index (not the external Table-ID) in which the client + * is resides. + */ + u32 rx_fib_index; +} dhcp_proxy_t; + +#define DHCP_N_PROTOS (FIB_PROTOCOL_IP6 + 1) + +/** + * @brief Collection of global DHCP proxy data + */ +typedef struct +{ + /* Pool of DHCP servers */ + dhcp_proxy_t *dhcp_servers[DHCP_N_PROTOS]; + + /* Pool of selected DHCP server. Zero is the default server */ + u32 *dhcp_server_index_by_rx_fib_index[DHCP_N_PROTOS]; + + /* to drop pkts in server-to-client direction */ + u32 error_drop_node_index; + + dhcp_vss_t *vss[DHCP_N_PROTOS]; + + /* hash lookup specific vrf_id -> option 82 vss suboption */ + u32 *vss_index_by_rx_fib_index[DHCP_N_PROTOS]; + + /* flags to indicate which udp ports have been registered */ + int udp_ports_registered; + + /* convenience */ + vlib_main_t *vlib_main; + +} dhcp_proxy_main_t; + +extern dhcp_proxy_main_t dhcp_proxy_main; + +/** + * @brief Register the dhcp client and/or server ports, if not already done + */ +void dhcp_maybe_register_udp_ports (dhcp_port_reg_flags_t ports); + +/** + * @brief Send the details of a proxy session to the API client during a dump + */ +void dhcp_send_details (fib_protocol_t proto, + void *opaque, u32 context, dhcp_proxy_t * proxy); + +/** + * @brief Show (on CLI) a VSS config during a show walk + */ +int dhcp_vss_show_walk (dhcp_vss_t * vss, u32 rx_table_id, void *ctx); + +/** + * @brief Configure/set a new VSS info + */ +int dhcp_proxy_set_vss (fib_protocol_t proto, + u32 tbl_id, + u8 vss_type, + u8 * vpn_ascii_id, u32 oui, u32 vpn_index, u8 is_del); + +/** + * @brief Dump the proxy configs to the API + */ +void dhcp_proxy_dump (fib_protocol_t proto, void *opaque, u32 context); + +/** + * @brief Add a new DHCP proxy server configuration. + * @return 1 is the config is new, + * 0 otherwise (implying a modify of an existing) + */ +int dhcp_proxy_server_add (fib_protocol_t proto, + ip46_address_t * addr, + ip46_address_t * src_address, + u32 rx_fib_iindex, u32 server_table_id); + +/** + * @brief Delete a DHCP proxy config + * @return 1 if the proxy is deleted, 0 otherwise + */ +int dhcp_proxy_server_del (fib_protocol_t proto, + u32 rx_fib_index, + ip46_address_t * addr, u32 server_table_id); + +u32 dhcp_proxy_rx_table_get_table_id (fib_protocol_t proto, u32 fib_index); + +/** + * @brief Callback function invoked for each DHCP proxy entry + * return 0 to break the walk, non-zero otherwise. + */ +typedef int (*dhcp_proxy_walk_fn_t) (dhcp_proxy_t * server, void *ctx); + +/** + * @brief Walk/Visit each DHCP proxy server + */ +void dhcp_proxy_walk (fib_protocol_t proto, + dhcp_proxy_walk_fn_t fn, void *ctx); + +/** + * @brief Callback function invoked for each DHCP VSS entry + * return 0 to break the walk, non-zero otherwise. + */ +typedef int (*dhcp_vss_walk_fn_t) (dhcp_vss_t * server, + u32 rx_table_id, void *ctx); + +/** + * @brief Walk/Visit each DHCP proxy VSS + */ +void dhcp_vss_walk (fib_protocol_t proto, dhcp_vss_walk_fn_t fn, void *ctx); + +/** + * @brief Lock a proxy object to prevent simultaneous access of its + * pending store + */ +void dhcp_proxy_lock (dhcp_proxy_t * server); + +/** + * @brief Lock a proxy object to prevent simultaneous access of its + * pending store + */ +void dhcp_proxy_unlock (dhcp_proxy_t * server); + +/** + * @brief Get the VSS data for the FIB index + */ +static inline dhcp_vss_t * +dhcp_get_vss_info (dhcp_proxy_main_t * dm, + u32 rx_fib_index, fib_protocol_t proto) +{ + dhcp_vss_t *v = NULL; + + if (vec_len (dm->vss_index_by_rx_fib_index[proto]) > rx_fib_index && + dm->vss_index_by_rx_fib_index[proto][rx_fib_index] != ~0) + { + v = pool_elt_at_index (dm->vss[proto], + dm->vss_index_by_rx_fib_index[proto] + [rx_fib_index]); + } + + return (v); +} + +/** + * @brief Get the DHCP proxy server data for the FIB index + */ +static inline dhcp_proxy_t * +dhcp_get_proxy (dhcp_proxy_main_t * dm, + u32 rx_fib_index, fib_protocol_t proto) +{ + dhcp_proxy_t *s = NULL; + + if (vec_len (dm->dhcp_server_index_by_rx_fib_index[proto]) > rx_fib_index && + dm->dhcp_server_index_by_rx_fib_index[proto][rx_fib_index] != ~0) + { + s = pool_elt_at_index (dm->dhcp_servers[proto], + dm->dhcp_server_index_by_rx_fib_index[proto] + [rx_fib_index]); + } + + return (s); +} + +int dhcp6_proxy_set_server (ip46_address_t * addr, + ip46_address_t * src_addr, + u32 rx_table_id, u32 server_table_id, int is_del); +int dhcp4_proxy_set_server (ip46_address_t * addr, + ip46_address_t * src_addr, + u32 rx_table_id, u32 server_table_id, int is_del); + +#endif /* included_dhcp_proxy_h */ + +/* + * fd.io coding-style-patch-verification: ON + * + * Local Variables: + * eval: (c-set-style "gnu") + * End: + */ diff --git a/src/plugins/dhcp/dhcp_test.c b/src/plugins/dhcp/dhcp_test.c new file mode 100644 index 00000000000..a042dc02843 --- /dev/null +++ b/src/plugins/dhcp/dhcp_test.c @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2015 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 <vat/vat.h> +#include <vlibapi/api.h> +#include <vlibmemory/api.h> +#include <vppinfra/error.h> + +#include <dhcp/client.h> +#include <dhcp/dhcp_proxy.h> +#include <vnet/ip/ip_format_fns.h> +#include <vnet/ethernet/ethernet_format_fns.h> + +/* define message IDs */ +#include <dhcp/dhcp.api_enum.h> +#include <dhcp/dhcp.api_types.h> + +typedef struct { + /* API message ID base */ + u16 msg_id_base; + vat_main_t *vat_main; +} dhcp_test_main_t; + +dhcp_test_main_t dhcp_test_main; + +#define __plugin_msg_base dhcp_test_main.msg_id_base +#include <vlibapi/vat_helper_macros.h> + +/* Macro to finish up custom dump fns */ +#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__) +#define FINISH \ + vec_add1 (s, 0); \ + vl_print (handle, (char *)s); \ + vec_free (s); \ + return handle; + +static int +api_dhcp_proxy_config (vat_main_t * vam) +{ + unformat_input_t *i = vam->input; + vl_api_dhcp_proxy_config_t *mp; + u32 rx_vrf_id = 0; + u32 server_vrf_id = 0; + u8 is_add = 1; + u8 v4_address_set = 0; + u8 v6_address_set = 0; + ip4_address_t v4address; + ip6_address_t v6address; + u8 v4_src_address_set = 0; + u8 v6_src_address_set = 0; + ip4_address_t v4srcaddress; + ip6_address_t v6srcaddress; + int ret; + + /* Parse args required to build the message */ + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "del")) + is_add = 0; + else if (unformat (i, "rx_vrf_id %d", &rx_vrf_id)) + ; + else if (unformat (i, "server_vrf_id %d", &server_vrf_id)) + ; + else if (unformat (i, "svr %U", unformat_ip4_address, &v4address)) + v4_address_set = 1; + else if (unformat (i, "svr %U", unformat_ip6_address, &v6address)) + v6_address_set = 1; + else if (unformat (i, "src %U", unformat_ip4_address, &v4srcaddress)) + v4_src_address_set = 1; + else if (unformat (i, "src %U", unformat_ip6_address, &v6srcaddress)) + v6_src_address_set = 1; + else + break; + } + + if (v4_address_set && v6_address_set) + { + errmsg ("both v4 and v6 server addresses set"); + return -99; + } + if (!v4_address_set && !v6_address_set) + { + errmsg ("no server addresses set"); + return -99; + } + + if (v4_src_address_set && v6_src_address_set) + { + errmsg ("both v4 and v6 src addresses set"); + return -99; + } + if (!v4_src_address_set && !v6_src_address_set) + { + errmsg ("no src addresses set"); + return -99; + } + + if (!(v4_src_address_set && v4_address_set) && + !(v6_src_address_set && v6_address_set)) + { + errmsg ("no matching server and src addresses set"); + return -99; + } + + /* Construct the API message */ + M (DHCP_PROXY_CONFIG, mp); + + mp->is_add = is_add; + mp->rx_vrf_id = ntohl (rx_vrf_id); + mp->server_vrf_id = ntohl (server_vrf_id); + if (v6_address_set) + { + clib_memcpy (&mp->dhcp_server.un, &v6address, sizeof (v6address)); + clib_memcpy (&mp->dhcp_src_address.un, &v6srcaddress, + sizeof (v6address)); + } + else + { + clib_memcpy (&mp->dhcp_server.un, &v4address, sizeof (v4address)); + clib_memcpy (&mp->dhcp_src_address.un, &v4srcaddress, + sizeof (v4address)); + } + + /* send it... */ + S (mp); + + /* Wait for a reply, return good/bad news */ + W (ret); + return ret; +} + +#define vl_api_dhcp_proxy_details_t_endian vl_noop_handler +#define vl_api_dhcp_proxy_details_t_print vl_noop_handler + +static void +vl_api_dhcp_proxy_details_t_handler (vl_api_dhcp_proxy_details_t * mp) +{ + vat_main_t *vam = &vat_main; + u32 i, count = mp->count; + vl_api_dhcp_server_t *s; + + if (mp->is_ipv6) + print (vam->ofp, + "RX Table-ID %d, Source Address %U, VSS Type %d, " + "VSS ASCII VPN-ID '%s', VSS RFC2685 VPN-ID (oui:id) %d:%d", + ntohl (mp->rx_vrf_id), + format_ip6_address, mp->dhcp_src_address, + mp->vss_type, mp->vss_vpn_ascii_id, + ntohl (mp->vss_oui), ntohl (mp->vss_fib_id)); + else + print (vam->ofp, + "RX Table-ID %d, Source Address %U, VSS Type %d, " + "VSS ASCII VPN-ID '%s', VSS RFC2685 VPN-ID (oui:id) %d:%d", + ntohl (mp->rx_vrf_id), + format_ip4_address, mp->dhcp_src_address, + mp->vss_type, mp->vss_vpn_ascii_id, + ntohl (mp->vss_oui), ntohl (mp->vss_fib_id)); + + for (i = 0; i < count; i++) + { + s = &mp->servers[i]; + + if (mp->is_ipv6) + print (vam->ofp, + " Server Table-ID %d, Server Address %U", + ntohl (s->server_vrf_id), format_ip6_address, s->dhcp_server); + else + print (vam->ofp, + " Server Table-ID %d, Server Address %U", + ntohl (s->server_vrf_id), format_ip4_address, s->dhcp_server); + } +} + +static int +api_dhcp_proxy_dump (vat_main_t * vam) +{ + unformat_input_t *i = vam->input; + vl_api_dhcp_plugin_control_ping_t *mp_ping; + vl_api_dhcp_proxy_dump_t *mp; + u8 is_ipv6 = 0; + int ret; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "ipv6")) + is_ipv6 = 1; + else + { + clib_warning ("parse error '%U'", format_unformat_error, i); + return -99; + } + } + + M (DHCP_PROXY_DUMP, mp); + + mp->is_ip6 = is_ipv6; + S (mp); + + /* Use a control ping for synchronization */ + MPING (DHCP_PLUGIN_CONTROL_PING, mp_ping); + S (mp_ping); + + W (ret); + return ret; +} + +static int +api_dhcp_proxy_set_vss (vat_main_t * vam) +{ + unformat_input_t *i = vam->input; + vl_api_dhcp_proxy_set_vss_t *mp; + u8 is_ipv6 = 0; + u8 is_add = 1; + u32 tbl_id = ~0; + u8 vss_type = VSS_TYPE_DEFAULT; + u8 *vpn_ascii_id = 0; + u32 oui = 0; + u32 fib_id = 0; + int ret; + + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "tbl_id %d", &tbl_id)) + ; + else if (unformat (i, "vpn_ascii_id %s", &vpn_ascii_id)) + vss_type = VSS_TYPE_ASCII; + else if (unformat (i, "fib_id %d", &fib_id)) + vss_type = VSS_TYPE_VPN_ID; + else if (unformat (i, "oui %d", &oui)) + vss_type = VSS_TYPE_VPN_ID; + else if (unformat (i, "ipv6")) + is_ipv6 = 1; + else if (unformat (i, "del")) + is_add = 0; + else + break; + } + + if (tbl_id == ~0) + { + errmsg ("missing tbl_id "); + vec_free (vpn_ascii_id); + return -99; + } + + if ((vpn_ascii_id) && (vec_len (vpn_ascii_id) > 128)) + { + errmsg ("vpn_ascii_id cannot be longer than 128 "); + vec_free (vpn_ascii_id); + return -99; + } + + M (DHCP_PROXY_SET_VSS, mp); + mp->tbl_id = ntohl (tbl_id); + mp->vss_type = vss_type; + if (vpn_ascii_id) + { + clib_memcpy (mp->vpn_ascii_id, vpn_ascii_id, vec_len (vpn_ascii_id)); + mp->vpn_ascii_id[vec_len (vpn_ascii_id)] = 0; + } + mp->vpn_index = ntohl (fib_id); + mp->oui = ntohl (oui); + mp->is_ipv6 = is_ipv6; + mp->is_add = is_add; + + S (mp); + W (ret); + + vec_free (vpn_ascii_id); + return ret; +} + +static int +api_dhcp_client_config (vat_main_t * vam) +{ + unformat_input_t *i = vam->input; + vl_api_dhcp_client_config_t *mp; + u32 sw_if_index; + u8 sw_if_index_set = 0; + u8 is_add = 1; + u8 *hostname = 0; + u8 disable_event = 0; + int ret; + + /* Parse args required to build the message */ + while (unformat_check_input (i) != UNFORMAT_END_OF_INPUT) + { + if (unformat (i, "del")) + is_add = 0; + else + if (unformat (i, "%U", unformat_sw_if_index, vam, &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (i, "sw_if_index %d", &sw_if_index)) + sw_if_index_set = 1; + else if (unformat (i, "hostname %s", &hostname)) + ; + else if (unformat (i, "disable_event")) + disable_event = 1; + else + break; + } + + if (sw_if_index_set == 0) + { + errmsg ("missing interface name or sw_if_index"); + return -99; + } + + if (vec_len (hostname) > 63) + { + errmsg ("hostname too long"); + } + vec_add1 (hostname, 0); + + /* Construct the API message */ + M (DHCP_CLIENT_CONFIG, mp); + + mp->is_add = is_add; + mp->client.sw_if_index = htonl (sw_if_index); + clib_memcpy (mp->client.hostname, hostname, vec_len (hostname)); + vec_free (hostname); + mp->client.want_dhcp_event = disable_event ? 0 : 1; + mp->client.pid = htonl (getpid ()); + + /* send it... */ + S (mp); + + /* Wait for a reply, return good/bad news */ + W (ret); + return ret; +} + +/* static void *vl_api_dhcp_proxy_config_t_print */ +/* (vl_api_dhcp_proxy_config_t * mp, void *handle) */ +/* { */ +/* u8 *s; */ + +/* s = format (0, "SCRIPT: dhcp_proxy_config_2 "); */ + +/* s = format (s, "rx_vrf_id %d ", (mp->rx_vrf_id)); */ +/* s = format (s, "server_vrf_id %d ", (mp->server_vrf_id)); */ + +/* s = format (s, "svr %U ", format_ip46_address, */ +/* (ip46_address_t *) & mp->dhcp_server.un); */ +/* s = format (s, "src %U ", format_ip46_address, */ +/* (ip46_address_t *) & mp->dhcp_src_address.un); */ + +/* if (mp->is_add == 0) */ +/* s = format (s, "del "); */ + +/* FINISH; */ +/* } */ + +/* static void *vl_api_dhcp_proxy_set_vss_t_print */ +/* (vl_api_dhcp_proxy_set_vss_t * mp, void *handle) */ +/* { */ +/* u8 *s; */ + +/* s = format (0, "SCRIPT: dhcp_proxy_set_vss "); */ + +/* s = format (s, "tbl_id %d ", (mp->tbl_id)); */ + +/* if (mp->vss_type == VSS_TYPE_VPN_ID) */ +/* { */ +/* s = format (s, "fib_id %d ", (mp->vpn_index)); */ +/* s = format (s, "oui %d ", (mp->oui)); */ +/* } */ +/* else if (mp->vss_type == VSS_TYPE_ASCII) */ +/* s = format (s, "vpn_ascii_id %s", mp->vpn_ascii_id); */ + +/* if (mp->is_ipv6 != 0) */ +/* s = format (s, "ipv6 "); */ + +/* if (mp->is_add == 0) */ +/* s = format (s, "del "); */ + +/* FINISH; */ +/* } */ + +/* static void *vl_api_dhcp_client_config_t_print */ +/* (vl_api_dhcp_client_config_t * mp, void *handle) */ +/* { */ +/* u8 *s; */ + +/* s = format (0, "SCRIPT: dhcp_client_config "); */ + +/* s = format (s, "sw_if_index %d ", (mp->client.sw_if_index)); */ + +/* s = format (s, "hostname %s ", mp->client.hostname); */ + +/* s = format (s, "want_dhcp_event %d ", mp->client.want_dhcp_event); */ + +/* s = format (s, "pid %d ", (mp->client.pid)); */ + +/* if (mp->is_add == 0) */ +/* s = format (s, "del "); */ + +/* FINISH; */ +/* } */ + +static int +api_want_dhcp6_reply_events (vat_main_t * vam) +{ + return -1; +} +static int +api_want_dhcp6_pd_reply_events (vat_main_t * vam) +{ + return -1; +} +static int +api_dhcp6_send_client_message (vat_main_t * vam) +{ + return -1; +} +static int +api_dhcp6_pd_send_client_message (vat_main_t * vam) +{ + return -1; +} +static int +api_dhcp_client_dump (vat_main_t * vam) +{ + return -1; +} +static int +api_dhcp6_duid_ll_set (vat_main_t * vam) +{ + return -1; +} +static int +api_dhcp6_clients_enable_disable (vat_main_t * vam) +{ + return -1; +} +static int +api_dhcp_plugin_control_ping (vat_main_t * vam) +{ + return -1; +} +static int +api_dhcp_plugin_get_version (vat_main_t * vam) +{ + return -1; +} + +#define vl_api_dhcp_client_details_t_handler vl_noop_handler + +static void +vl_api_dhcp_plugin_get_version_reply_t_handler (vl_api_dhcp_plugin_get_version_reply_t * mp) +{ +} + +static void +vl_api_dhcp_plugin_control_ping_reply_t_handler (vl_api_dhcp_plugin_get_version_reply_t * mp) +{ +} + +/* static void */ +/* vl_api_dhcp_compl_event_t_handler (vl_api_dhcp_compl_event_t * mp) */ +/* { */ +/* u8 *s, i; */ + +/* s = format (0, "DHCP compl event: pid %d hostname %s host_addr %U " */ +/* "host_mac %U router_addr %U", */ +/* ntohl (mp->pid), mp->lease.hostname, */ +/* format_ip4_address, mp->lease.host_address, */ +/* format_ethernet_address, mp->lease.host_mac, */ +/* format_ip4_address, mp->lease.router_address); */ + +/* for (i = 0; i < mp->lease.count; i++) */ +/* s = */ +/* format (s, " domain_server_addr %U", format_ip4_address, */ +/* mp->lease.domain_server[i].address); */ + +/* errmsg ((char *) s); */ +/* vec_free (s); */ +/* } */ + +#include <dhcp/dhcp.api_test.c> diff --git a/src/plugins/dhcp/test/test_dhcp.py b/src/plugins/dhcp/test/test_dhcp.py new file mode 100644 index 00000000000..2200e489d00 --- /dev/null +++ b/src/plugins/dhcp/test/test_dhcp.py @@ -0,0 +1,1653 @@ +#!/usr/bin/env python + +import unittest +import socket +import struct + +from framework import VppTestCase, VppTestRunner, running_extended_tests +from vpp_neighbor import VppNeighbor +from vpp_ip_route import find_route, VppIpTable +from util import mk_ll_addr +import scapy.compat +from scapy.layers.l2 import Ether, getmacbyip, ARP, Dot1Q +from scapy.layers.inet import IP, UDP, ICMP +from scapy.layers.inet6 import IPv6, in6_getnsmac +from scapy.utils6 import in6_mactoifaceid +from scapy.layers.dhcp import DHCP, BOOTP, DHCPTypes +from scapy.layers.dhcp6 import DHCP6, DHCP6_Solicit, DHCP6_RelayForward, \ + DHCP6_RelayReply, DHCP6_Advertise, DHCP6OptRelayMsg, DHCP6OptIfaceId, \ + DHCP6OptStatusCode, DHCP6OptVSS, DHCP6OptClientLinkLayerAddr, DHCP6_Request +from socket import AF_INET, AF_INET6 +from scapy.utils import inet_pton, inet_ntop +from scapy.utils6 import in6_ptop +from vpp_papi import mac_pton, VppEnum +from vpp_sub_interface import VppDot1QSubint +from vpp_qos import VppQosEgressMap, VppQosMark +from vpp_dhcp import VppDHCPClient, VppDHCPProxy + + +DHCP4_CLIENT_PORT = 68 +DHCP4_SERVER_PORT = 67 +DHCP6_CLIENT_PORT = 547 +DHCP6_SERVER_PORT = 546 + + +class TestDHCP(VppTestCase): + """ DHCP Test Case """ + + @classmethod + def setUpClass(cls): + super(TestDHCP, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestDHCP, cls).tearDownClass() + + def setUp(self): + super(TestDHCP, self).setUp() + + # create 6 pg interfaces for pg0 to pg5 + self.create_pg_interfaces(range(6)) + self.tables = [] + + # pg0 to 2 are IP configured in VRF 0, 1 and 2. + # pg3 to 5 are non IP-configured in VRF 0, 1 and 2. + table_id = 0 + for table_id in range(1, 4): + tbl4 = VppIpTable(self, table_id) + tbl4.add_vpp_config() + self.tables.append(tbl4) + tbl6 = VppIpTable(self, table_id, is_ip6=1) + tbl6.add_vpp_config() + self.tables.append(tbl6) + + table_id = 0 + for i in self.pg_interfaces[:3]: + i.admin_up() + i.set_table_ip4(table_id) + i.set_table_ip6(table_id) + i.config_ip4() + i.resolve_arp() + i.config_ip6() + i.resolve_ndp() + table_id += 1 + + table_id = 0 + for i in self.pg_interfaces[3:]: + i.admin_up() + i.set_table_ip4(table_id) + i.set_table_ip6(table_id) + table_id += 1 + + def tearDown(self): + for i in self.pg_interfaces[:3]: + i.unconfig_ip4() + i.unconfig_ip6() + + for i in self.pg_interfaces: + i.set_table_ip4(0) + i.set_table_ip6(0) + i.admin_down() + super(TestDHCP, self).tearDown() + + def verify_dhcp_has_option(self, pkt, option, value): + dhcp = pkt[DHCP] + found = False + + for i in dhcp.options: + if isinstance(i, tuple): + if i[0] == option: + self.assertEqual(i[1], value) + found = True + + self.assertTrue(found) + + def validate_relay_options(self, pkt, intf, ip_addr, vpn_id, fib_id, oui): + dhcp = pkt[DHCP] + found = 0 + data = [] + id_len = len(vpn_id) + + for i in dhcp.options: + if isinstance(i, tuple): + if i[0] == "relay_agent_Information": + # + # There are two sb-options present - each of length 6. + # + data = i[1] + if oui != 0: + self.assertEqual(len(data), 24) + elif len(vpn_id) > 0: + self.assertEqual(len(data), len(vpn_id) + 17) + else: + self.assertEqual(len(data), 12) + + # + # First sub-option is ID 1, len 4, then encoded + # sw_if_index. This test uses low valued indicies + # so [2:4] are 0. + # The ID space is VPP internal - so no matching value + # scapy + # + self.assertEqual(ord(data[0]), 1) + self.assertEqual(ord(data[1]), 4) + self.assertEqual(ord(data[2]), 0) + self.assertEqual(ord(data[3]), 0) + self.assertEqual(ord(data[4]), 0) + self.assertEqual(ord(data[5]), intf._sw_if_index) + + # + # next sub-option is the IP address of the client side + # interface. + # sub-option ID=5, length (of a v4 address)=4 + # + claddr = socket.inet_pton(AF_INET, ip_addr) + + self.assertEqual(ord(data[6]), 5) + self.assertEqual(ord(data[7]), 4) + self.assertEqual(data[8], claddr[0]) + self.assertEqual(data[9], claddr[1]) + self.assertEqual(data[10], claddr[2]) + self.assertEqual(data[11], claddr[3]) + + if oui != 0: + # sub-option 151 encodes vss_type 1, + # the 3 byte oui and the 4 byte fib_id + self.assertEqual(id_len, 0) + self.assertEqual(ord(data[12]), 151) + self.assertEqual(ord(data[13]), 8) + self.assertEqual(ord(data[14]), 1) + self.assertEqual(ord(data[15]), 0) + self.assertEqual(ord(data[16]), 0) + self.assertEqual(ord(data[17]), oui) + self.assertEqual(ord(data[18]), 0) + self.assertEqual(ord(data[19]), 0) + self.assertEqual(ord(data[20]), 0) + self.assertEqual(ord(data[21]), fib_id) + + # VSS control sub-option + self.assertEqual(ord(data[22]), 152) + self.assertEqual(ord(data[23]), 0) + + if id_len > 0: + # sub-option 151 encode vss_type of 0 + # followerd by vpn_id in ascii + self.assertEqual(oui, 0) + self.assertEqual(ord(data[12]), 151) + self.assertEqual(ord(data[13]), id_len + 1) + self.assertEqual(ord(data[14]), 0) + self.assertEqual(data[15:15 + id_len], vpn_id) + + # VSS control sub-option + self.assertEqual(ord(data[15 + len(vpn_id)]), 152) + self.assertEqual(ord(data[16 + len(vpn_id)]), 0) + + found = 1 + self.assertTrue(found) + + return data + + def verify_dhcp_msg_type(self, pkt, name): + dhcp = pkt[DHCP] + found = False + for o in dhcp.options: + if isinstance(o, tuple): + if o[0] == "message-type" \ + and DHCPTypes[o[1]] == name: + found = True + self.assertTrue(found) + + def verify_dhcp_offer(self, pkt, intf, vpn_id="", fib_id=0, oui=0): + ether = pkt[Ether] + self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff") + self.assertEqual(ether.src, intf.local_mac) + + ip = pkt[IP] + self.assertEqual(ip.dst, "255.255.255.255") + self.assertEqual(ip.src, intf.local_ip4) + + udp = pkt[UDP] + self.assertEqual(udp.dport, DHCP4_CLIENT_PORT) + self.assertEqual(udp.sport, DHCP4_SERVER_PORT) + + self.verify_dhcp_msg_type(pkt, "offer") + data = self.validate_relay_options(pkt, intf, intf.local_ip4, + vpn_id, fib_id, oui) + + def verify_orig_dhcp_pkt(self, pkt, intf, dscp, l2_bc=True): + ether = pkt[Ether] + if l2_bc: + self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff") + else: + self.assertEqual(ether.dst, intf.remote_mac) + self.assertEqual(ether.src, intf.local_mac) + + ip = pkt[IP] + + if (l2_bc): + self.assertEqual(ip.dst, "255.255.255.255") + self.assertEqual(ip.src, "0.0.0.0") + else: + self.assertEqual(ip.dst, intf.remote_ip4) + self.assertEqual(ip.src, intf.local_ip4) + self.assertEqual(ip.tos, dscp) + + udp = pkt[UDP] + self.assertEqual(udp.dport, DHCP4_SERVER_PORT) + self.assertEqual(udp.sport, DHCP4_CLIENT_PORT) + + def verify_orig_dhcp_discover(self, pkt, intf, hostname, client_id=None, + broadcast=True, dscp=0): + self.verify_orig_dhcp_pkt(pkt, intf, dscp) + + self.verify_dhcp_msg_type(pkt, "discover") + self.verify_dhcp_has_option(pkt, "hostname", hostname) + if client_id: + self.verify_dhcp_has_option(pkt, "client_id", client_id) + bootp = pkt[BOOTP] + self.assertEqual(bootp.ciaddr, "0.0.0.0") + self.assertEqual(bootp.giaddr, "0.0.0.0") + if broadcast: + self.assertEqual(bootp.flags, 0x8000) + else: + self.assertEqual(bootp.flags, 0x0000) + + def verify_orig_dhcp_request(self, pkt, intf, hostname, ip, + broadcast=True, + l2_bc=True, + dscp=0): + self.verify_orig_dhcp_pkt(pkt, intf, dscp, l2_bc=l2_bc) + + self.verify_dhcp_msg_type(pkt, "request") + self.verify_dhcp_has_option(pkt, "hostname", hostname) + self.verify_dhcp_has_option(pkt, "requested_addr", ip) + bootp = pkt[BOOTP] + + if l2_bc: + self.assertEqual(bootp.ciaddr, "0.0.0.0") + else: + self.assertEqual(bootp.ciaddr, intf.local_ip4) + self.assertEqual(bootp.giaddr, "0.0.0.0") + + if broadcast: + self.assertEqual(bootp.flags, 0x8000) + else: + self.assertEqual(bootp.flags, 0x0000) + + def verify_relayed_dhcp_discover(self, pkt, intf, src_intf=None, + fib_id=0, oui=0, + vpn_id="", + dst_mac=None, dst_ip=None): + if not dst_mac: + dst_mac = intf.remote_mac + if not dst_ip: + dst_ip = intf.remote_ip4 + + ether = pkt[Ether] + self.assertEqual(ether.dst, dst_mac) + self.assertEqual(ether.src, intf.local_mac) + + ip = pkt[IP] + self.assertEqual(ip.dst, dst_ip) + self.assertEqual(ip.src, intf.local_ip4) + + udp = pkt[UDP] + self.assertEqual(udp.dport, DHCP4_SERVER_PORT) + self.assertEqual(udp.sport, DHCP4_CLIENT_PORT) + + dhcp = pkt[DHCP] + + is_discover = False + for o in dhcp.options: + if isinstance(o, tuple): + if o[0] == "message-type" \ + and DHCPTypes[o[1]] == "discover": + is_discover = True + self.assertTrue(is_discover) + + data = self.validate_relay_options(pkt, src_intf, + src_intf.local_ip4, + vpn_id, + fib_id, oui) + return data + + def verify_dhcp6_solicit(self, pkt, intf, + peer_ip, peer_mac, + vpn_id="", + fib_id=0, + oui=0, + dst_mac=None, + dst_ip=None): + if not dst_mac: + dst_mac = intf.remote_mac + if not dst_ip: + dst_ip = in6_ptop(intf.remote_ip6) + + ether = pkt[Ether] + self.assertEqual(ether.dst, dst_mac) + self.assertEqual(ether.src, intf.local_mac) + + ip = pkt[IPv6] + self.assertEqual(in6_ptop(ip.dst), dst_ip) + self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6)) + + udp = pkt[UDP] + self.assertEqual(udp.dport, DHCP6_CLIENT_PORT) + self.assertEqual(udp.sport, DHCP6_SERVER_PORT) + + relay = pkt[DHCP6_RelayForward] + self.assertEqual(in6_ptop(relay.peeraddr), in6_ptop(peer_ip)) + oid = pkt[DHCP6OptIfaceId] + cll = pkt[DHCP6OptClientLinkLayerAddr] + self.assertEqual(cll.optlen, 8) + self.assertEqual(cll.lltype, 1) + self.assertEqual(cll.clladdr, peer_mac) + + id_len = len(vpn_id) + + if fib_id != 0: + self.assertEqual(id_len, 0) + vss = pkt[DHCP6OptVSS] + self.assertEqual(vss.optlen, 8) + self.assertEqual(vss.type, 1) + # the OUI and FIB-id are really 3 and 4 bytes resp. + # but the tested range is small + self.assertEqual(ord(vss.data[0]), 0) + self.assertEqual(ord(vss.data[1]), 0) + self.assertEqual(ord(vss.data[2]), oui) + self.assertEqual(ord(vss.data[3]), 0) + self.assertEqual(ord(vss.data[4]), 0) + self.assertEqual(ord(vss.data[5]), 0) + self.assertEqual(ord(vss.data[6]), fib_id) + + if id_len > 0: + self.assertEqual(oui, 0) + vss = pkt[DHCP6OptVSS] + self.assertEqual(vss.optlen, id_len + 1) + self.assertEqual(vss.type, 0) + self.assertEqual(vss.data[0:id_len], vpn_id) + + # the relay message should be an encoded Solicit + msg = pkt[DHCP6OptRelayMsg] + sol = DHCP6_Solicit() + self.assertEqual(msg.optlen, len(str(sol))) + self.assertEqual(str(sol), (str(msg[1]))[:msg.optlen]) + + def verify_dhcp6_advert(self, pkt, intf, peer): + ether = pkt[Ether] + self.assertEqual(ether.dst, "ff:ff:ff:ff:ff:ff") + self.assertEqual(ether.src, intf.local_mac) + + ip = pkt[IPv6] + self.assertEqual(in6_ptop(ip.dst), in6_ptop(peer)) + self.assertEqual(in6_ptop(ip.src), in6_ptop(intf.local_ip6)) + + udp = pkt[UDP] + self.assertEqual(udp.dport, DHCP6_SERVER_PORT) + self.assertEqual(udp.sport, DHCP6_CLIENT_PORT) + + # not sure why this is not decoding + # adv = pkt[DHCP6_Advertise] + + def wait_for_no_route(self, address, length, + n_tries=50, s_time=1): + while (n_tries): + if not find_route(self, address, length): + return True + n_tries = n_tries - 1 + self.sleep(s_time) + + return False + + def test_dhcp_proxy(self): + """ DHCPv4 Proxy """ + + # + # Verify no response to DHCP request without DHCP config + # + p_disc_vrf0 = (Ether(dst="ff:ff:ff:ff:ff:ff", + src=self.pg3.remote_mac) / + IP(src="0.0.0.0", dst="255.255.255.255") / + UDP(sport=DHCP4_CLIENT_PORT, + dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'discover'), ('end')])) + pkts_disc_vrf0 = [p_disc_vrf0] + p_disc_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff", + src=self.pg4.remote_mac) / + IP(src="0.0.0.0", dst="255.255.255.255") / + UDP(sport=DHCP4_CLIENT_PORT, + dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'discover'), ('end')])) + pkts_disc_vrf1 = [p_disc_vrf1] + p_disc_vrf2 = (Ether(dst="ff:ff:ff:ff:ff:ff", + src=self.pg5.remote_mac) / + IP(src="0.0.0.0", dst="255.255.255.255") / + UDP(sport=DHCP4_CLIENT_PORT, + dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'discover'), ('end')])) + pkts_disc_vrf2 = [p_disc_vrf2] + + self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0, + "DHCP with no configuration") + self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, + "DHCP with no configuration") + self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2, + "DHCP with no configuration") + + # + # Enable DHCP proxy in VRF 0 + # + server_addr = self.pg0.remote_ip4 + src_addr = self.pg0.local_ip4 + + Proxy = VppDHCPProxy(self, server_addr, src_addr, rx_vrf_id=0) + Proxy.add_vpp_config() + + # + # Discover packets from the client are dropped because there is no + # IP address configured on the client facing interface + # + self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0, + "Discover DHCP no relay address") + + # + # Inject a response from the server + # dropped, because there is no IP addrees on the + # client interfce to fill in the option. + # + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), ('end')])) + pkts = [p] + + self.send_and_assert_no_replies(self.pg3, pkts, + "Offer DHCP no relay address") + + # + # configure an IP address on the client facing interface + # + self.pg3.config_ip4() + + # + # Try again with a discover packet + # Rx'd packet should be to the server address and from the configured + # source address + # UDP source ports are unchanged + # we've no option 82 config so that should be absent + # + self.pg3.add_stream(pkts_disc_vrf0) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture(1) + rx = rx[0] + + option_82 = self.verify_relayed_dhcp_discover(rx, self.pg0, + src_intf=self.pg3) + + # + # Create an DHCP offer reply from the server with a correctly formatted + # option 82. i.e. send back what we just captured + # The offer, sent mcast to the client, still has option 82. + # + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), + ('relay_agent_Information', option_82), + ('end')])) + pkts = [p] + + self.pg0.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + rx = rx[0] + + self.verify_dhcp_offer(rx, self.pg3) + + # + # Bogus Option 82: + # + # 1. not our IP address = not checked by VPP? so offer is replayed + # to client + bad_ip = option_82[0:8] + scapy.compat.chb(33) + option_82[9:] + + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), + ('relay_agent_Information', bad_ip), + ('end')])) + pkts = [p] + self.send_and_assert_no_replies(self.pg0, pkts, + "DHCP offer option 82 bad address") + + # 2. Not a sw_if_index VPP knows + bad_if_index = option_82[0:2] + scapy.compat.chb(33) + option_82[3:] + + p = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IP(src=self.pg0.remote_ip4, dst=self.pg0.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), + ('relay_agent_Information', bad_if_index), + ('end')])) + pkts = [p] + self.send_and_assert_no_replies(self.pg0, pkts, + "DHCP offer option 82 bad if index") + + # + # Send a DHCP request in VRF 1. should be dropped. + # + self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, + "DHCP with no configuration VRF 1") + + # + # Delete the DHCP config in VRF 0 + # Should now drop requests. + # + Proxy.remove_vpp_config() + + self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0, + "DHCP config removed VRF 0") + self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, + "DHCP config removed VRF 1") + + # + # Add DHCP config for VRF 1 & 2 + # + server_addr1 = self.pg1.remote_ip4 + src_addr1 = self.pg1.local_ip4 + Proxy1 = VppDHCPProxy( + self, + server_addr1, + src_addr1, + rx_vrf_id=1, + server_vrf_id=1) + Proxy1.add_vpp_config() + + server_addr2 = self.pg2.remote_ip4 + src_addr2 = self.pg2.local_ip4 + Proxy2 = VppDHCPProxy( + self, + server_addr2, + src_addr2, + rx_vrf_id=2, + server_vrf_id=2) + Proxy2.add_vpp_config() + + # + # Confim DHCP requests ok in VRF 1 & 2. + # - dropped on IP config on client interface + # + self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, + "DHCP config removed VRF 1") + self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2, + "DHCP config removed VRF 2") + + # + # configure an IP address on the client facing interface + # + self.pg4.config_ip4() + self.pg4.add_stream(pkts_disc_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = self.pg1.get_capture(1) + rx = rx[0] + self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4) + + self.pg5.config_ip4() + self.pg5.add_stream(pkts_disc_vrf2) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + rx = self.pg2.get_capture(1) + rx = rx[0] + self.verify_relayed_dhcp_discover(rx, self.pg2, src_intf=self.pg5) + + # + # Add VSS config + # table=1, vss_type=1, vpn_index=1, oui=4 + # table=2, vss_type=0, vpn_id = "ip4-table-2" + self.vapi.dhcp_proxy_set_vss(tbl_id=1, vss_type=1, + vpn_index=1, oui=4, is_add=1) + self.vapi.dhcp_proxy_set_vss(tbl_id=2, vss_type=0, + vpn_ascii_id="ip4-table-2", is_add=1) + + self.pg4.add_stream(pkts_disc_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + rx = rx[0] + self.verify_relayed_dhcp_discover(rx, self.pg1, + src_intf=self.pg4, + fib_id=1, oui=4) + + self.pg5.add_stream(pkts_disc_vrf2) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg2.get_capture(1) + rx = rx[0] + self.verify_relayed_dhcp_discover(rx, self.pg2, + src_intf=self.pg5, + vpn_id="ip4-table-2") + + # + # Add a second DHCP server in VRF 1 + # expect clients messages to be relay to both configured servers + # + self.pg1.generate_remote_hosts(2) + server_addr12 = self.pg1.remote_hosts[1].ip4 + + Proxy12 = VppDHCPProxy( + self, + server_addr12, + src_addr, + rx_vrf_id=1, + server_vrf_id=1) + Proxy12.add_vpp_config() + + # + # We'll need an ARP entry for the server to send it packets + # + arp_entry = VppNeighbor(self, + self.pg1.sw_if_index, + self.pg1.remote_hosts[1].mac, + self.pg1.remote_hosts[1].ip4) + arp_entry.add_vpp_config() + + # + # Send a discover from the client. expect two relayed messages + # The frist packet is sent to the second server + # We're not enforcing that here, it's just the way it is. + # + self.pg4.add_stream(pkts_disc_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(2) + + option_82 = self.verify_relayed_dhcp_discover( + rx[0], self.pg1, + src_intf=self.pg4, + dst_mac=self.pg1.remote_hosts[1].mac, + dst_ip=self.pg1.remote_hosts[1].ip4, + fib_id=1, oui=4) + self.verify_relayed_dhcp_discover(rx[1], self.pg1, + src_intf=self.pg4, + fib_id=1, oui=4) + + # + # Send both packets back. Client gets both. + # + p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / + IP(src=self.pg1.remote_ip4, dst=self.pg1.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), + ('relay_agent_Information', option_82), + ('end')])) + p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / + IP(src=self.pg1.remote_hosts[1].ip4, dst=self.pg1.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), + ('relay_agent_Information', option_82), + ('end')])) + pkts = [p1, p2] + + self.pg1.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg4.get_capture(2) + + self.verify_dhcp_offer(rx[0], self.pg4, fib_id=1, oui=4) + self.verify_dhcp_offer(rx[1], self.pg4, fib_id=1, oui=4) + + # + # Ensure offers from non-servers are dropeed + # + p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / + IP(src="8.8.8.8", dst=self.pg1.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'offer'), + ('relay_agent_Information', option_82), + ('end')])) + self.send_and_assert_no_replies(self.pg1, p2, + "DHCP offer from non-server") + + # + # Ensure only the discover is sent to multiple servers + # + p_req_vrf1 = (Ether(dst="ff:ff:ff:ff:ff:ff", + src=self.pg4.remote_mac) / + IP(src="0.0.0.0", dst="255.255.255.255") / + UDP(sport=DHCP4_CLIENT_PORT, + dport=DHCP4_SERVER_PORT) / + BOOTP(op=1) / + DHCP(options=[('message-type', 'request'), + ('end')])) + + self.pg4.add_stream(p_req_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + + # + # Remove the second DHCP server + # + Proxy12.remove_vpp_config() + + # + # Test we can still relay with the first + # + self.pg4.add_stream(pkts_disc_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + rx = rx[0] + self.verify_relayed_dhcp_discover(rx, self.pg1, + src_intf=self.pg4, + fib_id=1, oui=4) + + # + # Remove the VSS config + # relayed DHCP has default vlaues in the option. + # + self.vapi.dhcp_proxy_set_vss(tbl_id=1, is_add=0) + self.vapi.dhcp_proxy_set_vss(tbl_id=2, is_add=0) + + self.pg4.add_stream(pkts_disc_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + rx = rx[0] + self.verify_relayed_dhcp_discover(rx, self.pg1, src_intf=self.pg4) + + # + # remove DHCP config to cleanup + # + Proxy1.remove_vpp_config() + Proxy2.remove_vpp_config() + + self.send_and_assert_no_replies(self.pg3, pkts_disc_vrf0, + "DHCP cleanup VRF 0") + self.send_and_assert_no_replies(self.pg4, pkts_disc_vrf1, + "DHCP cleanup VRF 1") + self.send_and_assert_no_replies(self.pg5, pkts_disc_vrf2, + "DHCP cleanup VRF 2") + + self.pg3.unconfig_ip4() + self.pg4.unconfig_ip4() + self.pg5.unconfig_ip4() + + def test_dhcp6_proxy(self): + """ DHCPv6 Proxy""" + # + # Verify no response to DHCP request without DHCP config + # + dhcp_solicit_dst = "ff02::1:2" + dhcp_solicit_src_vrf0 = mk_ll_addr(self.pg3.remote_mac) + dhcp_solicit_src_vrf1 = mk_ll_addr(self.pg4.remote_mac) + dhcp_solicit_src_vrf2 = mk_ll_addr(self.pg5.remote_mac) + server_addr_vrf0 = self.pg0.remote_ip6 + src_addr_vrf0 = self.pg0.local_ip6 + server_addr_vrf1 = self.pg1.remote_ip6 + src_addr_vrf1 = self.pg1.local_ip6 + server_addr_vrf2 = self.pg2.remote_ip6 + src_addr_vrf2 = self.pg2.local_ip6 + + dmac = in6_getnsmac(inet_pton(socket.AF_INET6, dhcp_solicit_dst)) + p_solicit_vrf0 = (Ether(dst=dmac, src=self.pg3.remote_mac) / + IPv6(src=dhcp_solicit_src_vrf0, + dst=dhcp_solicit_dst) / + UDP(sport=DHCP6_SERVER_PORT, + dport=DHCP6_CLIENT_PORT) / + DHCP6_Solicit()) + p_solicit_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) / + IPv6(src=dhcp_solicit_src_vrf1, + dst=dhcp_solicit_dst) / + UDP(sport=DHCP6_SERVER_PORT, + dport=DHCP6_CLIENT_PORT) / + DHCP6_Solicit()) + p_solicit_vrf2 = (Ether(dst=dmac, src=self.pg5.remote_mac) / + IPv6(src=dhcp_solicit_src_vrf2, + dst=dhcp_solicit_dst) / + UDP(sport=DHCP6_SERVER_PORT, + dport=DHCP6_CLIENT_PORT) / + DHCP6_Solicit()) + + self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0, + "DHCP with no configuration") + self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1, + "DHCP with no configuration") + self.send_and_assert_no_replies(self.pg5, p_solicit_vrf2, + "DHCP with no configuration") + + # + # DHCPv6 config in VRF 0. + # Packets still dropped because the client facing interface has no + # IPv6 config + # + Proxy = VppDHCPProxy( + self, + server_addr_vrf0, + src_addr_vrf0, + rx_vrf_id=0, + server_vrf_id=0) + Proxy.add_vpp_config() + + self.send_and_assert_no_replies(self.pg3, p_solicit_vrf0, + "DHCP with no configuration") + self.send_and_assert_no_replies(self.pg4, p_solicit_vrf1, + "DHCP with no configuration") + + # + # configure an IP address on the client facing interface + # + self.pg3.config_ip6() + + # + # Now the DHCP requests are relayed to the server + # + self.pg3.add_stream(p_solicit_vrf0) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg0.get_capture(1) + + self.verify_dhcp6_solicit(rx[0], self.pg0, + dhcp_solicit_src_vrf0, + self.pg3.remote_mac) + + # + # Exception cases for rejected relay responses + # + + # 1 - not a relay reply + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_Advertise()) + self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, + "DHCP6 not a relay reply") + + # 2 - no relay message option + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply() / + DHCP6_Advertise()) + self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, + "DHCP not a relay message") + + # 3 - no circuit ID + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply() / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise()) + self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, + "DHCP6 no circuit ID") + # 4 - wrong circuit ID + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply() / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise()) + self.send_and_assert_no_replies(self.pg3, p_adv_vrf0, + "DHCP6 wrong circuit ID") + + # + # Send the relay response (the advertisement) + # - no peer address + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply() / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise(trid=1) / + DHCP6OptStatusCode(statuscode=0)) + pkts_adv_vrf0 = [p_adv_vrf0] + + self.pg0.add_stream(pkts_adv_vrf0) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + + self.verify_dhcp6_advert(rx[0], self.pg3, "::") + + # + # Send the relay response (the advertisement) + # - with peer address + p_adv_vrf0 = (Ether(dst=self.pg0.local_mac, src=self.pg0.remote_mac) / + IPv6(dst=self.pg0.local_ip6, src=self.pg0.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf0) / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x04') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise(trid=1) / + DHCP6OptStatusCode(statuscode=0)) + pkts_adv_vrf0 = [p_adv_vrf0] + + self.pg0.add_stream(pkts_adv_vrf0) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + + self.verify_dhcp6_advert(rx[0], self.pg3, dhcp_solicit_src_vrf0) + + # + # Add all the config for VRF 1 & 2 + # + Proxy1 = VppDHCPProxy( + self, + server_addr_vrf1, + src_addr_vrf1, + rx_vrf_id=1, + server_vrf_id=1) + Proxy1.add_vpp_config() + self.pg4.config_ip6() + + Proxy2 = VppDHCPProxy( + self, + server_addr_vrf2, + src_addr_vrf2, + rx_vrf_id=2, + server_vrf_id=2) + Proxy2.add_vpp_config() + self.pg5.config_ip6() + + # + # VRF 1 solicit + # + self.pg4.add_stream(p_solicit_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + + self.verify_dhcp6_solicit(rx[0], self.pg1, + dhcp_solicit_src_vrf1, + self.pg4.remote_mac) + + # + # VRF 2 solicit + # + self.pg5.add_stream(p_solicit_vrf2) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg2.get_capture(1) + + self.verify_dhcp6_solicit(rx[0], self.pg2, + dhcp_solicit_src_vrf2, + self.pg5.remote_mac) + + # + # VRF 1 Advert + # + p_adv_vrf1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / + IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise(trid=1) / + DHCP6OptStatusCode(statuscode=0)) + pkts_adv_vrf1 = [p_adv_vrf1] + + self.pg1.add_stream(pkts_adv_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg4.get_capture(1) + + self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1) + + # + # Add VSS config + # + self.vapi.dhcp_proxy_set_vss( + tbl_id=1, vss_type=1, oui=4, vpn_index=1, is_ipv6=1, is_add=1) + self.vapi.dhcp_proxy_set_vss( + tbl_id=2, + vss_type=0, + vpn_ascii_id="IPv6-table-2", + is_ipv6=1, + is_add=1) + + self.pg4.add_stream(p_solicit_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + + self.verify_dhcp6_solicit(rx[0], self.pg1, + dhcp_solicit_src_vrf1, + self.pg4.remote_mac, + fib_id=1, + oui=4) + + self.pg5.add_stream(p_solicit_vrf2) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg2.get_capture(1) + + self.verify_dhcp6_solicit(rx[0], self.pg2, + dhcp_solicit_src_vrf2, + self.pg5.remote_mac, + vpn_id="IPv6-table-2") + + # + # Remove the VSS config + # relayed DHCP has default vlaues in the option. + # + self.vapi.dhcp_proxy_set_vss(tbl_id=1, is_ipv6=1, is_add=0) + + self.pg4.add_stream(p_solicit_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + + self.verify_dhcp6_solicit(rx[0], self.pg1, + dhcp_solicit_src_vrf1, + self.pg4.remote_mac) + + # + # Add a second DHCP server in VRF 1 + # expect clients messages to be relay to both configured servers + # + self.pg1.generate_remote_hosts(2) + server_addr12 = self.pg1.remote_hosts[1].ip6 + + Proxy12 = VppDHCPProxy( + self, + server_addr12, + src_addr_vrf1, + rx_vrf_id=1, + server_vrf_id=1) + Proxy12.add_vpp_config() + + # + # We'll need an ND entry for the server to send it packets + # + nd_entry = VppNeighbor(self, + self.pg1.sw_if_index, + self.pg1.remote_hosts[1].mac, + self.pg1.remote_hosts[1].ip6) + nd_entry.add_vpp_config() + + # + # Send a discover from the client. expect two relayed messages + # The frist packet is sent to the second server + # We're not enforcing that here, it's just the way it is. + # + self.pg4.add_stream(p_solicit_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(2) + + self.verify_dhcp6_solicit(rx[0], self.pg1, + dhcp_solicit_src_vrf1, + self.pg4.remote_mac) + self.verify_dhcp6_solicit(rx[1], self.pg1, + dhcp_solicit_src_vrf1, + self.pg4.remote_mac, + dst_mac=self.pg1.remote_hosts[1].mac, + dst_ip=self.pg1.remote_hosts[1].ip6) + + # + # Send both packets back. Client gets both. + # + p1 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_mac) / + IPv6(dst=self.pg1.local_ip6, src=self.pg1.remote_ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise(trid=1) / + DHCP6OptStatusCode(statuscode=0)) + p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) / + IPv6(dst=self.pg1.local_ip6, src=self.pg1._remote_hosts[1].ip6) / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise(trid=1) / + DHCP6OptStatusCode(statuscode=0)) + + pkts = [p1, p2] + + self.pg1.add_stream(pkts) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg4.get_capture(2) + + self.verify_dhcp6_advert(rx[0], self.pg4, dhcp_solicit_src_vrf1) + self.verify_dhcp6_advert(rx[1], self.pg4, dhcp_solicit_src_vrf1) + + # + # Ensure only solicit messages are duplicated + # + p_request_vrf1 = (Ether(dst=dmac, src=self.pg4.remote_mac) / + IPv6(src=dhcp_solicit_src_vrf1, + dst=dhcp_solicit_dst) / + UDP(sport=DHCP6_SERVER_PORT, + dport=DHCP6_CLIENT_PORT) / + DHCP6_Request()) + + self.pg4.add_stream(p_request_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + + # + # Test we drop DHCP packets from addresses that are not configured as + # DHCP servers + # + p2 = (Ether(dst=self.pg1.local_mac, src=self.pg1.remote_hosts[1].mac) / + IPv6(dst=self.pg1.local_ip6, src="3001::1") / + UDP(sport=DHCP6_SERVER_PORT, dport=DHCP6_SERVER_PORT) / + DHCP6_RelayReply(peeraddr=dhcp_solicit_src_vrf1) / + DHCP6OptIfaceId(optlen=4, ifaceid='\x00\x00\x00\x05') / + DHCP6OptRelayMsg(optlen=0) / + DHCP6_Advertise(trid=1) / + DHCP6OptStatusCode(statuscode=0)) + self.send_and_assert_no_replies(self.pg1, p2, + "DHCP6 not from server") + + # + # Remove the second DHCP server + # + Proxy12.remove_vpp_config() + + # + # Test we can still relay with the first + # + self.pg4.add_stream(p_solicit_vrf1) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg1.get_capture(1) + + self.verify_dhcp6_solicit(rx[0], self.pg1, + dhcp_solicit_src_vrf1, + self.pg4.remote_mac) + + # + # Cleanup + # + Proxy.remove_vpp_config() + Proxy1.remove_vpp_config() + Proxy2.remove_vpp_config() + + self.pg3.unconfig_ip6() + self.pg4.unconfig_ip6() + self.pg5.unconfig_ip6() + + def test_dhcp_client(self): + """ DHCP Client""" + + vdscp = VppEnum.vl_api_ip_dscp_t + hostname = 'universal-dp' + + self.pg_enable_capture(self.pg_interfaces) + + # + # Configure DHCP client on PG3 and capture the discover sent + # + Client = VppDHCPClient(self, self.pg3.sw_if_index, hostname) + Client.add_vpp_config() + self.assertTrue(Client.query_vpp_config()) + + rx = self.pg3.get_capture(1) + + self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname) + + # + # Send back on offer, expect the request + # + p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst="255.255.255.255") / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, + yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'offer'), + ('server_id', self.pg3.remote_ip4), + 'end'])) + + self.pg3.add_stream(p_offer) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, + self.pg3.local_ip4) + + # + # Send an acknowledgment + # + p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst="255.255.255.255") / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'ack'), + ('subnet_mask', "255.255.255.0"), + ('router', self.pg3.remote_ip4), + ('server_id', self.pg3.remote_ip4), + ('lease_time', 43200), + 'end'])) + + self.pg3.add_stream(p_ack) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # + # We'll get an ARP request for the router address + # + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) + self.pg_enable_capture(self.pg_interfaces) + + # + # At the end of this procedure there should be a connected route + # in the FIB + # + self.assertTrue(find_route(self, self.pg3.local_ip4, 24)) + self.assertTrue(find_route(self, self.pg3.local_ip4, 32)) + + # remove the left over ARP entry + self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index, + self.pg3.remote_mac, + self.pg3.remote_ip4, + is_add=0) + + # + # remove the DHCP config + # + Client.remove_vpp_config() + + # + # and now the route should be gone + # + self.assertFalse(find_route(self, self.pg3.local_ip4, 32)) + self.assertFalse(find_route(self, self.pg3.local_ip4, 24)) + + # + # Start the procedure again. this time have VPP send the client-ID + # and set the DSCP value + # + self.pg3.admin_down() + self.sleep(1) + self.pg3.admin_up() + Client.set_client(self.pg3.sw_if_index, hostname, + id=self.pg3.local_mac, + dscp=vdscp.IP_API_DSCP_EF) + Client.add_vpp_config() + + rx = self.pg3.get_capture(1) + + self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname, + self.pg3.local_mac, + dscp=vdscp.IP_API_DSCP_EF) + + # TODO: VPP DHCP client should not accept DHCP OFFER message with + # the XID (Transaction ID) not matching the XID of the most recent + # DHCP DISCOVERY message. + # Such DHCP OFFER message must be silently discarded - RFC2131. + # Reported in Jira ticket: VPP-99 + self.pg3.add_stream(p_offer) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, + self.pg3.local_ip4, + dscp=vdscp.IP_API_DSCP_EF) + + # + # unicast the ack to the offered address + # + p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'ack'), + ('subnet_mask', "255.255.255.0"), + ('router', self.pg3.remote_ip4), + ('server_id', self.pg3.remote_ip4), + ('lease_time', 43200), + 'end'])) + + self.pg3.add_stream(p_ack) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # + # We'll get an ARP request for the router address + # + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) + self.pg_enable_capture(self.pg_interfaces) + + # + # At the end of this procedure there should be a connected route + # in the FIB + # + self.assertTrue(find_route(self, self.pg3.local_ip4, 32)) + self.assertTrue(find_route(self, self.pg3.local_ip4, 24)) + + # + # remove the DHCP config + # + Client.remove_vpp_config() + + self.assertFalse(find_route(self, self.pg3.local_ip4, 32)) + self.assertFalse(find_route(self, self.pg3.local_ip4, 24)) + + # + # Rince and repeat, this time with VPP configured not to set + # the braodcast flag in the discover and request messages, + # and for the server to unicast the responses. + # + # Configure DHCP client on PG3 and capture the discover sent + # + Client.set_client( + self.pg3.sw_if_index, + hostname, + set_broadcast_flag=False) + Client.add_vpp_config() + + rx = self.pg3.get_capture(1) + + self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname, + broadcast=False) + + # + # Send back on offer, unicasted to the offered address. + # Expect the request. + # + p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'offer'), + ('server_id', self.pg3.remote_ip4), + 'end'])) + + self.pg3.add_stream(p_offer) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, + self.pg3.local_ip4, + broadcast=False) + + # + # Send an acknowledgment, the lease renewal time is 2 seconds + # so we should expect the renew straight after + # + p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst=self.pg3.local_ip4) / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'ack'), + ('subnet_mask', "255.255.255.0"), + ('router', self.pg3.remote_ip4), + ('server_id', self.pg3.remote_ip4), + ('lease_time', 43200), + ('renewal_time', 2), + 'end'])) + + self.pg3.add_stream(p_ack) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # + # We'll get an ARP request for the router address + # + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) + self.pg_enable_capture(self.pg_interfaces) + + # + # At the end of this procedure there should be a connected route + # in the FIB + # + self.assertTrue(find_route(self, self.pg3.local_ip4, 24)) + self.assertTrue(find_route(self, self.pg3.local_ip4, 32)) + + # + # wait for the unicasted renewal + # the first attempt will be an ARP packet, since we have not yet + # responded to VPP's request + # + self.logger.info(self.vapi.cli("sh dhcp client intfc pg3 verbose")) + rx = self.pg3.get_capture(1, timeout=10) + + self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) + + # respond to the arp + p_arp = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + ARP(op="is-at", + hwdst=self.pg3.local_mac, + hwsrc=self.pg3.remote_mac, + pdst=self.pg3.local_ip4, + psrc=self.pg3.remote_ip4)) + self.pg3.add_stream(p_arp) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # the next packet is the unicasted renewal + rx = self.pg3.get_capture(1, timeout=10) + self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, + self.pg3.local_ip4, + l2_bc=False, + broadcast=False) + + # + # read the DHCP client details from a dump + # + clients = self.vapi.dhcp_client_dump() + + self.assertEqual(clients[0].client.sw_if_index, + self.pg3.sw_if_index) + self.assertEqual(clients[0].lease.sw_if_index, + self.pg3.sw_if_index) + self.assertEqual(clients[0].client.hostname.rstrip('\0'), + hostname) + self.assertEqual(clients[0].lease.hostname.rstrip('\0'), + hostname) + # 0 = DISCOVER, 1 = REQUEST, 2 = BOUND + self.assertEqual(clients[0].lease.state, 2) + self.assertEqual(clients[0].lease.mask_width, 24) + self.assertEqual(str(clients[0].lease.router_address), + self.pg3.remote_ip4) + self.assertEqual(str(clients[0].lease.host_address), + self.pg3.local_ip4) + + # remove the left over ARP entry + self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index, + self.pg3.remote_mac, + self.pg3.remote_ip4, + is_add=0) + + # + # remove the DHCP config + # + Client.remove_vpp_config() + + # + # and now the route should be gone + # + self.assertFalse(find_route(self, self.pg3.local_ip4, 32)) + self.assertFalse(find_route(self, self.pg3.local_ip4, 24)) + + # + # Start the procedure again. Use requested lease time option. + # + hostname += "-2" + self.pg3.admin_down() + self.sleep(1) + self.pg3.admin_up() + self.pg_enable_capture(self.pg_interfaces) + Client.set_client(self.pg3.sw_if_index, hostname) + Client.add_vpp_config() + + rx = self.pg3.get_capture(1) + + self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname) + + # + # Send back on offer with requested lease time, expect the request + # + lease_time = 1 + p_offer = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst='255.255.255.255') / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, + yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'offer'), + ('server_id', self.pg3.remote_ip4), + ('lease_time', lease_time), + 'end'])) + + self.pg3.add_stream(p_offer) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + rx = self.pg3.get_capture(1) + self.verify_orig_dhcp_request(rx[0], self.pg3, hostname, + self.pg3.local_ip4) + + # + # Send an acknowledgment + # + p_ack = (Ether(dst=self.pg3.local_mac, src=self.pg3.remote_mac) / + IP(src=self.pg3.remote_ip4, dst='255.255.255.255') / + UDP(sport=DHCP4_SERVER_PORT, dport=DHCP4_CLIENT_PORT) / + BOOTP(op=1, yiaddr=self.pg3.local_ip4, + chaddr=mac_pton(self.pg3.local_mac)) / + DHCP(options=[('message-type', 'ack'), + ('subnet_mask', '255.255.255.0'), + ('router', self.pg3.remote_ip4), + ('server_id', self.pg3.remote_ip4), + ('lease_time', lease_time), + 'end'])) + + self.pg3.add_stream(p_ack) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + # + # We'll get an ARP request for the router address + # + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][ARP].pdst, self.pg3.remote_ip4) + + # + # At the end of this procedure there should be a connected route + # in the FIB + # + self.assertTrue(find_route(self, self.pg3.local_ip4, 32)) + self.assertTrue(find_route(self, self.pg3.local_ip4, 24)) + + # remove the left over ARP entry + self.vapi.ip_neighbor_add_del(self.pg3.sw_if_index, + self.pg3.remote_mac, + self.pg3.remote_ip4, + is_add=0) + + # + # the route should be gone after the lease expires + # + self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 32)) + self.assertTrue(self.wait_for_no_route(self.pg3.local_ip4, 24)) + + # + # remove the DHCP config + # + Client.remove_vpp_config() + + def test_dhcp_client_vlan(self): + """ DHCP Client w/ VLAN""" + + vdscp = VppEnum.vl_api_ip_dscp_t + vqos = VppEnum.vl_api_qos_source_t + hostname = 'universal-dp' + + self.pg_enable_capture(self.pg_interfaces) + + vlan_100 = VppDot1QSubint(self, self.pg3, 100) + vlan_100.admin_up() + + output = [scapy.compat.chb(4)] * 256 + os = b''.join(output) + rows = [{'outputs': os}, + {'outputs': os}, + {'outputs': os}, + {'outputs': os}] + + qem1 = VppQosEgressMap(self, 1, rows).add_vpp_config() + qm1 = VppQosMark(self, vlan_100, qem1, + vqos.QOS_API_SOURCE_VLAN).add_vpp_config() + + # + # Configure DHCP client on PG3 and capture the discover sent + # + Client = VppDHCPClient( + self, + vlan_100.sw_if_index, + hostname, + dscp=vdscp.IP_API_DSCP_EF) + Client.add_vpp_config() + + rx = self.pg3.get_capture(1) + + self.assertEqual(rx[0][Dot1Q].vlan, 100) + self.assertEqual(rx[0][Dot1Q].prio, 2) + + self.verify_orig_dhcp_discover(rx[0], self.pg3, hostname, + dscp=vdscp.IP_API_DSCP_EF) + + +if __name__ == '__main__': + unittest.main(testRunner=VppTestRunner) diff --git a/src/plugins/dhcp/test/test_dhcp6.py b/src/plugins/dhcp/test/test_dhcp6.py new file mode 100644 index 00000000000..02f420243cb --- /dev/null +++ b/src/plugins/dhcp/test/test_dhcp6.py @@ -0,0 +1,806 @@ +from socket import AF_INET6 + +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_LL, DHCP6_Release, DHCP6OptElapsedTime, DHCP6OptIA_NA, \ + DHCP6OptIAAddress +from scapy.layers.inet6 import IPv6, Ether, UDP +from scapy.utils6 import in6_mactoifaceid +from scapy.utils import inet_ntop, inet_pton + +from framework import VppTestCase +from vpp_papi import VppEnum +import util +import os + + +def ip6_normalize(ip6): + return inet_ntop(AF_INET6, inet_pton(AF_INET6, ip6)) + + +class TestDHCPv6DataPlane(VppTestCase): + """ DHCPv6 Data Plane Test Case """ + + @classmethod + def setUpClass(cls): + super(TestDHCPv6DataPlane, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestDHCPv6DataPlane, cls).tearDownClass() + + def setUp(self): + super(TestDHCPv6DataPlane, 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() + + self.server_duid = DUID_LL(lladdr=self.pg0.remote_mac) + + def tearDown(self): + for i in self.interfaces: + i.unconfig_ip6() + i.admin_down() + super(TestDHCPv6DataPlane, self).tearDown() + + def test_dhcp_ia_na_send_solicit_receive_advertise(self): + """ Verify DHCPv6 IA NA Solicit packet and Advertise event """ + + self.vapi.dhcp6_clients_enable_disable(enable=1) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + address = {'address': '1:2:3::5', + 'preferred_time': 60, + 'valid_time': 120} + self.vapi.dhcp6_send_client_message( + server_index=0xffffffff, + mrc=1, + msg_type=VppEnum.vl_api_dhcpv6_msg_type_t.DHCPV6_MSG_API_SOLICIT, + sw_if_index=self.pg0.sw_if_index, + T1=20, + T2=40, + addresses=[address], + n_addresses=len( + [address])) + rx_list = self.pg0.get_capture(1) + self.assertEqual(len(rx_list), 1) + packet = rx_list[0] + + self.assertEqual(packet.haslayer(IPv6), 1) + self.assertEqual(packet[IPv6].haslayer(DHCP6_Solicit), 1) + + 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_na = packet[DHCP6OptIA_NA] + self.assert_equal(ia_na.T1, 20) + self.assert_equal(ia_na.T2, 40) + self.assert_equal(len(ia_na.ianaopts), 1) + address = ia_na.ianaopts[0] + self.assert_equal(address.addr, '1:2:3::5') + self.assert_equal(address.preflft, 60) + self.assert_equal(address.validlft, 120) + + self.vapi.want_dhcp6_reply_events(enable_disable=1, + pid=os.getpid()) + + try: + ia_na_opts = DHCP6OptIAAddress(addr='7:8::2', preflft=60, + validlft=120) + p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / + IPv6(src=util.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_NA(iaid=1, T1=20, T2=40, ianaopts=ia_na_opts) + ) + self.pg0.add_stream([p]) + self.pg_start() + + ev = self.vapi.wait_for_event(1, "dhcp6_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_address = ev.addresses[0] + address = ia_na_opts.getfieldval("addr") + self.assert_equal(str(reported_address.address), address) + self.assert_equal(reported_address.preferred_time, + ia_na_opts.getfieldval("preflft")) + self.assert_equal(reported_address.valid_time, + ia_na_opts.getfieldval("validlft")) + + finally: + self.vapi.want_dhcp6_reply_events(enable_disable=0) + self.vapi.dhcp6_clients_enable_disable(enable=0) + + def test_dhcp_pd_send_solicit_receive_advertise(self): + """ Verify DHCPv6 PD Solicit packet and Advertise event """ + + self.vapi.dhcp6_clients_enable_disable(enable=1) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + prefix = {'prefix': {'address': '1:2:3::', 'len': 50}, + 'preferred_time': 60, + 'valid_time': 120} + prefixes = [prefix] + self.vapi.dhcp6_pd_send_client_message( + server_index=0xffffffff, + mrc=1, + msg_type=VppEnum.vl_api_dhcpv6_msg_type_t.DHCPV6_MSG_API_SOLICIT, + sw_if_index=self.pg0.sw_if_index, + T1=20, + T2=40, + prefixes=prefixes, + n_prefixes=len(prefixes)) + rx_list = self.pg0.get_capture(1) + self.assertEqual(len(rx_list), 1) + packet = rx_list[0] + + self.assertEqual(packet.haslayer(IPv6), 1) + self.assertEqual(packet[IPv6].haslayer(DHCP6_Solicit), 1) + + 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(enable_disable=1, + pid=os.getpid()) + + try: + 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=util.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(1, "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 = ia_pd_opts.getfieldval("prefix") + self.assert_equal( + str(reported_prefix.prefix).split('/')[0], prefix) + self.assert_equal(int(str(reported_prefix.prefix).split('/')[1]), + 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")) + + finally: + self.vapi.want_dhcp6_pd_reply_events(enable_disable=0) + self.vapi.dhcp6_clients_enable_disable(enable=0) + + +class TestDHCPv6IANAControlPlane(VppTestCase): + """ DHCPv6 IA NA Control Plane Test Case """ + + @classmethod + def setUpClass(cls): + super(TestDHCPv6IANAControlPlane, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestDHCPv6IANAControlPlane, cls).tearDownClass() + + def setUp(self): + super(TestDHCPv6IANAControlPlane, self).setUp() + + self.create_pg_interfaces(range(1)) + self.interfaces = list(self.pg_interfaces) + for i in self.interfaces: + i.admin_up() + + self.server_duid = DUID_LL(lladdr=self.pg0.remote_mac) + self.client_duid = None + self.T1 = 1 + self.T2 = 2 + + fib = self.vapi.ip_route_dump(0, True) + self.initial_addresses = set(self.get_interface_addresses(fib, + self.pg0)) + + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + self.vapi.dhcp6_client_enable_disable(sw_if_index=self.pg0.sw_if_index, + enable=1) + + def tearDown(self): + self.vapi.dhcp6_client_enable_disable(sw_if_index=self.pg0.sw_if_index, + enable=0) + + for i in self.interfaces: + i.admin_down() + + super(TestDHCPv6IANAControlPlane, self).tearDown() + + @staticmethod + def get_interface_addresses(fib, pg): + lst = [] + for entry in fib: + if entry.route.prefix.prefixlen == 128: + path = entry.route.paths[0] + if path.sw_if_index == pg.sw_if_index: + lst.append(str(entry.route.prefix.network_address)) + return lst + + def get_addresses(self): + fib = self.vapi.ip_route_dump(0, True) + addresses = set(self.get_interface_addresses(fib, self.pg0)) + return addresses.difference(self.initial_addresses) + + def validate_duid_ll(self, duid): + DUID_LL(duid) + + def validate_packet(self, packet, msg_type, is_resend=False): + try: + self.assertEqual(packet.haslayer(msg_type), 1) + client_duid = packet[DHCP6OptClientId].duid + if self.client_duid is None: + self.client_duid = client_duid + self.validate_duid_ll(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 BaseException: + 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, ianaopts=None): + if t1 is None: + t1 = self.T1 + if t2 is None: + t2 = self.T2 + if ianaopts is None: + opt_ia_na = DHCP6OptIA_NA(iaid=1, T1=t1, T2=t2) + else: + opt_ia_na = DHCP6OptIA_NA(iaid=1, T1=t1, T2=t2, ianaopts=ianaopts) + p = (Ether(src=self.pg0.remote_mac, dst=self.pg0.local_mac) / + IPv6(src=util.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_na + ) + self.pg0.add_stream([p]) + self.pg_enable_capture(self.pg_interfaces) + self.pg_start() + + def send_advertise(self, t1=None, t2=None, ianaopts=None): + self.send_packet(DHCP6_Advertise, t1, t2, ianaopts) + + def send_reply(self, t1=None, t2=None, ianaopts=None): + self.send_packet(DHCP6_Reply, t1, t2, ianaopts) + + 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_addresses(self): + """ Test handling of addresses """ + + ia_na_opts = DHCP6OptIAAddress(addr='7:8::2', preflft=1, + validlft=2) + + self.wait_for_solicit() + self.send_advertise(t1=20, t2=40, ianaopts=ia_na_opts) + self.wait_for_request() + self.send_reply(t1=20, t2=40, ianaopts=ia_na_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(addr, '7:8::2') + + self.sleep(2) + + # check that the address is deleted + fib = self.vapi.ip_route_dump(0, True) + addresses = set(self.get_interface_addresses(fib, self.pg0)) + new_addresses = addresses.difference(self.initial_addresses) + self.assertEqual(len(new_addresses), 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_inappropriate_packets(self): + """ Server sends messages with inappropriate 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_address_available_in_advertise(self): + """ Advertise message contains NoAddrsAvail status code """ + + self.wait_for_solicit() + noavail = DHCP6OptStatusCode(statuscode=2) # NoAddrsAvail + self.send_advertise(ianaopts=noavail) + self.wait_for_solicit(is_resend=True) + + def test_preferred_greater_than_valid_lifetime(self): + """ Preferred lifetime is greater than valid lifetime """ + + self.wait_for_solicit() + self.send_advertise() + self.wait_for_request() + ia_na_opts = DHCP6OptIAAddress(addr='7:8::2', preflft=4, validlft=3) + self.send_reply(ianaopts=ia_na_opts) + + self.sleep(0.5) + + # check FIB contains no addresses + fib = self.vapi.ip_route_dump(0, True) + addresses = set(self.get_interface_addresses(fib, self.pg0)) + new_addresses = addresses.difference(self.initial_addresses) + self.assertEqual(len(new_addresses), 0) + + def test_T1_greater_than_T2(self): + """ T1 is greater than T2 """ + + self.wait_for_solicit() + self.send_advertise() + self.wait_for_request() + ia_na_opts = DHCP6OptIAAddress(addr='7:8::2', preflft=4, validlft=8) + self.send_reply(t1=80, t2=40, ianaopts=ia_na_opts) + + self.sleep(0.5) + + # check FIB contains no addresses + fib = self.vapi.ip_route_dump(0, True) + addresses = set(self.get_interface_addresses(fib, self.pg0)) + new_addresses = addresses.difference(self.initial_addresses) + self.assertEqual(len(new_addresses), 0) + + +class TestDHCPv6PDControlPlane(VppTestCase): + """ DHCPv6 PD Control Plane Test Case """ + + @classmethod + def setUpClass(cls): + super(TestDHCPv6PDControlPlane, cls).setUpClass() + + @classmethod + def tearDownClass(cls): + super(TestDHCPv6PDControlPlane, cls).tearDownClass() + + 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() + + self.server_duid = DUID_LL(lladdr=self.pg0.remote_mac) + self.client_duid = None + self.T1 = 1 + self.T2 = 2 + + fib = self.vapi.ip_route_dump(0, True) + 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( + enable=1, + sw_if_index=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.route.prefix.prefixlen == 128: + path = entry.route.paths[0] + if path.sw_if_index == pg.sw_if_index: + lst.append(str(entry.route.prefix.network_address)) + return lst + + def get_addresses(self): + fib = self.vapi.ip_route_dump(0, True) + addresses = set(self.get_interface_addresses(fib, self.pg1)) + return addresses.difference(self.initial_addresses) + + def validate_duid_ll(self, duid): + DUID_LL(duid) + + def validate_packet(self, packet, msg_type, is_resend=False): + try: + self.assertEqual(packet.haslayer(msg_type), 1) + client_duid = packet[DHCP6OptClientId].duid + if self.client_duid is None: + self.client_duid = client_duid + self.validate_duid_ll(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 BaseException: + 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=util.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(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.ip_route_dump(0, True) + 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 addr1 == '7:8:0:76::406': + addr1, addr2 = addr2, addr1 + self.assertEqual(addr1, '7:8:0:2::405') + self.assertEqual(addr2, '7:8:0:76::406') + + self.sleep(1) + + # check that the addresses are deleted + fib = self.vapi.ip_route_dump(0, True) + 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_inappropriate_packets(self): + """ Server sends messages with inappropriate 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_valid_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.ip_route_dump(0, True) + 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.ip_route_dump(0, True) + 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/src/plugins/dhcp/test/vpp_dhcp.py b/src/plugins/dhcp/test/vpp_dhcp.py new file mode 100644 index 00000000000..56ec8caa7df --- /dev/null +++ b/src/plugins/dhcp/test/vpp_dhcp.py @@ -0,0 +1,129 @@ +from vpp_object import VppObject + + +class VppDHCPProxy(VppObject): + + def __init__( + self, + test, + dhcp_server, + dhcp_src_address, + rx_vrf_id=0, + server_vrf_id=0, + ): + self._test = test + self._rx_vrf_id = rx_vrf_id + self._server_vrf_id = server_vrf_id + self._dhcp_server = dhcp_server + self._dhcp_src_address = dhcp_src_address + + def set_proxy( + self, + dhcp_server, + dhcp_src_address, + rx_vrf_id=0, + server_vrf_id=0): + if self.query_vpp_config(): + raise Exception('Vpp config present') + self._rx_vrf_id = rx_vrf_id + self._server_vrf_id = server_vrf_id + self._dhcp_server = dhcp_server + self._dhcp_src_address = dhcp_src_address + + def add_vpp_config(self): + self._test.vapi.dhcp_proxy_config( + is_add=1, + rx_vrf_id=self._rx_vrf_id, + server_vrf_id=self._server_vrf_id, + dhcp_server=self._dhcp_server, + dhcp_src_address=self._dhcp_src_address) + self._test.registry.register(self, self._test.logger) + + def remove_vpp_config(self): + self._test.vapi.dhcp_proxy_config( + rx_vrf_id=self._rx_vrf_id, + server_vrf_id=self._server_vrf_id, + dhcp_server=self._dhcp_server, + dhcp_src_address=self._dhcp_src_address, + is_add=0) + + def get_vpp_dump(self): + dump = self._test.vapi.dhcp_proxy_dump() + for entry in dump: + if entry.rx_vrf_id == self._rx_vrf_id: + return entry + + def query_vpp_config(self): + dump = self.get_vpp_dump() + return True if dump else False + + def object_id(self): + return "dhcp-proxy-%d" % self._rx_vrf_id + + +class VppDHCPClient(VppObject): + + def __init__( + self, + test, + sw_if_index, + hostname, + id=None, + want_dhcp_event=False, + set_broadcast_flag=True, + dscp=None, + pid=None): + self._test = test + self._sw_if_index = sw_if_index + self._hostname = hostname + self._id = id + self._want_dhcp_event = want_dhcp_event + self._set_broadcast_flag = set_broadcast_flag + self._dscp = dscp + self._pid = pid + + def set_client( + self, + sw_if_index, + hostname, + id=None, + want_dhcp_event=False, + set_broadcast_flag=True, + dscp=None, + pid=None): + if self.query_vpp_config(): + raise Exception('Vpp config present') + self._sw_if_index = sw_if_index + self._hostname = hostname + self._id = id + self._want_dhcp_event = want_dhcp_event + self._set_broadcast_flag = set_broadcast_flag + self._dscp = dscp + self._pid = pid + + def add_vpp_config(self): + client = {'sw_if_index': self._sw_if_index, 'hostname': self._hostname, + 'id': self._id, 'want_dhcp_event': self._want_dhcp_event, + 'set_broadcast_flag': self._set_broadcast_flag, + 'dscp': self._dscp, 'pid': self._pid} + self._test.vapi.dhcp_client_config(is_add=1, client=client) + self._test.registry.register(self, self._test.logger) + + def remove_vpp_config(self): + client = client = { + 'sw_if_index': self._sw_if_index, + 'hostname': self._hostname} + self._test.vapi.dhcp_client_config(client=client, is_add=0) + + def get_vpp_dump(self): + dump = self._test.vapi.dhcp_client_dump() + for entry in dump: + if entry.client.sw_if_index == self._sw_if_index: + return entry + + def query_vpp_config(self): + dump = self.get_vpp_dump() + return True if dump else False + + def object_id(self): + return "dhcp-client-%s/%d" % (self._hostname, self._sw_if_index) |