linux-cp: Complete interface creation logic 81/33481/23
authorPim van Pelt <pim@ipng.nl>
Thu, 12 Aug 2021 19:35:06 +0000 (21:35 +0200)
committerMatthew Smith <mgsmith@netgate.com>
Thu, 9 Sep 2021 13:02:34 +0000 (13:02 +0000)
Linux Control Plane interface creation logic is currently only able to
create untagged interfaces, and dot1q VLAN sub-interfaces. This change
makes it possible to create dot1ad VLAN sub-ints, and Q-in-AD as well
as Q-in-Q sub-interfaces as well.

It makes the plugin a bit more robust by catching a few common errors,
such as creating an lcp on a sub-interface without its parent having
one, and creating an lcp on a sub-interface that is not exact-match.

This change has a bunch of smaller improvemnets as well. I documented
my work in this post:
  https://ipng.ch/s/articles/2021/08/12/vpp-1.html

It shows that after the change is merged, all VPP interface types now
create and operate cleanly as LCP interfaces as well.

Type: improvement
Signed-off-by: Pim van Pelt <pim@ipng.nl>
Change-Id: I322669f7316d44c227090b83d6a574fb9c00e76a

src/plugins/linux-cp/lcp.c
src/plugins/linux-cp/lcp.h
src/plugins/linux-cp/lcp.rst
src/plugins/linux-cp/lcp_adj.c
src/plugins/linux-cp/lcp_api.c
src/plugins/linux-cp/lcp_interface.c

index f4c491c..a4d3faf 100644 (file)
@@ -28,8 +28,9 @@ lcp_get_default_ns (void)
 {
   lcp_main_t *lcpm = &lcp_main;
 
-  if (lcpm->default_namespace[0] == 0)
-    return 0;
+  if (!lcpm->default_namespace || lcpm->default_namespace[0] == 0)
+    return NULL;
+
   return lcpm->default_namespace;
 }
 
@@ -59,16 +60,15 @@ lcp_set_default_ns (u8 *ns)
 
   if (!p || *p == 0)
     {
-      clib_memset (lcpm->default_namespace, 0,
-                  sizeof (lcpm->default_namespace));
+      lcpm->default_namespace = NULL;
       if (lcpm->default_ns_fd > 0)
        close (lcpm->default_ns_fd);
       lcpm->default_ns_fd = 0;
       return 0;
     }
 
-  clib_strncpy ((char *) lcpm->default_namespace, p, LCP_NS_LEN - 1);
-
+  vec_validate_init_c_string (lcpm->default_namespace, p,
+                             clib_strnlen (p, LCP_NS_LEN));
   s = format (0, "/var/run/netns/%s%c", (char *) lcpm->default_namespace, 0);
   lcpm->default_ns_fd = open ((char *) s, O_RDONLY);
   vec_free (s);
index 7fdad37..19636c5 100644 (file)
@@ -22,9 +22,8 @@
 typedef struct lcp_main_s
 {
   u16 msg_id_base;                 /* API message ID base */
-  u8 default_namespace[LCP_NS_LEN]; /* default namespace if set */
+  u8 *default_namespace;           /* default namespace if set */
   int default_ns_fd;
-  u8 auto_intf;
   /* Set when Unit testing */
   u8 test_mode;
 } lcp_main_t;
index 6d81901..f199812 100644 (file)
@@ -8,9 +8,9 @@ Linux Control Plane Integration
 Overview
 ________
 
-This plugin allows VPP to integrate with the Linux. The
+This plugin allows VPP to integrate with the Linux kernel. The
 general model is that Linux is the network stack, i.e. it has the
-control plane protocols, like ARP, IPv6 ND/MLD, Ping, etc, and VPP
+control plane protocols, like ARP, IPv6 ND/MLD, ping, etc, and VPP
 provides a SW based ASIC for forwarding.
 
 Interfaces
@@ -20,16 +20,17 @@ VPP owns the interfaces in the system; physical (.e.g PCI), quasi
 physical (e.g. vhost), or virtual (e.g. tunnel). However,
 for the Linux networking stack to function it needs a representation
 of these interfaces; it needs a mirror image in the kernel. For this
-mirror we use a Tap interface, if the VPP interface is multi-point, a
-Tun if it's point-to-point. A physical and its mirror form an
+mirror we use a TAP interface, if the VPP interface is multi-point, a
+TUN if it's point-to-point. A physical and its mirror form an
 interface 'pair'.
 
-The host interface has two identities; the sw_if_index of the Tap and
-the virtual interface index in the kernel. It may be in a Linux namespace.
+The host interface has two identities; the sw_if_index of the TAP and
+the virtual interface index in the kernel. It may be in a Linux network
+namespace.
 
 The creation of the interface pairs is required from the control
 plane. It can be statically configured in the VPP startup
-configuration file. The intent here was to make the pair creation
+configuration file. The intent here is to make the pair creation
 explicit, rather than have VPP guess which of the interfaces it owns
 require a mirror.
 
@@ -46,22 +47,21 @@ achieved in various ways, for example by listening to the netlink
 messages and applying the config. As a result all e.g. routes
 programmed in Linux, will also be present in VPP's FIB.
 
-Linux will own the [ARP/ND] nieghbor tables (which will be copied via
+Linux will own the [ARP/ND] neighbor tables (which will be copied via
 netlink to VPP also). This means that Linux will send packets with the
 peer's MAC address in the rewrite to VPP. The receiving TAP interface
 must therefore be in promiscuous mode.
 
-
 Forwarding
 __________
 
 The basic principle is to x-connect traffic from a Linux host interface
-(received on the Tap/Tun) to its paired the physical, and vice-versa.
+(received on the tap/tun) to its paired the physical, and vice-versa.
 
 Host to Physical
 ^^^^^^^^^^^^^^^^
 
-All packets sent by the host, and received by VPP on a Tap/Tun should
+All packets sent by the host, and received by VPP on a tap/tun should
 be sent to its paired physical interface. However, they should be sent
 with the same consequences as if they had originated from VPP,
 i.e. they should be subject to all output features on the physical
@@ -73,17 +73,18 @@ adjacency that VPP would have used to send this packet; this adjacency
 is stored in the buffer's meta data so that it is available to all
 output features. Then the packet is sent through the physical
 interface's IP output feature arc.
+
 All ARP packets are x-connected from the tap to the physical.
 
 Physical to Host
 ^^^^^^^^^^^^^^^^
 
 All ARP packets received on the physical are sent to the paired
-Tap. This allows the Linux network stack to build the nieghbour table.
+tap. This allows the Linux network stack to build the neighbor table.
 
 IP packets that are punted are sent to the host. They are sent on the
 tap that is paired with the physical on which they were originally
-received. The packet is sent on the Tap/Tun 'exactly' as it was
+received. The packet is sent on the tap/tun 'exactly' as it was
 received (i.e. with the L2 rewrite) but post any translations that
 input features may have made.
 
@@ -92,5 +93,4 @@ Recommendations
 ^^^^^^^^^^^^^^^
 
 When using this plugin disable the ARP, ND, IGMP plugins; this is the
-task for Linux.
-Disable ping plugin, since Linux will now respond.
+task for Linux.  Disable ping plugin, since Linux will now respond.
index bfbc2fe..b10c706 100644 (file)
@@ -185,8 +185,8 @@ lcp_adj_show_cmd (vlib_main_t *vm, unformat_input_t *input,
   if (unformat (input, "verbose"))
     verbose = 1;
 
-  vlib_cli_output (vm, "Linux-CP Adjs:\n%U", BV (format_bihash), &lcp_adj_tbl,
-                  verbose);
+  vlib_cli_output (vm, "linux-cp adjacencies:\n%U", BV (format_bihash),
+                  &lcp_adj_tbl, verbose);
 
   return 0;
 }
@@ -210,7 +210,7 @@ lcp_adj_init (vlib_main_t *vm)
 {
   adj_type = adj_delegate_register_new_type (&lcp_adj_vft);
 
-  BV (clib_bihash_init) (&lcp_adj_tbl, "linux-cp ADJ table", 1024, 1 << 24);
+  BV (clib_bihash_init) (&lcp_adj_tbl, "linux-cp adjacencies", 1024, 1 << 24);
   BV (clib_bihash_set_kvp_format_fn) (&lcp_adj_tbl, format_lcp_adj_kvp);
 
   return (NULL);
index c9aa015..7f77bf7 100644 (file)
@@ -41,22 +41,6 @@ api_encode_host_type (lip_host_type_t type)
   return LCP_API_ITF_HOST_TAP;
 }
 
-void
-lcp_set_auto_intf (u8 is_auto)
-{
-  lcp_main_t *lcpm = &lcp_main;
-
-  lcpm->auto_intf = (is_auto != 0);
-}
-
-int
-lcp_auto_intf (void)
-{
-  lcp_main_t *lcpm = &lcp_main;
-
-  return lcpm->auto_intf;
-}
-
 static int
 vl_api_lcp_itf_pair_add (u32 phy_sw_if_index, lip_host_type_t lip_host_type,
                         u8 *mp_host_if_name, size_t sizeof_host_if_name,
index da40961..aeff9a1 100644 (file)
@@ -21,6 +21,7 @@
 
 #include <linux-cp/lcp_interface.h>
 #include <netlink/route/link/vlan.h>
+#include <linux/if_ether.h>
 
 #include <vnet/plugin/plugin.h>
 #include <vnet/plugin/plugin.h>
@@ -43,7 +44,7 @@ static vlib_log_class_t lcp_itf_pair_logger;
 /**
  * Pool of LIP objects
  */
-lcp_itf_pair_t *lcp_itf_pair_pool;
+lcp_itf_pair_t *lcp_itf_pair_pool = NULL;
 
 u32
 lcp_itf_num_pairs (void)
@@ -78,6 +79,8 @@ lcp_itf_pair_register_vft (lcp_itf_pair_vft_t *lcp_itf_vft)
 #define LCP_ITF_PAIR_INFO(...)                                                \
   vlib_log_notice (lcp_itf_pair_logger, __VA_ARGS__);
 
+#define LCP_ITF_PAIR_ERR(...) vlib_log_err (lcp_itf_pair_logger, __VA_ARGS__);
+
 u8 *
 format_lcp_itf_pair (u8 *s, va_list *args)
 {
@@ -152,6 +155,9 @@ lcp_itf_pair_show (u32 phy_sw_if_index)
 lcp_itf_pair_t *
 lcp_itf_pair_get (u32 index)
 {
+  if (!lcp_itf_pair_pool)
+    return NULL;
+
   return pool_elt_at_index (lcp_itf_pair_pool, index);
 }
 
@@ -175,6 +181,13 @@ lcp_itf_pair_add_sub (u32 vif, u8 *host_if_name, u32 sub_sw_if_index,
   lcp_itf_pair_t *lip;
 
   lip = lcp_itf_pair_get (lcp_itf_pair_find_by_phy (phy_sw_if_index));
+  if (!lip)
+    {
+      LCP_ITF_PAIR_DBG ("lcp_itf_pair_add_sub: can't find LCP of parent %U",
+                       format_vnet_sw_if_index_name, vnet_get_main (),
+                       phy_sw_if_index);
+      return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+    }
 
   return lcp_itf_pair_add (lip->lip_host_sw_if_index, sub_sw_if_index,
                           host_if_name, vif, lip->lip_host_type, ns);
@@ -237,7 +250,7 @@ lcp_itf_pair_add (u32 host_sw_if_index, u32 phy_sw_if_index, u8 *host_name,
 
   lipi = lcp_itf_pair_find_by_phy (phy_sw_if_index);
 
-  LCP_ITF_PAIR_INFO ("add: host:%U phy:%U, host_if:%v vif:%d ns:%v",
+  LCP_ITF_PAIR_INFO ("add: host:%U phy:%U, host_if:%v vif:%d ns:%s",
                     format_vnet_sw_if_index_name, vnet_get_main (),
                     host_sw_if_index, format_vnet_sw_if_index_name,
                     vnet_get_main (), phy_sw_if_index, host_name, host_index,
@@ -336,7 +349,7 @@ lcp_itf_pair_add (u32 host_sw_if_index, u32 phy_sw_if_index, u8 *host_name,
 }
 
 static clib_error_t *
-lcp_netlink_add_link_vlan (int parent, u32 vlan, const char *name)
+lcp_netlink_add_link_vlan (int parent, u32 vlan, u16 proto, const char *name)
 {
   struct rtnl_link *link;
   struct nl_sock *sk;
@@ -344,17 +357,25 @@ lcp_netlink_add_link_vlan (int parent, u32 vlan, const char *name)
 
   sk = nl_socket_alloc ();
   if ((err = nl_connect (sk, NETLINK_ROUTE)) < 0)
-    return clib_error_return (NULL, "Unable to connect socket: %d", err);
+    {
+      LCP_ITF_PAIR_ERR ("netlink_add_link_vlan: connect error: %s",
+                       nl_geterror (err));
+      return clib_error_return (NULL, "Unable to connect socket: %d", err);
+    }
 
   link = rtnl_link_vlan_alloc ();
 
   rtnl_link_set_link (link, parent);
   rtnl_link_set_name (link, name);
-
   rtnl_link_vlan_set_id (link, vlan);
+  rtnl_link_vlan_set_protocol (link, htons (proto));
 
   if ((err = rtnl_link_add (sk, link, NLM_F_CREATE)) < 0)
-    return clib_error_return (NULL, "Unable to add link %s: %d", name, err);
+    {
+      LCP_ITF_PAIR_ERR ("netlink_add_link_vlan: link add error: %s",
+                       nl_geterror (err));
+      return clib_error_return (NULL, "Unable to add link %s: %d", name, err);
+    }
 
   rtnl_link_put (link);
   nl_close (sk);
@@ -531,7 +552,7 @@ lcp_itf_pair_config (vlib_main_t *vm, unformat_input_t *input)
          if (vec_len (ns) > LCP_NS_LEN)
            {
              return clib_error_return (0,
-                                       "linux-cp IF namespace must"
+                                       "linux-cp namespace must"
                                        " be less than %d characters",
                                        LCP_NS_LEN);
            }
@@ -616,21 +637,24 @@ lcp_validate_if_name (u8 *name)
 }
 
 static void
-lcp_itf_set_vif_link_state (u32 vif_index, u8 up, u8 *ns)
+lcp_itf_set_link_state (const lcp_itf_pair_t *lip, u8 state)
 {
   int curr_ns_fd, vif_ns_fd;
 
+  if (!lip)
+    return;
+
   curr_ns_fd = vif_ns_fd = -1;
 
-  if (ns)
+  if (lip->lip_namespace)
     {
       curr_ns_fd = clib_netns_open (NULL /* self */);
-      vif_ns_fd = clib_netns_open (ns);
+      vif_ns_fd = clib_netns_open (lip->lip_namespace);
       if (vif_ns_fd != -1)
        clib_setns (vif_ns_fd);
     }
 
-  vnet_netlink_set_link_state (vif_index, up);
+  vnet_netlink_set_link_state (lip->lip_vif_index, state);
 
   if (vif_ns_fd != -1)
     close (vif_ns_fd);
@@ -640,6 +664,63 @@ lcp_itf_set_vif_link_state (u32 vif_index, u8 up, u8 *ns)
       clib_setns (curr_ns_fd);
       close (curr_ns_fd);
     }
+
+  return;
+}
+
+typedef struct
+{
+  u32 vlan;
+  bool dot1ad;
+
+  u32 matched_sw_if_index;
+} lcp_itf_match_t;
+
+static walk_rc_t
+lcp_itf_pair_find_walk (vnet_main_t *vnm, u32 sw_if_index, void *arg)
+{
+  lcp_itf_match_t *match = arg;
+  const vnet_sw_interface_t *sw;
+
+  sw = vnet_get_sw_interface (vnm, sw_if_index);
+  if (sw && (sw->sub.eth.inner_vlan_id == 0) &&
+      (sw->sub.eth.outer_vlan_id == match->vlan) &&
+      (sw->sub.eth.flags.dot1ad == match->dot1ad))
+    {
+      LCP_ITF_PAIR_DBG ("find_walk: found match outer %d dot1ad %d "
+                       "inner-dot1q %d: interface %U",
+                       sw->sub.eth.outer_vlan_id, sw->sub.eth.flags.dot1ad,
+                       sw->sub.eth.inner_vlan_id,
+                       format_vnet_sw_if_index_name, vnet_get_main (),
+                       sw->sw_if_index);
+      match->matched_sw_if_index = sw->sw_if_index;
+      return WALK_STOP;
+    }
+
+  return WALK_CONTINUE;
+}
+
+/* Return the index of the sub-int on the phy that has the given vlan and
+ * proto,
+ */
+static index_t
+lcp_itf_pair_find_by_outer_vlan (u32 sup_if_index, u16 vlan, bool dot1ad)
+{
+  lcp_itf_match_t match;
+  const vnet_hw_interface_t *hw;
+
+  match.vlan = vlan;
+  match.dot1ad = dot1ad;
+  match.matched_sw_if_index = INDEX_INVALID;
+  hw = vnet_get_sup_hw_interface (vnet_get_main (), sup_if_index);
+
+  vnet_hw_interface_walk_sw (vnet_get_main (), hw->hw_if_index,
+                            lcp_itf_pair_find_walk, &match);
+
+  if (match.matched_sw_if_index >= vec_len (lip_db_by_phy))
+    return INDEX_INVALID;
+
+  return lip_db_by_phy[match.matched_sw_if_index];
 }
 
 int
@@ -652,21 +733,33 @@ lcp_itf_pair_create (u32 phy_sw_if_index, u8 *host_if_name,
   u32 vif_index = 0, host_sw_if_index;
   const vnet_sw_interface_t *sw;
   const vnet_hw_interface_t *hw;
+  const lcp_itf_pair_t *lip;
 
   if (!vnet_sw_if_index_is_api_valid (phy_sw_if_index))
-    return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+    {
+      LCP_ITF_PAIR_ERR ("pair_create: invalid phy index %u", phy_sw_if_index);
+      return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+    }
 
   if (!lcp_validate_if_name (host_if_name))
-    return VNET_API_ERROR_INVALID_ARGUMENT;
+    {
+      LCP_ITF_PAIR_ERR ("pair_create: invalid host-if-name '%s'",
+                       host_if_name);
+      return VNET_API_ERROR_INVALID_ARGUMENT;
+    }
 
   vnm = vnet_get_main ();
   sw = vnet_get_sw_interface (vnm, phy_sw_if_index);
   hw = vnet_get_sup_hw_interface (vnm, phy_sw_if_index);
+  if (!sw || !hw)
+    {
+      LCP_ITF_PAIR_ERR ("pair_create: invalid interface");
+      return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+    }
 
   /*
    * Use interface-specific netns if supplied.
-   * Otherwise, use default netns if defined.
-   * Otherwise ignore a netns and use the OS default.
+   * Otherwise, use netns if defined, otherwise use the OS default.
    */
   if (ns == 0 || ns[0] == 0)
     ns = lcp_get_default_ns ();
@@ -674,16 +767,51 @@ lcp_itf_pair_create (u32 phy_sw_if_index, u8 *host_if_name,
   /* sub interfaces do not need a tap created */
   if (vnet_sw_interface_is_sub (vnm, phy_sw_if_index))
     {
-      const lcp_itf_pair_t *lip;
+      index_t parent_if_index;
       int orig_ns_fd, ns_fd;
       clib_error_t *err;
-      u16 vlan;
+      u16 outer_vlan, inner_vlan;
+      u16 outer_proto, inner_proto;
+      u16 vlan, proto;
+      u32 parent_vif_index;
+
+      // TODO(pim) replace with vnet_sw_interface_supports_addressing()
+      if (sw->type == VNET_SW_INTERFACE_TYPE_SUB &&
+         sw->sub.eth.flags.exact_match == 0)
+       {
+         LCP_ITF_PAIR_ERR ("pair_create: can't create LCP for a "
+                           "sub-interface without exact-match set");
+         return VNET_API_ERROR_INVALID_ARGUMENT;
+       }
 
-      /*
-       * Find the parent tap by finding the pair from the parent phy
-       */
-      lip = lcp_itf_pair_get (lcp_itf_pair_find_by_phy (sw->sup_sw_if_index));
-      vlan = sw->sub.eth.outer_vlan_id;
+      outer_vlan = sw->sub.eth.outer_vlan_id;
+      inner_vlan = sw->sub.eth.inner_vlan_id;
+      outer_proto = inner_proto = ETH_P_8021Q;
+      if (1 == sw->sub.eth.flags.dot1ad)
+       outer_proto = ETH_P_8021AD;
+
+      LCP_ITF_PAIR_INFO ("pair_create: subif: dot1%s outer %d inner %d on %U",
+                        sw->sub.eth.flags.dot1ad ? "ad" : "q", outer_vlan,
+                        inner_vlan, format_vnet_sw_if_index_name, vnm,
+                        hw->sw_if_index);
+
+      parent_if_index = lcp_itf_pair_find_by_phy (sw->sup_sw_if_index);
+      if (INDEX_INVALID == parent_if_index)
+       {
+         LCP_ITF_PAIR_ERR ("pair_create: can't find LCP for %U",
+                           format_vnet_sw_if_index_name, vnet_get_main (),
+                           sw->sup_sw_if_index);
+         return VNET_API_ERROR_INVALID_SW_IF_INDEX;
+       }
+      lip = lcp_itf_pair_get (parent_if_index);
+      if (!lip)
+       {
+         LCP_ITF_PAIR_ERR ("pair_create: can't create LCP for a "
+                           "sub-interface without an LCP on the parent");
+         return VNET_API_ERROR_INVALID_ARGUMENT;
+       }
+      LCP_ITF_PAIR_DBG ("pair_create: parent %U", format_lcp_itf_pair, lip);
+      parent_vif_index = lip->lip_vif_index;
 
       /*
        * see if the requested host interface has already been created
@@ -708,11 +836,56 @@ lcp_itf_pair_create (u32 phy_sw_if_index, u8 *host_if_name,
          /*
           * no existing host interface, create it now
           */
-         err = lcp_netlink_add_link_vlan (lip->lip_vif_index, vlan,
-                                          (const char *) host_if_name);
 
-         if (!err && -1 != ns_fd)
-           err = vnet_netlink_set_link_netns (vif_index, ns_fd, NULL);
+         /*
+          * Find the parent tap:
+          * - if this is an outer VLAN, use the pair from the parent phy
+          * - if this is an inner VLAN, find the pair from the outer sub-int,
+          * which must exist.
+          */
+         if (inner_vlan)
+           {
+             index_t linux_parent_if_index;
+             const lcp_itf_pair_t *llip;
+
+             vlan = inner_vlan;
+             proto = inner_proto;
+             linux_parent_if_index = lcp_itf_pair_find_by_outer_vlan (
+               hw->sw_if_index, sw->sub.eth.outer_vlan_id,
+               sw->sub.eth.flags.dot1ad);
+             if (INDEX_INVALID == linux_parent_if_index ||
+                 !(llip = lcp_itf_pair_get (linux_parent_if_index)))
+               {
+                 LCP_ITF_PAIR_ERR (
+                   "pair_create: can't find LCP for outer vlan %d "
+                   "proto %s on %U",
+                   outer_vlan,
+                   outer_proto == ETH_P_8021AD ? "dot1ad" : "dot1q",
+                   format_vnet_sw_if_index_name, vnm, hw->sw_if_index);
+                 err = clib_error_return (0, "parent pair not found");
+                 goto socket_close;
+               }
+
+             LCP_ITF_PAIR_DBG ("pair_create: linux parent %U",
+                               format_lcp_itf_pair, llip);
+             parent_vif_index = llip->lip_vif_index;
+           }
+         else
+           {
+             vlan = outer_vlan;
+             proto = outer_proto;
+           }
+
+         err = lcp_netlink_add_link_vlan (parent_vif_index, vlan, proto,
+                                          (const char *) host_if_name);
+         if (err != 0)
+           {
+             LCP_ITF_PAIR_ERR ("pair_create: cannot create link "
+                               "outer(proto:0x%04x,vlan:%u).inner(proto:0x%"
+                               "04x,vlan:%u) name:'%s'",
+                               outer_proto, outer_vlan, inner_proto,
+                               inner_vlan, host_if_name);
+           }
 
          if (!err)
            vif_index = if_nametoindex ((char *) host_if_name);
@@ -721,13 +894,20 @@ lcp_itf_pair_create (u32 phy_sw_if_index, u8 *host_if_name,
       /*
        * create a sub-interface on the tap
        */
-      if (!err && vnet_create_sub_interface (lip->lip_host_sw_if_index,
-                                            sw->sub.id, sw->sub.eth.raw_flags,
-                                            sw->sub.eth.inner_vlan_id, vlan,
-                                            &host_sw_if_index))
-       LCP_ITF_PAIR_INFO ("failed create vlan: %d on %U", vlan,
-                          format_vnet_sw_if_index_name, vnet_get_main (),
-                          lip->lip_host_sw_if_index);
+      if (!err &&
+         vnet_create_sub_interface (lip->lip_host_sw_if_index, sw->sub.id,
+                                    sw->sub.eth.raw_flags, inner_vlan,
+                                    outer_vlan, &host_sw_if_index))
+       {
+         LCP_ITF_PAIR_ERR (
+           "pair_create: failed to create tap subint: %d.%d on %U",
+           outer_vlan, inner_vlan, format_vnet_sw_if_index_name, vnm,
+           lip->lip_host_sw_if_index);
+         err = clib_error_return (
+           0, "failed to create tap subinti: %d.%d. on %U", outer_vlan,
+           inner_vlan, format_vnet_sw_if_index_name, vnm,
+           lip->lip_host_sw_if_index);
+       }
 
     socket_close:
       if (orig_ns_fd != -1)
@@ -776,24 +956,33 @@ lcp_itf_pair_create (u32 phy_sw_if_index, u8 *host_if_name,
 
       if (args.rv < 0)
        {
+         LCP_ITF_PAIR_ERR ("pair_create: could not create tap, retval:%d",
+                           args.rv);
          return args.rv;
        }
 
       /*
-       * get the hw and ethernet of the tap
+       * The TAP interface does copy forward the host MTU based on the VPP
+       * interface's L3 MTU, but it should also ensure that the VPP tap
+       * interface has an MTU that is greater-or-equal to those. Considering
+       * users can set the interfaces at runtime (set interface mtu packet ...)
+       * ensure that the tap MTU is large enough, taking the VPP interface L3
+       * if it's set, and otherwise a sensible default.
        */
-      hw = vnet_get_sup_hw_interface (vnm, args.sw_if_index);
+      if (sw->mtu[VNET_MTU_L3])
+       vnet_sw_interface_set_mtu (vnm, args.sw_if_index,
+                                  sw->mtu[VNET_MTU_L3]);
+      else
+       vnet_sw_interface_set_mtu (vnm, args.sw_if_index,
+                                  ETHERNET_MAX_PACKET_BYTES);
 
       /*
-       * Set the interface down on the host side.
-       * This controls whether the host can RX/TX.
+       * get the hw and ethernet of the tap
        */
+      hw = vnet_get_sup_hw_interface (vnm, args.sw_if_index);
       virtio_main_t *mm = &virtio_main;
       virtio_if_t *vif = pool_elt_at_index (mm->interfaces, hw->dev_instance);
 
-      lcp_itf_set_vif_link_state (vif->ifindex, 0 /* down */,
-                                 args.host_namespace);
-
       /*
        * Leave the TAP permanently up on the VPP side.
        * This TAP will be shared by many sub-interface.
@@ -819,14 +1008,24 @@ lcp_itf_pair_create (u32 phy_sw_if_index, u8 *host_if_name,
       return -1;
     }
 
-  vnet_sw_interface_admin_up (vnm, host_sw_if_index);
-  lcp_itf_pair_add (host_sw_if_index, phy_sw_if_index, host_if_name, vif_index,
-                   host_if_type, ns);
-
   LCP_ITF_PAIR_INFO ("pair create: {%U, %U, %s}", format_vnet_sw_if_index_name,
                     vnet_get_main (), phy_sw_if_index,
                     format_vnet_sw_if_index_name, vnet_get_main (),
                     host_sw_if_index, host_if_name);
+  lcp_itf_pair_add (host_sw_if_index, phy_sw_if_index, host_if_name, vif_index,
+                   host_if_type, ns);
+
+  /*
+   * Copy the link state from VPP into the host side.
+   * The TAP is shared by many interfaces, always keep it up.
+   * This controls whether the host can RX/TX.
+   */
+
+  lip = lcp_itf_pair_get (lcp_itf_pair_find_by_vif (vif_index));
+  LCP_ITF_PAIR_INFO ("pair create: %U sw-flags %u hw-flags %u",
+                    format_lcp_itf_pair, lip, sw->flags, hw->flags);
+  vnet_sw_interface_admin_up (vnm, host_sw_if_index);
+  lcp_itf_set_link_state (lip, sw->flags & VNET_SW_INTERFACE_FLAG_ADMIN_UP);
 
   if (host_sw_if_indexp)
     *host_sw_if_indexp = host_sw_if_index;