aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins/dhcp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/dhcp')
-rw-r--r--src/plugins/dhcp/CMakeLists.txt51
-rw-r--r--src/plugins/dhcp/client.c1255
-rw-r--r--src/plugins/dhcp/client.h179
-rw-r--r--src/plugins/dhcp/dhcp.api510
-rw-r--r--src/plugins/dhcp/dhcp4_packet.h80
-rw-r--r--src/plugins/dhcp/dhcp4_proxy_error.def32
-rw-r--r--src/plugins/dhcp/dhcp4_proxy_node.c1090
-rw-r--r--src/plugins/dhcp/dhcp6_client_common_dp.c469
-rw-r--r--src/plugins/dhcp/dhcp6_client_common_dp.h94
-rw-r--r--src/plugins/dhcp/dhcp6_ia_na_client_cp.api38
-rw-r--r--src/plugins/dhcp/dhcp6_ia_na_client_cp.c775
-rw-r--r--src/plugins/dhcp/dhcp6_ia_na_client_cp_api.c80
-rw-r--r--src/plugins/dhcp/dhcp6_ia_na_client_dp.c436
-rw-r--r--src/plugins/dhcp/dhcp6_ia_na_client_dp.h165
-rw-r--r--src/plugins/dhcp/dhcp6_packet.h271
-rw-r--r--src/plugins/dhcp/dhcp6_pd_client_cp.api61
-rw-r--r--src/plugins/dhcp/dhcp6_pd_client_cp.c1324
-rw-r--r--src/plugins/dhcp/dhcp6_pd_client_cp_api.c102
-rw-r--r--src/plugins/dhcp/dhcp6_pd_client_dp.c437
-rw-r--r--src/plugins/dhcp/dhcp6_pd_client_dp.h172
-rw-r--r--src/plugins/dhcp/dhcp6_pd_doc.md86
-rw-r--r--src/plugins/dhcp/dhcp6_proxy_error.def29
-rw-r--r--src/plugins/dhcp/dhcp6_proxy_node.c1186
-rw-r--r--src/plugins/dhcp/dhcp_api.c871
-rw-r--r--src/plugins/dhcp/dhcp_client_detect.c324
-rw-r--r--src/plugins/dhcp/dhcp_proxy.c375
-rw-r--r--src/plugins/dhcp/dhcp_proxy.h306
-rw-r--r--src/plugins/dhcp/dhcp_test.c492
-rw-r--r--src/plugins/dhcp/test/test_dhcp.py1653
-rw-r--r--src/plugins/dhcp/test/test_dhcp6.py806
-rw-r--r--src/plugins/dhcp/test/vpp_dhcp.py129
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 (&ethernet_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, &params);
+
+ 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, &params);
+
+ 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 = &params.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, &params);
+}
+
+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 =
+ &params.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,
+ &params);
+}
+
+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)