dhcp: Move to plugin

Type: feature

Change-Id: I3fe27a8ef577741d9a5c4f090ec91cf68fb44fe3
Signed-off-by: Neale Ranns <nranns@cisco.com>
diff --git a/src/plugins/dhcp/CMakeLists.txt b/src/plugins/dhcp/CMakeLists.txt
new file mode 100644
index 0000000..b2dd630
--- /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/vnet/dhcp/client.c b/src/plugins/dhcp/client.c
similarity index 99%
rename from src/vnet/dhcp/client.c
rename to src/plugins/dhcp/client.c
index aaeda96..800da3e 100644
--- a/src/vnet/dhcp/client.c
+++ b/src/plugins/dhcp/client.c
@@ -14,8 +14,8 @@
  */
 #include <vlib/vlib.h>
 #include <vlibmemory/api.h>
-#include <vnet/dhcp/client.h>
-#include <vnet/dhcp/dhcp_proxy.h>
+#include <dhcp/client.h>
+#include <dhcp/dhcp_proxy.h>
 #include <vnet/fib/fib_table.h>
 #include <vnet/qos/qos_types.h>
 
diff --git a/src/vnet/dhcp/client.h b/src/plugins/dhcp/client.h
similarity index 98%
rename from src/vnet/dhcp/client.h
rename to src/plugins/dhcp/client.h
index 5191fcf..68176ab 100644
--- a/src/vnet/dhcp/client.h
+++ b/src/plugins/dhcp/client.h
@@ -20,7 +20,7 @@
 #define included_dhcp_client_h
 
 #include <vnet/ip/ip.h>
-#include <vnet/dhcp/dhcp4_packet.h>
+#include <dhcp/dhcp4_packet.h>
 
 #define foreach_dhcp_client_state               \
 _(DHCP_DISCOVER)                                \
diff --git a/src/vnet/dhcp/dhcp.api b/src/plugins/dhcp/dhcp.api
similarity index 91%
rename from src/vnet/dhcp/dhcp.api
rename to src/plugins/dhcp/dhcp.api
index f3fef51..a91874b 100644
--- a/src/vnet/dhcp/dhcp.api
+++ b/src/plugins/dhcp/dhcp.api
@@ -49,6 +49,54 @@
   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
diff --git a/src/vnet/dhcp/dhcp4_packet.h b/src/plugins/dhcp/dhcp4_packet.h
similarity index 100%
rename from src/vnet/dhcp/dhcp4_packet.h
rename to src/plugins/dhcp/dhcp4_packet.h
diff --git a/src/vnet/dhcp/dhcp4_proxy_error.def b/src/plugins/dhcp/dhcp4_proxy_error.def
similarity index 100%
rename from src/vnet/dhcp/dhcp4_proxy_error.def
rename to src/plugins/dhcp/dhcp4_proxy_error.def
diff --git a/src/vnet/dhcp/dhcp4_proxy_node.c b/src/plugins/dhcp/dhcp4_proxy_node.c
similarity index 99%
rename from src/vnet/dhcp/dhcp4_proxy_node.c
rename to src/plugins/dhcp/dhcp4_proxy_node.c
index 3576f0c..10963c7 100644
--- a/src/vnet/dhcp/dhcp4_proxy_node.c
+++ b/src/plugins/dhcp/dhcp4_proxy_node.c
@@ -17,13 +17,13 @@
 
 #include <vlib/vlib.h>
 #include <vnet/pg/pg.h>
-#include <vnet/dhcp/dhcp_proxy.h>
-#include <vnet/dhcp/client.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 <vnet/dhcp/dhcp4_proxy_error.def>
+#include <dhcp/dhcp4_proxy_error.def>
 #undef dhcp_proxy_error
 };
 
diff --git a/src/vnet/dhcp/dhcp6_client_common_dp.c b/src/plugins/dhcp/dhcp6_client_common_dp.c
similarity index 85%
rename from src/vnet/dhcp/dhcp6_client_common_dp.c
rename to src/plugins/dhcp/dhcp6_client_common_dp.c
index 7ca3b61..e42ec3f 100644
--- a/src/vnet/dhcp/dhcp6_client_common_dp.c
+++ b/src/plugins/dhcp/dhcp6_client_common_dp.c
@@ -14,10 +14,11 @@
  */
 
 #include <vnet/ethernet/ethernet.h>
-#include <vnet/dhcp/dhcp6_client_common_dp.h>
-#include <vnet/dhcp/dhcp6_ia_na_client_dp.h>
-#include <vnet/dhcp/dhcp6_pd_client_dp.h>
-#include <vnet/dhcp/dhcp6_packet.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;
@@ -48,30 +49,11 @@
   return vec_len (ccm->server_ids) - 1;
 }
 
-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
 generate_client_duid (void)
 {
-  client_duid.duid_type = htons (DHCPV6_DUID_LL);
-  client_duid.hardware_type = htons (1);
+  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;
@@ -190,8 +172,8 @@
 	  u32 dhcpv6_ip6_payload_offset =
 	    (u8 *) dhcpv60 - ((u8 *) ip0 + sizeof (*ip0));
 	  options_length =
-	    ntohs (ip0->payload_length) - dhcpv6_ip6_payload_offset -
-	    sizeof (*dhcpv60);
+	    clib_net_to_host_u16 (ip0->payload_length) -
+	    dhcpv6_ip6_payload_offset - sizeof (*dhcpv60);
 
 	  clib_memset (&report, 0, sizeof (report));
 
@@ -232,22 +214,24 @@
 	      while (options_length > 0)
 		{
 		  if (options_length <
-		      ntohs (option->length) + sizeof (*option))
+		      clib_net_to_host_u16 (option->length) +
+		      sizeof (*option))
 		    {
 		      clib_warning
 			("remaining payload length < option length (%d < %d)",
 			 options_length,
-			 ntohs (option->length) + sizeof (*option));
+			 clib_net_to_host_u16 (option->length) +
+			 sizeof (*option));
 		      break;
 		    }
-		  u16 oo = ntohs (option->option);
+		  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 = ntohl (ia_header->iaid);
-		      u32 T1 = ntohl (ia_header->t1);
-		      u32 T2 = ntohl (ia_header->t2);
+		      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)
@@ -260,11 +244,12 @@
 		      dhcpv6_option_t *inner_option =
 			(void *) ia_header->data;
 		      u16 inner_options_length =
-			ntohs (option->length) - (sizeof (*ia_header) -
-						  sizeof (dhcpv6_option_t));
+			clib_net_to_host_u16 (option->length) -
+			(sizeof (*ia_header) - sizeof (dhcpv6_option_t));
 		      while (inner_options_length > 0)
 			{
-			  u16 inner_oo = ntohs (inner_option->option);
+			  u16 inner_oo =
+			    clib_net_to_host_u16 (inner_option->option);
 			  if (discard_option)
 			    ;
 			  else if (inner_oo == DHCPV6_OPTION_IAADDR)
@@ -276,9 +261,9 @@
 			      dhcp6_address_info_t *address_info =
 				&addresses[n_addresses];
 			      address_info->preferred_time =
-				ntohl (iaaddr->preferred);
+				clib_net_to_host_u32 (iaaddr->preferred);
 			      address_info->valid_time =
-				ntohl (iaaddr->valid);
+				clib_net_to_host_u32 (iaaddr->valid);
 			      address_info->address = iaaddr->addr;
 			    }
 			  else if (inner_oo == DHCPV6_OPTION_IAPREFIX)
@@ -290,9 +275,9 @@
 			      dhcp6_prefix_info_t *prefix_info =
 				&prefixes[n_prefixes];
 			      prefix_info->preferred_time =
-				ntohl (iaprefix->preferred);
+				clib_net_to_host_u32 (iaprefix->preferred);
 			      prefix_info->valid_time =
-				ntohl (iaprefix->valid);
+				clib_net_to_host_u32 (iaprefix->valid);
 			      prefix_info->prefix_length = iaprefix->prefix;
 			      prefix_info->prefix = iaprefix->addr;
 			    }
@@ -301,15 +286,16 @@
 			      dhcpv6_status_code_t *sc =
 				(void *) inner_option;
 			      report.inner_status_code =
-				ntohs (sc->status_code);
+				clib_net_to_host_u16 (sc->status_code);
 			    }
 			  inner_options_length -=
 			    sizeof (*inner_option) +
-			    ntohs (inner_option->length);
+			    clib_net_to_host_u16 (inner_option->length);
 			  inner_option =
 			    (void *) ((u8 *) inner_option +
 				      sizeof (*inner_option) +
-				      ntohs (inner_option->length));
+				      clib_net_to_host_u16
+				      (inner_option->length));
 			}
 		    }
 		  else if (oo == DHCPV6_OPTION_CLIENTID)
@@ -322,7 +308,7 @@
 			}
 		      else
 			{
-			  u16 len = ntohs (option->length);
+			  u16 len = clib_net_to_host_u16 (option->length);
 			  client_id_present = 1;
 			  if (len != CLIENT_DUID_LENGTH ||
 			      0 != memcmp (option->data,
@@ -345,7 +331,7 @@
 			}
 		      else
 			{
-			  u16 ol = ntohs (option->length);
+			  u16 ol = clib_net_to_host_u16 (option->length);
 			  if (ol - 2 /* 2 byte DUID type code */  > 128)
 			    {
 			      clib_warning
@@ -366,12 +352,14 @@
 		  else if (oo == DHCPV6_OPTION_STATUS_CODE)
 		    {
 		      dhcpv6_status_code_t *sc = (void *) option;
-		      report.status_code = ntohs (sc->status_code);
+		      report.status_code =
+			clib_net_to_host_u16 (sc->status_code);
 		    }
-		  options_length -= sizeof (*option) + ntohs (option->length);
+		  options_length -=
+		    sizeof (*option) + clib_net_to_host_u16 (option->length);
 		  option =
 		    (void *) ((u8 *) option + sizeof (*option) +
-			      ntohs (option->length));
+			      clib_net_to_host_u16 (option->length));
 		}
 
 	      if (!client_id_present)
@@ -472,18 +460,6 @@
 			     0 /* is_ip6 */ );
 }
 
-void
-  vl_api_dhcp6_clients_enable_disable_t_handler
-  (vl_api_dhcp6_clients_enable_disable_t * mp)
-{
-  vl_api_dhcp6_clients_enable_disable_reply_t *rmp;
-  int rv = 0;
-
-  dhcp6_clients_enable_disable (mp->enable);
-
-  REPLY_MACRO (VL_API_DHCP6_CLIENTS_ENABLE_DISABLE_REPLY);
-}
-
 /*
  * fd.io coding-style-patch-verification: ON
  *
diff --git a/src/vnet/dhcp/dhcp6_client_common_dp.h b/src/plugins/dhcp/dhcp6_client_common_dp.h
similarity index 72%
rename from src/vnet/dhcp/dhcp6_client_common_dp.h
rename to src/plugins/dhcp/dhcp6_client_common_dp.h
index 0acef84..4010aa0 100644
--- a/src/vnet/dhcp/dhcp6_client_common_dp.h
+++ b/src/plugins/dhcp/dhcp6_client_common_dp.h
@@ -17,27 +17,7 @@
 #define included_vnet_dhcp6_client_common_dp_h
 
 #include <vlib/vlib.h>
-#include <vnet/dhcp/dhcp6_client_common_dp.h>
-#include <vnet/dhcp/dhcp6_packet.h>
-#include <vnet/vnet_msg_enum.h>
-#include <vlibapi/api_common.h>
-#include <vlibmemory/api.h>
-
-#define vl_typedefs		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_typedefs
-
-#define vl_endianfun		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_endianfun
-
-/* instantiate all the print functions we know about */
-#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
-#define vl_printfun
-#include <vnet/vnet_all_api_h.h>
-#undef vl_printfun
-
-#include <vlibapi/api_helper_macros.h>
+#include <dhcp/dhcp6_packet.h>
 
 typedef struct
 {
@@ -82,11 +62,7 @@
 void dhcp6_clients_enable_disable (u8 enable);
 u32 server_index_get_or_create (u8 * data, u16 len);
 
-#define vl_typedefs		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_typedefs
-
-void vl_api_dhcp6_duid_ll_set_t_handler (vl_api_dhcp6_duid_ll_set_t * mp);
+extern dhcpv6_duid_ll_string_t client_duid;
 
 static_always_inline f64
 random_f64_from_to (f64 from, f64 to)
diff --git a/src/vnet/dhcp/dhcp6_ia_na_client_cp.api b/src/plugins/dhcp/dhcp6_ia_na_client_cp.api
similarity index 100%
rename from src/vnet/dhcp/dhcp6_ia_na_client_cp.api
rename to src/plugins/dhcp/dhcp6_ia_na_client_cp.api
diff --git a/src/vnet/dhcp/dhcp6_ia_na_client_cp.c b/src/plugins/dhcp/dhcp6_ia_na_client_cp.c
similarity index 90%
rename from src/vnet/dhcp/dhcp6_ia_na_client_cp.c
rename to src/plugins/dhcp/dhcp6_ia_na_client_cp.c
index e440805..2d4135d 100644
--- a/src/vnet/dhcp/dhcp6_ia_na_client_cp.c
+++ b/src/plugins/dhcp/dhcp6_ia_na_client_cp.c
@@ -16,28 +16,13 @@
 #include <vnet/vnet.h>
 #include <vlibmemory/api.h>
 #include <vnet/vnet_msg_enum.h>
-#include <vnet/dhcp/dhcp6_packet.h>
-#include <vnet/dhcp/dhcp6_ia_na_client_dp.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>
 
-#define vl_typedefs		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_typedefs
-
-#define vl_endianfun		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_endianfun
-
-#include <vlibapi/api_helper_macros.h>
-
-#define foreach_dhcp6_client_cp_msg                                           \
-_(DHCP6_CLIENT_ENABLE_DISABLE, dhcp6_client_enable_disable)
-
-#define vl_api_dhcp6_client_enable_disable_t_print vl_noop_handler
-
 typedef struct
 {
   u32 sw_if_index;
@@ -622,7 +607,7 @@
 };
 /* *INDENT-ON* */
 
-static int
+int
 dhcp6_client_enable_disable (u32 sw_if_index, u8 enable)
 {
   dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main;
@@ -766,67 +751,20 @@
 };
 /* *INDENT-ON* */
 
-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_SW_INTERFACE_SET_TABLE_REPLY);
-}
-
-#define vl_msg_name_crc_list
-#include <vnet/dhcp/dhcp6_ia_na_client_cp.api.h>
-#undef vl_msg_name_crc_list
-
-static void
-setup_message_id_table (api_main_t * am)
-{
-#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id);
-  foreach_vl_msg_name_crc_dhcp6_ia_na_client_cp;
-#undef _
-}
-
 static clib_error_t *
-dhcp_client_cp_init (vlib_main_t * vm)
+dhcp_ia_na_client_cp_init (vlib_main_t * vm)
 {
   dhcp6_client_cp_main_t *rm = &dhcp6_client_cp_main;
-  api_main_t *am = &api_main;
 
   rm->vlib_main = vm;
   rm->vnet_main = vnet_get_main ();
-  rm->api_main = am;
+  rm->api_main = &api_main;
   rm->node_index = dhcp6_client_cp_process_node.index;
 
-#define _(N,n)                                                  \
-    vl_msg_api_set_handlers(VL_API_##N, #n,                     \
-                           vl_api_##n##_t_handler,              \
-                           vl_noop_handler,                     \
-                           vl_api_##n##_t_endian,               \
-                           vl_api_##n##_t_print,                \
-                           sizeof(vl_api_##n##_t), 0/* do NOT trace! */);
-  foreach_dhcp6_client_cp_msg;
-#undef _
-
-  /*
-   * Set up the (msg_name, crc, message-id) table
-   */
-  setup_message_id_table (am);
-
-  return 0;
+  return NULL;
 }
 
-VLIB_INIT_FUNCTION (dhcp_client_cp_init);
+VLIB_INIT_FUNCTION (dhcp_ia_na_client_cp_init);
 
 /*
  * fd.io coding-style-patch-verification: ON
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 0000000..8e5898a
--- /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/vnet/dhcp/dhcp6_ia_na_client_dp.c b/src/plugins/dhcp/dhcp6_ia_na_client_dp.c
similarity index 64%
rename from src/vnet/dhcp/dhcp6_ia_na_client_dp.c
rename to src/plugins/dhcp/dhcp6_ia_na_client_dp.c
index f49017b..6ea822f 100644
--- a/src/vnet/dhcp/dhcp6_ia_na_client_dp.c
+++ b/src/plugins/dhcp/dhcp6_ia_na_client_dp.c
@@ -14,37 +14,17 @@
  */
 
 #include <vlib/vlib.h>
-#include <vnet/dhcp/dhcp6_packet.h>
-#include <vnet/dhcp/dhcp_proxy.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 <vlibapi/api_common.h>
-#include <vlibmemory/api.h>
-#include <vnet/dhcp/dhcp6_ia_na_client_dp.h>
-#include <vnet/dhcp/dhcp6_client_common_dp.h>
+#include <dhcp/dhcp6_ia_na_client_dp.h>
+#include <dhcp/dhcp6_client_common_dp.h>
 #include <vnet/ip/ip_types_api.h>
 
-#include <vnet/vnet_msg_enum.h>
-
-#define vl_typedefs		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_typedefs
-
-#define vl_endianfun		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_endianfun
-
-/* instantiate all the print functions we know about */
-#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
-#define vl_printfun
-#include <vnet/vnet_all_api_h.h>
-#undef vl_printfun
-
-#include <vlibapi/api_helper_macros.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;
 
@@ -430,208 +410,6 @@
     }
 }
 
-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);
-}
-
-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);
-                    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* */
-
-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);
-}
-
 static clib_error_t *
 dhcp6_client_init (vlib_main_t * vm)
 {
diff --git a/src/vnet/dhcp/dhcp6_ia_na_client_dp.h b/src/plugins/dhcp/dhcp6_ia_na_client_dp.h
similarity index 92%
rename from src/vnet/dhcp/dhcp6_ia_na_client_dp.h
rename to src/plugins/dhcp/dhcp6_ia_na_client_dp.h
index a866479..88a6b75 100644
--- a/src/vnet/dhcp/dhcp6_ia_na_client_dp.h
+++ b/src/plugins/dhcp/dhcp6_ia_na_client_dp.h
@@ -16,8 +16,9 @@
 #ifndef included_vnet_dhcp6_client_dp_h
 #define included_vnet_dhcp6_client_dp_h
 
-#include <vlib/vlib.h>
-#include <vnet/dhcp/dhcp6_client_common_dp.h>
+#include <stdbool.h>
+
+#include <dhcp/dhcp6_client_common_dp.h>
 
 typedef struct
 {
@@ -87,31 +88,19 @@
   dhcp6_address_info_t *addresses;
 } address_report_t;
 
-#define vl_typedefs		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_typedefs
-
 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);
-
-
-void
-  vl_api_want_dhcp6_reply_events_t_handler
-  (vl_api_want_dhcp6_reply_events_t * mp);
-void
-  vl_api_dhcp6_send_client_message_t_handler
-  (vl_api_dhcp6_send_client_message_t * mp);
-void
-  vl_api_dhcp6_clients_enable_disable_t_handler
-  (vl_api_dhcp6_clients_enable_disable_t * mp);
+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;
diff --git a/src/vnet/dhcp/dhcp6_packet.h b/src/plugins/dhcp/dhcp6_packet.h
similarity index 100%
rename from src/vnet/dhcp/dhcp6_packet.h
rename to src/plugins/dhcp/dhcp6_packet.h
diff --git a/src/vnet/dhcp/dhcp6_pd_client_cp.api b/src/plugins/dhcp/dhcp6_pd_client_cp.api
similarity index 100%
rename from src/vnet/dhcp/dhcp6_pd_client_cp.api
rename to src/plugins/dhcp/dhcp6_pd_client_cp.api
diff --git a/src/vnet/dhcp/dhcp6_pd_client_cp.c b/src/plugins/dhcp/dhcp6_pd_client_cp.c
similarity index 92%
rename from src/vnet/dhcp/dhcp6_pd_client_cp.c
rename to src/plugins/dhcp/dhcp6_pd_client_cp.c
index cc538a7..6f15143 100644
--- a/src/vnet/dhcp/dhcp6_pd_client_cp.c
+++ b/src/plugins/dhcp/dhcp6_pd_client_cp.c
@@ -15,32 +15,14 @@
 
 #include <vnet/vnet.h>
 #include <vlibmemory/api.h>
-#include <vnet/vnet_msg_enum.h>
-#include <vnet/dhcp/dhcp6_packet.h>
-#include <vnet/dhcp/dhcp6_pd_client_dp.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>
 
-#define vl_typedefs		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_typedefs
-
-#define vl_endianfun		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_endianfun
-
-#include <vlibapi/api_helper_macros.h>
-
-#define foreach_dhcp6_pd_client_cp_msg                                        \
-_(DHCP6_PD_CLIENT_ENABLE_DISABLE, dhcp6_pd_client_enable_disable)             \
-_(IP6_ADD_DEL_ADDRESS_USING_PREFIX, ip6_add_del_address_using_prefix)
-
-#define vl_api_dhcp6_pd_client_enable_disable_t_print vl_noop_handler
-#define vl_api_ip6_add_del_address_using_prefix_t_print vl_noop_handler
-
 typedef struct
 {
   u32 prefix_group_index;
@@ -868,9 +850,10 @@
     }
 }
 
-static int
-cp_ip6_address_add_del (u32 sw_if_index, const u8 * prefix_group,
-			ip6_address_t address, u8 prefix_length, u8 is_add)
+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;
@@ -927,32 +910,6 @@
   return 0;
 }
 
-static void
-  vl_api_ip6_add_del_address_using_prefix_t_handler
-  (vl_api_ip6_add_del_address_using_prefix_t * mp)
-{
-  vl_api_ip6_add_del_address_using_prefix_reply_t *rmp;
-  u32 sw_if_index;
-  ip6_address_t address;
-  u8 prefix_length;
-  int rv = 0;
-
-  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 =
-    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);
-}
-
 static clib_error_t *
 cp_ip6_address_add_del_command_function (vlib_main_t * vm,
 					 unformat_input_t * input,
@@ -999,7 +956,7 @@
     error = clib_error_return (0, "Missing address");
   else
     {
-      if (cp_ip6_address_add_del
+      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");
     }
@@ -1169,9 +1126,11 @@
 };
 /* *INDENT-ON* */
 
-static int
-dhcp6_pd_client_enable_disable (u32 sw_if_index, const u8 * prefix_group,
-				u8 enable)
+
+
+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;
@@ -1341,66 +1300,17 @@
 };
 /* *INDENT-ON* */
 
-static void
-  vl_api_dhcp6_pd_client_enable_disable_t_handler
-  (vl_api_dhcp6_pd_client_enable_disable_t * mp)
-{
-  vl_api_dhcp6_pd_client_enable_disable_reply_t *rmp;
-  u32 sw_if_index;
-  int rv = 0;
-
-  VALIDATE_SW_IF_INDEX (mp);
-
-  sw_if_index = ntohl (mp->sw_if_index);
-
-  rv =
-    dhcp6_pd_client_enable_disable (sw_if_index, mp->prefix_group,
-				    mp->enable);
-
-  BAD_SW_IF_INDEX_LABEL;
-
-  REPLY_MACRO (VL_API_DHCP6_PD_CLIENT_ENABLE_DISABLE_REPLY);
-}
-
-#define vl_msg_name_crc_list
-#include <vnet/dhcp/dhcp6_pd_client_cp.api.h>
-#undef vl_msg_name_crc_list
-
-static void
-setup_message_id_table (api_main_t * am)
-{
-#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id);
-  foreach_vl_msg_name_crc_dhcp6_pd_client_cp;
-#undef _
-}
-
 static clib_error_t *
 dhcp_pd_client_cp_init (vlib_main_t * vm)
 {
   dhcp6_pd_client_cp_main_t *rm = &dhcp6_pd_client_cp_main;
-  api_main_t *am = &api_main;
 
   rm->vlib_main = vm;
   rm->vnet_main = vnet_get_main ();
-  rm->api_main = am;
+  rm->api_main = &api_main;
   rm->node_index = dhcp6_pd_client_cp_process_node.index;
 
-#define _(N,n)                                                  \
-    vl_msg_api_set_handlers(VL_API_##N, #n,                     \
-                           vl_api_##n##_t_handler,              \
-                           vl_noop_handler,                     \
-                           vl_api_##n##_t_endian,               \
-                           vl_api_##n##_t_print,                \
-                           sizeof(vl_api_##n##_t), 0/* do NOT trace! */);
-  foreach_dhcp6_pd_client_cp_msg;
-#undef _
-
-  /*
-   * Set up the (msg_name, crc, message-id) table
-   */
-  setup_message_id_table (am);
-
-  return 0;
+  return (NULL);
 }
 
 VLIB_INIT_FUNCTION (dhcp_pd_client_cp_init);
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 0000000..4999fd7
--- /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/vnet/dhcp/dhcp6_pd_client_dp.c b/src/plugins/dhcp/dhcp6_pd_client_dp.c
similarity index 63%
rename from src/vnet/dhcp/dhcp6_pd_client_dp.c
rename to src/plugins/dhcp/dhcp6_pd_client_dp.c
index c665b17..74a1f16 100644
--- a/src/vnet/dhcp/dhcp6_pd_client_dp.c
+++ b/src/plugins/dhcp/dhcp6_pd_client_dp.c
@@ -14,37 +14,17 @@
  */
 
 #include <vlib/vlib.h>
-#include <vnet/dhcp/dhcp6_packet.h>
-#include <vnet/dhcp/dhcp_proxy.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 <vlibapi/api_common.h>
-#include <vlibmemory/api.h>
-#include <vnet/dhcp/dhcp6_pd_client_dp.h>
-#include <vnet/dhcp/dhcp6_client_common_dp.h>
+#include <dhcp/dhcp6_pd_client_dp.h>
+#include <dhcp/dhcp6_client_common_dp.h>
 #include <vnet/ip/ip_types_api.h>
 
-#include <vnet/vnet_msg_enum.h>
-
-#define vl_typedefs		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_typedefs
-
-#define vl_endianfun		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_endianfun
-
-/* instantiate all the print functions we know about */
-#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
-#define vl_printfun
-#include <vnet/vnet_all_api_h.h>
-#undef vl_printfun
-
-#include <vlibapi/api_helper_macros.h>
-
 dhcp6_pd_client_main_t dhcp6_pd_client_main;
 dhcp6_pd_client_public_main_t dhcp6_pd_client_public_main;
 
@@ -433,214 +413,6 @@
     }
 }
 
-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_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);
-                    msg->client_index = reg->client_index;
-                    msg->pid = reg->client_pid;
-                    vl_api_send_msg (vl_reg, (u8 *) msg);
-                  }
-              }));
-              /* *INDENT-ON* */
-
-	      clib_mem_free (event);
-	    }
-	}
-      vlib_process_put_event_data (vm, event_data);
-    }
-
-  return 0;
-}
-
-/* *INDENT-OFF* */
-VLIB_REGISTER_NODE (dhcp6_pd_reply_process_node) = {
-  .function = dhcp6_pd_reply_process,
-  .type = VLIB_NODE_TYPE_PROCESS,
-  .name = "dhcp6-pd-reply-publisher-process",
-};
-/* *INDENT-ON* */
-
-void
-  vl_api_want_dhcp6_pd_reply_events_t_handler
-  (vl_api_want_dhcp6_pd_reply_events_t * mp)
-{
-  vpe_api_main_t *am = &vpe_api_main;
-  vl_api_want_dhcp6_pd_reply_events_reply_t *rmp;
-  int rv = 0;
-
-  uword *p =
-    hash_get (am->dhcp6_pd_reply_events_registration_hash, mp->client_index);
-  vpe_client_registration_t *rp;
-  if (p)
-    {
-      if (mp->enable_disable)
-	{
-	  clib_warning ("pid %d: already enabled...", ntohl (mp->pid));
-	  rv = VNET_API_ERROR_INVALID_REGISTRATION;
-	  goto reply;
-	}
-      else
-	{
-	  rp =
-	    pool_elt_at_index (am->dhcp6_pd_reply_events_registrations, p[0]);
-	  pool_put (am->dhcp6_pd_reply_events_registrations, rp);
-	  hash_unset (am->dhcp6_pd_reply_events_registration_hash,
-		      mp->client_index);
-	  if (pool_elts (am->dhcp6_pd_reply_events_registrations) == 0)
-	    dhcp6_pd_set_publisher_node (~0, 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);
-}
-
 static clib_error_t *
 dhcp6_pd_client_init (vlib_main_t * vm)
 {
diff --git a/src/vnet/dhcp/dhcp6_pd_client_dp.h b/src/plugins/dhcp/dhcp6_pd_client_dp.h
similarity index 92%
rename from src/vnet/dhcp/dhcp6_pd_client_dp.h
rename to src/plugins/dhcp/dhcp6_pd_client_dp.h
index 88c731c..561ca8a 100644
--- a/src/vnet/dhcp/dhcp6_pd_client_dp.h
+++ b/src/plugins/dhcp/dhcp6_pd_client_dp.h
@@ -17,7 +17,7 @@
 #define included_vnet_dhcp6_pd_client_dp_h
 
 #include <vlib/vlib.h>
-#include <vnet/dhcp/dhcp6_client_common_dp.h>
+#include <dhcp/dhcp6_client_common_dp.h>
 
 typedef struct
 {
@@ -89,31 +89,24 @@
   dhcp6_prefix_info_t *prefixes;
 } prefix_report_t;
 
-#define vl_typedefs		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_typedefs
-
 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);
-
-void
-  vl_api_want_dhcp6_pd_reply_events_t_handler
-  (vl_api_want_dhcp6_pd_reply_events_t * mp);
-void
-  vl_api_dhcp6_pd_send_client_message_t_handler
-  (vl_api_dhcp6_pd_send_client_message_t * mp);
-void
-  vl_api_dhcp6_clients_enable_disable_t_handler
-  (vl_api_dhcp6_clients_enable_disable_t * mp);
+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
diff --git a/src/vnet/dhcp/dhcp6_pd_doc.md b/src/plugins/dhcp/dhcp6_pd_doc.md
similarity index 100%
rename from src/vnet/dhcp/dhcp6_pd_doc.md
rename to src/plugins/dhcp/dhcp6_pd_doc.md
diff --git a/src/vnet/dhcp/dhcp6_proxy_error.def b/src/plugins/dhcp/dhcp6_proxy_error.def
similarity index 100%
rename from src/vnet/dhcp/dhcp6_proxy_error.def
rename to src/plugins/dhcp/dhcp6_proxy_error.def
diff --git a/src/vnet/dhcp/dhcp6_proxy_node.c b/src/plugins/dhcp/dhcp6_proxy_node.c
similarity index 99%
rename from src/vnet/dhcp/dhcp6_proxy_node.c
rename to src/plugins/dhcp/dhcp6_proxy_node.c
index 174548f..929b49e 100644
--- a/src/vnet/dhcp/dhcp6_proxy_node.c
+++ b/src/plugins/dhcp/dhcp6_proxy_node.c
@@ -17,15 +17,15 @@
 
 #include <vlib/vlib.h>
 #include <vnet/pg/pg.h>
-#include <vnet/dhcp/dhcp_proxy.h>
-#include <vnet/dhcp/dhcp6_packet.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 <vnet/dhcp/dhcp6_proxy_error.def>
+#include <dhcp/dhcp6_proxy_error.def>
 #undef dhcpv6_proxy_error
 };
 
diff --git a/src/plugins/dhcp/dhcp_api.c b/src/plugins/dhcp/dhcp_api.c
new file mode 100644
index 0000000..61efaba
--- /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/vnet/dhcp/dhcp_client_detect.c b/src/plugins/dhcp/dhcp_client_detect.c
similarity index 99%
rename from src/vnet/dhcp/dhcp_client_detect.c
rename to src/plugins/dhcp/dhcp_client_detect.c
index c79970d..31b8985 100644
--- a/src/vnet/dhcp/dhcp_client_detect.c
+++ b/src/plugins/dhcp/dhcp_client_detect.c
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-#include <vnet/dhcp/client.h>
+#include <dhcp/client.h>
 #include <vnet/udp/udp.h>
 
 #define foreach_dhcp_client_detect                    \
diff --git a/src/vnet/dhcp/dhcp_proxy.c b/src/plugins/dhcp/dhcp_proxy.c
similarity index 99%
rename from src/vnet/dhcp/dhcp_proxy.c
rename to src/plugins/dhcp/dhcp_proxy.c
index 9a69041..1890c87 100644
--- a/src/vnet/dhcp/dhcp_proxy.c
+++ b/src/plugins/dhcp/dhcp_proxy.c
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-#include <vnet/dhcp/dhcp_proxy.h>
+#include <dhcp/dhcp_proxy.h>
 #include <vnet/fib/fib_table.h>
 #include <vnet/mfib/mfib_table.h>
 
diff --git a/src/vnet/dhcp/dhcp_proxy.h b/src/plugins/dhcp/dhcp_proxy.h
similarity index 98%
rename from src/vnet/dhcp/dhcp_proxy.h
rename to src/plugins/dhcp/dhcp_proxy.h
index 60c4eb8..2b120b5 100644
--- a/src/vnet/dhcp/dhcp_proxy.h
+++ b/src/plugins/dhcp/dhcp_proxy.h
@@ -19,7 +19,7 @@
 #define included_dhcp_proxy_h
 
 #include <vnet/vnet.h>
-#include <vnet/dhcp/dhcp4_packet.h>
+#include <dhcp/dhcp4_packet.h>
 #include <vnet/ethernet/ethernet.h>
 #include <vnet/ip/ip.h>
 #include <vnet/ip/ip4.h>
@@ -31,7 +31,7 @@
 typedef enum
 {
 #define dhcp_proxy_error(n,s) DHCP_PROXY_ERROR_##n,
-#include <vnet/dhcp/dhcp4_proxy_error.def>
+#include <dhcp/dhcp4_proxy_error.def>
 #undef dhcp_proxy_error
   DHCP_PROXY_N_ERROR,
 } dhcp_proxy_error_t;
@@ -39,7 +39,7 @@
 typedef enum
 {
 #define dhcpv6_proxy_error(n,s) DHCPV6_PROXY_ERROR_##n,
-#include <vnet/dhcp/dhcp6_proxy_error.def>
+#include <dhcp/dhcp6_proxy_error.def>
 #undef dhcpv6_proxy_error
   DHCPV6_PROXY_N_ERROR,
 } dhcpv6_proxy_error_t;
diff --git a/src/plugins/dhcp/dhcp_test.c b/src/plugins/dhcp/dhcp_test.c
new file mode 100644
index 0000000..a042dc0
--- /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 0000000..2200e48
--- /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 0000000..02f4202
--- /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 0000000..56ec8ca
--- /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)
diff --git a/src/vat/api_format.c b/src/vat/api_format.c
index 16abf45..4c1a85f 100644
--- a/src/vat/api_format.c
+++ b/src/vat/api_format.c
@@ -51,7 +51,6 @@
 #include <vnet/policer/policer.h>
 #include <vnet/policer/police.h>
 #include <vnet/mfib/mfib_types.h>
-#include <vnet/dhcp/dhcp_proxy.h>
 #include <vnet/bonding/node.h>
 #include <vnet/qos/qos_types.h>
 #include <vnet/ethernet/ethernet_types_api.h>
@@ -2666,33 +2665,6 @@
 			   clib_net_to_host_u32 (mp->sw_if_index));
 }
 
-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);
-}
-
-static void vl_api_dhcp_compl_event_t_handler_json
-  (vl_api_dhcp_compl_event_t * mp)
-{
-  /* JSON output not supported */
-}
-
 static void vl_api_get_first_msg_id_reply_t_handler
   (vl_api_get_first_msg_id_reply_t * mp)
 {
@@ -5108,9 +5080,6 @@
 _(sw_interface_set_unnumbered_reply)                    \
 _(ip_neighbor_add_del_reply)                            \
 _(reset_fib_reply)                                      \
-_(dhcp_proxy_config_reply)                              \
-_(dhcp_proxy_set_vss_reply)                             \
-_(dhcp_client_config_reply)                             \
 _(set_ip_flow_hash_reply)                               \
 _(sw_interface_ip6_enable_disable_reply)                \
 _(ip6nd_proxy_add_del_reply)                            \
@@ -5313,10 +5282,6 @@
 _(CREATE_VLAN_SUBIF_REPLY, create_vlan_subif_reply)                     \
 _(CREATE_SUBIF_REPLY, create_subif_reply)                     		\
 _(RESET_FIB_REPLY, reset_fib_reply)                                     \
-_(DHCP_PROXY_CONFIG_REPLY, dhcp_proxy_config_reply)                     \
-_(DHCP_PROXY_SET_VSS_REPLY, dhcp_proxy_set_vss_reply)                   \
-_(DHCP_PROXY_DETAILS, dhcp_proxy_details)                               \
-_(DHCP_CLIENT_CONFIG_REPLY, dhcp_client_config_reply)                   \
 _(SET_IP_FLOW_HASH_REPLY, set_ip_flow_hash_reply)                       \
 _(SW_INTERFACE_IP6_ENABLE_DISABLE_REPLY,                                \
   sw_interface_ip6_enable_disable_reply)                                \
@@ -5392,7 +5357,6 @@
 _(BD_IP_MAC_ADD_DEL_REPLY, bd_ip_mac_add_del_reply)                     \
 _(BD_IP_MAC_FLUSH_REPLY, bd_ip_mac_flush_reply)                         \
 _(BD_IP_MAC_DETAILS, bd_ip_mac_details)                                 \
-_(DHCP_COMPL_EVENT, dhcp_compl_event)                                   \
 _(WANT_INTERFACE_EVENTS_REPLY, want_interface_events_reply)             \
 _(GET_FIRST_MSG_ID_REPLY, get_first_msg_id_reply)    			\
 _(COP_INTERFACE_ENABLE_DISABLE_REPLY, cop_interface_enable_disable_reply) \
@@ -9279,358 +9243,6 @@
 }
 
 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 void vl_api_dhcp_proxy_details_t_handler_json
-  (vl_api_dhcp_proxy_details_t * mp)
-{
-  vat_main_t *vam = &vat_main;
-  vat_json_node_t *node = NULL;
-  u32 i, count = mp->count;
-  struct in_addr ip4;
-  struct in6_addr ip6;
-  vl_api_dhcp_server_t *s;
-
-  if (VAT_JSON_ARRAY != vam->json_tree.type)
-    {
-      ASSERT (VAT_JSON_NONE == vam->json_tree.type);
-      vat_json_init_array (&vam->json_tree);
-    }
-  node = vat_json_array_add (&vam->json_tree);
-
-  vat_json_init_object (node);
-  vat_json_object_add_uint (node, "rx-table-id", ntohl (mp->rx_vrf_id));
-  vat_json_object_add_uint (node, "vss-type", ntohl (mp->vss_type));
-  vat_json_object_add_string_copy (node, "vss-vpn-ascii-id",
-				   mp->vss_vpn_ascii_id);
-  vat_json_object_add_uint (node, "vss-fib-id", ntohl (mp->vss_fib_id));
-  vat_json_object_add_uint (node, "vss-oui", ntohl (mp->vss_oui));
-
-  if (mp->is_ipv6)
-    {
-      clib_memcpy (&ip6, &mp->dhcp_src_address, sizeof (ip6));
-      vat_json_object_add_ip6 (node, "src_address", ip6);
-    }
-  else
-    {
-      clib_memcpy (&ip4, &mp->dhcp_src_address, sizeof (ip4));
-      vat_json_object_add_ip4 (node, "src_address", ip4);
-    }
-
-  for (i = 0; i < count; i++)
-    {
-      s = &mp->servers[i];
-
-      vat_json_object_add_uint (node, "server-table-id",
-				ntohl (s->server_vrf_id));
-
-      if (mp->is_ipv6)
-	{
-	  clib_memcpy (&ip4, &s->dhcp_server, sizeof (ip4));
-	  vat_json_object_add_ip4 (node, "src_address", ip4);
-	}
-      else
-	{
-	  clib_memcpy (&ip6, &s->dhcp_server, sizeof (ip6));
-	  vat_json_object_add_ip6 (node, "server_address", ip6);
-	}
-    }
-}
-
-static int
-api_dhcp_proxy_dump (vat_main_t * vam)
-{
-  unformat_input_t *i = vam->input;
-  vl_api_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 (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", api_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 int
 api_set_ip_flow_hash (vat_main_t * vam)
 {
   unformat_input_t *i = vam->input;
@@ -21829,14 +21441,6 @@
   "[no_tags][one_tag][two_tags][dot1ad][exact_match][default_sub]\n"    \
   "[outer_vlan_id_any][inner_vlan_id_any]")                             \
 _(reset_fib, "vrf <n> [ipv6]")                                          \
-_(dhcp_proxy_config,                                                    \
-  "svr <v46-address> src <v46-address>\n"                               \
-   "rx_vrf_id <nn> server_vrf_id <nn>  [del]")                          \
-_(dhcp_proxy_set_vss,                                                   \
-  "tbl_id <n> [fib_id <n> oui <n> | vpn_ascii_id <text>] [ipv6] [del]") \
-_(dhcp_proxy_dump, "ip6")                                               \
-_(dhcp_client_config,                                                   \
-  "<intfc> | sw_if_index <id> [hostname <name>] [disable_event] [del]") \
 _(set_ip_flow_hash,                                                     \
   "vrf <n> [src] [dst] [sport] [dport] [proto] [reverse] [ipv6]")       \
 _(sw_interface_ip6_enable_disable,                                      \
diff --git a/src/vnet/CMakeLists.txt b/src/vnet/CMakeLists.txt
index 6ce3833..b107365 100644
--- a/src/vnet/CMakeLists.txt
+++ b/src/vnet/CMakeLists.txt
@@ -891,54 +891,6 @@
 list(APPEND VNET_API_FILES lisp-gpe/lisp_gpe.api)
 
 ##############################################################################
-# DHCP client
-##############################################################################
-list(APPEND VNET_SOURCES
-  dhcp/client.c
-  dhcp/dhcp_client_detect.c
-  dhcp/dhcp6_client_common_dp.c
-  dhcp/dhcp6_pd_client_dp.c
-  dhcp/dhcp6_pd_client_cp.c
-  dhcp/dhcp6_ia_na_client_dp.c
-  dhcp/dhcp6_ia_na_client_cp.c
-  dhcp/dhcp_api.c
-)
-
-list(APPEND VNET_MULTIARCH_SOURCES
-  dhcp/dhcp_client_detect.c
-)
-
-list(APPEND VNET_HEADERS
-  dhcp/client.h
-  dhcp/dhcp6_client_common_dp.h
-  dhcp/dhcp6_pd_client_dp.h
-  dhcp/dhcp6_ia_na_client_dp.h
-)
-
-list(APPEND VNET_API_FILES
-  dhcp/dhcp.api
-  dhcp/dhcp6_pd_client_cp.api
-  dhcp/dhcp6_ia_na_client_cp.api
-)
-
-##############################################################################
-# DHCP proxy
-##############################################################################
-list(APPEND VNET_SOURCES
-  dhcp/dhcp6_proxy_node.c
-  dhcp/dhcp4_proxy_node.c
-  dhcp/dhcp_proxy.c
-)
-
-list(APPEND VNET_HEADERS
-  dhcp/dhcp4_packet.h
-  dhcp/dhcp6_packet.h
-  dhcp/dhcp_proxy.h
-  dhcp/dhcp6_proxy_error.def
-  dhcp/dhcp4_proxy_error.def
-)
-
-##############################################################################
 # ipv6 segment routing
 ##############################################################################
 
diff --git a/src/vnet/dhcp/dhcp_api.c b/src/vnet/dhcp/dhcp_api.c
deleted file mode 100644
index 252f2df..0000000
--- a/src/vnet/dhcp/dhcp_api.c
+++ /dev/null
@@ -1,420 +0,0 @@
-/*
- *------------------------------------------------------------------
- * 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 <vnet/dhcp/dhcp_proxy.h>
-#include <vnet/dhcp/client.h>
-#include <vnet/dhcp/dhcp6_pd_client_dp.h>
-#include <vnet/dhcp/dhcp6_ia_na_client_dp.h>
-#include <vnet/dhcp/dhcp6_client_common_dp.h>
-#include <vnet/fib/fib_table.h>
-#include <vnet/ip/ip_types_api.h>
-
-#include <vnet/vnet_msg_enum.h>
-
-#define vl_typedefs		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_typedefs
-
-#define vl_endianfun		/* define message structures */
-#include <vnet/vnet_all_api_h.h>
-#undef vl_endianfun
-
-/* instantiate all the print functions we know about */
-#define vl_print(handle, ...) vlib_cli_output (handle, __VA_ARGS__)
-#define vl_printfun
-#include <vnet/vnet_all_api_h.h>
-#undef vl_printfun
-
-#include <vlibapi/api_helper_macros.h>
-
-#define foreach_vpe_api_msg                       \
-_(DHCP_PROXY_CONFIG,dhcp_proxy_config)            \
-_(DHCP_PROXY_DUMP,dhcp_proxy_dump)                \
-_(DHCP_PROXY_SET_VSS,dhcp_proxy_set_vss)          \
-_(DHCP_CLIENT_CONFIG, dhcp_client_config)         \
-_(DHCP_CLIENT_DUMP, dhcp_client_dump)             \
-_(WANT_DHCP6_PD_REPLY_EVENTS, want_dhcp6_pd_reply_events)               \
-_(DHCP6_PD_SEND_CLIENT_MESSAGE, dhcp6_pd_send_client_message)           \
-_(WANT_DHCP6_REPLY_EVENTS, want_dhcp6_reply_events)               \
-_(DHCP6_SEND_CLIENT_MESSAGE, dhcp6_send_client_message)           \
-_(DHCP6_CLIENTS_ENABLE_DISABLE, dhcp6_clients_enable_disable)     \
-_(DHCP6_DUID_LL_SET, dhcp6_duid_ll_set)
-
-
-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);
-  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);
-
-  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);
-  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);
-}
-
-/*
- * 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()
- */
-#define vl_msg_name_crc_list
-#include <vnet/vnet_all_api_h.h>
-#undef vl_msg_name_crc_list
-
-static void
-setup_message_id_table (api_main_t * am)
-{
-#define _(id,n,crc) vl_msg_api_add_msg_name_crc (am, #n "_" #crc, id);
-  foreach_vl_msg_name_crc_dhcp;
-#undef _
-}
-
-static clib_error_t *
-dhcp_api_hookup (vlib_main_t * vm)
-{
-  api_main_t *am = &api_main;
-
-#define _(N,n)                                                  \
-    vl_msg_api_set_handlers(VL_API_##N, #n,                     \
-                           vl_api_##n##_t_handler,              \
-                           vl_noop_handler,                     \
-                           vl_api_##n##_t_endian,               \
-                           vl_api_##n##_t_print,                \
-                           sizeof(vl_api_##n##_t), 1);
-  foreach_vpe_api_msg;
-#undef _
-
-  /*
-   * Set up the (msg_name, crc, message-id) table
-   */
-  setup_message_id_table (am);
-
-  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);
-
-/*
- * fd.io coding-style-patch-verification: ON
- *
- * Local Variables:
- * eval: (c-set-style "gnu")
- * End:
- */
diff --git a/src/vnet/vnet_all_api_h.h b/src/vnet/vnet_all_api_h.h
index 3b5140e..519f521 100644
--- a/src/vnet/vnet_all_api_h.h
+++ b/src/vnet/vnet_all_api_h.h
@@ -60,7 +60,6 @@
 #include <vnet/srmpls/sr_mpls.api.h>
 #include <vnet/classify/classify.api.h>
 #include <vnet/ipfix-export/ipfix_export.api.h>
-#include <vnet/dhcp/dhcp.api.h>
 #include <vnet/cop/cop.api.h>
 #include <vnet/policer/policer.api.h>
 #include <vnet/ethernet/p2p_ethernet.api.h>
@@ -71,8 +70,6 @@
 #include <vnet/pg/pg.api.h>
 #include <vnet/feature/feature.api.h>
 #include <vnet/qos/qos.api.h>
-#include <vnet/dhcp/dhcp6_pd_client_cp.api.h>
-#include <vnet/dhcp/dhcp6_ia_na_client_cp.api.h>
 #include <vnet/devices/pipe/pipe.api.h>
 #include <vnet/vxlan-gbp/vxlan_gbp.api.h>
 #include <vnet/syslog/syslog.api.h>
diff --git a/src/vpp/api/custom_dump.c b/src/vpp/api/custom_dump.c
index 0d342a5..3c6eada 100644
--- a/src/vpp/api/custom_dump.c
+++ b/src/vpp/api/custom_dump.c
@@ -24,7 +24,6 @@
 #include <vnet/fib/fib_api.h>
 #include <vnet/unix/tuntap.h>
 #include <vnet/mpls/mpls.h>
-#include <vnet/dhcp/dhcp_proxy.h>
 #include <vnet/l2tp/l2tp.h>
 #include <vnet/l2/l2_input.h>
 #include <vnet/srv6/sr.h>
@@ -1037,75 +1036,6 @@
   FINISH;
 }
 
-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 void *vl_api_set_ip_flow_hash_t_print
   (vl_api_set_ip_flow_hash_t * mp, void *handle)
 {
@@ -3786,8 +3716,6 @@
 _(CREATE_VLAN_SUBIF, create_vlan_subif)                                 \
 _(CREATE_SUBIF, create_subif)                                           \
 _(RESET_FIB, reset_fib)                                                 \
-_(DHCP_PROXY_CONFIG, dhcp_proxy_config)                                 \
-_(DHCP_PROXY_SET_VSS, dhcp_proxy_set_vss)                               \
 _(SET_IP_FLOW_HASH, set_ip_flow_hash)                                   \
 _(SW_INTERFACE_IP6ND_RA_PREFIX, sw_interface_ip6nd_ra_prefix)           \
 _(SW_INTERFACE_IP6ND_RA_CONFIG, sw_interface_ip6nd_ra_config)           \
@@ -3814,7 +3742,6 @@
 _(CLASSIFY_SET_INTERFACE_IP_TABLE, classify_set_interface_ip_table)	\
 _(CLASSIFY_SET_INTERFACE_L2_TABLES, classify_set_interface_l2_tables)	\
 _(ADD_NODE_NEXT, add_node_next)						\
-_(DHCP_CLIENT_CONFIG, dhcp_client_config)	                        \
 _(L2TPV3_CREATE_TUNNEL, l2tpv3_create_tunnel)                           \
 _(L2TPV3_SET_TUNNEL_COOKIES, l2tpv3_set_tunnel_cookies)                 \
 _(L2TPV3_INTERFACE_ENABLE_DISABLE, l2tpv3_interface_enable_disable)     \