[ovs-dev] [PATCH v9 7/7] userspace: add non-tap (l3) support to GRE vports

Simon Horman simon.horman at netronome.com
Wed Jan 20 06:15:06 UTC 2016


Add support for layer 3 GRE vports (non-tap aka non-VTEP).

This makes use of a separate vport type for GRE, rather than a new mode for
the existing (tap/VTEP) GRE vports as this fits more naturally with the
kernel where implementation of GRE and thus implementation of this feature
there.

In order to differentiate packets for two different types of GRE vports a
new flow key attribute, OVS_KEY_ATTR_NEXT_BASE_LAYER, is used.  It is
intended that this attribute is only used in userspace as there appears to
be no need for it to be used in the kernel datapath.

It is envisaged that this attribute may be used for other non-UDP
encapsulation protocols that support both layer3 and layer2 inner-packets.
While for UDP encapsulation the UDP port can be used for differentiation
without the need for this new attribute.

One alternative approach to this new attribute, which I have not
investigated in detail, would be to use a second classifier in tnl-ports.c
for non-UDP layer3 tunnels; leaving the existing classifier for all other
tunnels.

Signed-off-by: Simon Horman <simon.horman at netronome.com>

---
v9
* New patch
---
 datapath-windows/ovsext/Vport.c                   |  4 ++
 datapath/linux/compat/include/linux/openvswitch.h |  4 +-
 lib/dpif-netlink.c                                |  5 ++
 lib/flow.c                                        | 18 +++++
 lib/flow.h                                        |  4 ++
 lib/match.c                                       |  4 ++
 lib/netdev-linux.c                                |  3 +-
 lib/netdev-vport.c                                | 88 +++++++++++++++++++----
 lib/netdev-vport.h                                |  1 +
 lib/odp-execute.c                                 |  2 +
 lib/odp-util.c                                    | 21 ++++++
 lib/tnl-ports.c                                   | 74 +++++++++++++------
 lib/tnl-ports.h                                   |  4 +-
 ofproto/ofproto-dpif-ipfix.c                      |  2 +-
 ofproto/ofproto-dpif-sflow.c                      |  3 +-
 ofproto/tunnel.c                                  |  7 +-
 tests/tunnel-push-pop-ipv6.at                     | 20 +++---
 tests/tunnel-push-pop.at                          | 33 +++++++--
 18 files changed, 240 insertions(+), 57 deletions(-)

diff --git a/datapath-windows/ovsext/Vport.c b/datapath-windows/ovsext/Vport.c
index 7b0103d6b523..fc2299f1c051 100644
--- a/datapath-windows/ovsext/Vport.c
+++ b/datapath-windows/ovsext/Vport.c
@@ -1005,6 +1005,8 @@ OvsInitTunnelVport(PVOID userContext,
     case OVS_VPORT_TYPE_GRE:
         status = OvsInitGreTunnel(vport);
         break;
+    case OVS_VPORT_TYPE_GRE_L3:
+        break;
     case OVS_VPORT_TYPE_VXLAN:
     {
         POVS_TUNFLT_INIT_CONTEXT tunnelContext = NULL;
@@ -1266,6 +1268,8 @@ OvsRemoveAndDeleteVport(PVOID usrParamsContext,
     case OVS_VPORT_TYPE_GRE:
         OvsCleanupGreTunnel(vport);
         break;
+    case OVS_VPORT_TYPE_GRE_L3:
+        break;
     case OVS_VPORT_TYPE_NETDEV:
         if (vport->isExternal) {
             if (vport->nicIndex == 0) {
diff --git a/datapath/linux/compat/include/linux/openvswitch.h b/datapath/linux/compat/include/linux/openvswitch.h
index 502e8f1aca66..f68697f578f3 100644
--- a/datapath/linux/compat/include/linux/openvswitch.h
+++ b/datapath/linux/compat/include/linux/openvswitch.h
@@ -230,9 +230,10 @@ enum ovs_vport_type {
 	OVS_VPORT_TYPE_UNSPEC,
 	OVS_VPORT_TYPE_NETDEV,   /* network device */
 	OVS_VPORT_TYPE_INTERNAL, /* network device implemented by datapath */
-	OVS_VPORT_TYPE_GRE,      /* GRE tunnel. */
+	OVS_VPORT_TYPE_GRE,      /* GRE Tap tunnel (L2 in GRE). */
 	OVS_VPORT_TYPE_VXLAN,	 /* VXLAN tunnel. */
 	OVS_VPORT_TYPE_GENEVE,	 /* Geneve tunnel. */
+	OVS_VPORT_TYPE_GRE_L3,   /* GRE tunnel (L3 in GRE). */
 	OVS_VPORT_TYPE_LISP = 105,  /* LISP tunnel */
 	OVS_VPORT_TYPE_STT = 106, /* STT tunnel */
 	__OVS_VPORT_TYPE_MAX
@@ -354,6 +355,7 @@ enum ovs_key_attr {
 	OVS_KEY_ATTR_CT_LABELS,	/* 16-octet connection tracking labels */
 	OVS_KEY_ATTR_PACKET_ETHERTYPE, /* be16 Ethernet type for packet
 					* execution. */
+	OVS_KEY_ATTR_NEXT_BASE_LAYER, /* base layer of encapsulated packet */
 
 #ifdef __KERNEL__
 	/* Only used within kernel data path. */
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index bab2297541ac..3cd273d13384 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -760,6 +760,9 @@ get_vport_type(const struct dpif_netlink_vport *vport)
     case OVS_VPORT_TYPE_GRE:
         return "gre";
 
+    case OVS_VPORT_TYPE_GRE_L3:
+        return "l3gre";
+
     case OVS_VPORT_TYPE_VXLAN:
         return "vxlan";
 
@@ -792,6 +795,8 @@ netdev_to_ovs_vport_type(const struct netdev *netdev)
         return OVS_VPORT_TYPE_STT;
     } else if (!strcmp(type, "geneve")) {
         return OVS_VPORT_TYPE_GENEVE;
+    } else if (!strcmp(type, "l3gre")) { /* Must be before search for "gre" */
+        return OVS_VPORT_TYPE_GRE_L3;
     } else if (strstr(type, "gre")) {
         return OVS_VPORT_TYPE_GRE;
     } else if (!strcmp(type, "vxlan")) {
diff --git a/lib/flow.c b/lib/flow.c
index 4cd7ebedb9c9..09544bbef904 100644
--- a/lib/flow.c
+++ b/lib/flow.c
@@ -821,6 +821,20 @@ miniflow_extract(struct dp_packet *packet, struct miniflow *dst)
                 miniflow_push_be16(mf, tp_dst, htons(icmp->icmp6_code));
                 miniflow_pad_to_64(mf, tp_dst);
             }
+        } else if (OVS_LIKELY(nw_proto == IPPROTO_GRE)) {
+            if (OVS_LIKELY(size >= sizeof(struct gre_base_hdr))) {
+                const struct gre_base_hdr *gre = data_pull(&data, &size,
+                                                           sizeof *gre);
+			    if (gre->protocol == htons(ETH_TYPE_TEB)) {
+                    /* No need to store a zero value for next_base_layer
+                     * in the miniflow which would cost an extra word of
+                     * storage. */
+                    BUILD_ASSERT(LAYER_2 == 0);
+                } else {
+                    miniflow_push_uint8(mf, next_base_layer, LAYER_3);
+                    miniflow_pad_to_64(mf, next_base_layer);
+                }
+            }
         }
     }
  out:
@@ -1435,6 +1449,8 @@ flow_wc_map(const struct flow *flow, struct flowmap *map)
 
         if (OVS_UNLIKELY(flow->nw_proto == IPPROTO_IGMP)) {
             FLOWMAP_SET(map, igmp_group_ip4);
+        } else if (OVS_UNLIKELY(flow->nw_proto == IPPROTO_GRE)) {
+            FLOWMAP_SET(map, next_base_layer);
         } else {
             FLOWMAP_SET(map, tcp_flags);
             FLOWMAP_SET(map, tp_src);
@@ -1453,6 +1469,8 @@ flow_wc_map(const struct flow *flow, struct flowmap *map)
             FLOWMAP_SET(map, nd_target);
             FLOWMAP_SET(map, arp_sha);
             FLOWMAP_SET(map, arp_tha);
+        } else if (OVS_UNLIKELY(flow->nw_proto == IPPROTO_GRE)) {
+            FLOWMAP_SET(map, next_base_layer);
         } else {
             FLOWMAP_SET(map, tcp_flags);
             FLOWMAP_SET(map, tp_src);
diff --git a/lib/flow.h b/lib/flow.h
index 7e5f50e0ad4f..ccbe522cd968 100644
--- a/lib/flow.h
+++ b/lib/flow.h
@@ -149,6 +149,10 @@ struct flow {
     ovs_be16 tp_dst;            /* TCP/UDP/SCTP destination port/ICMP code. */
     ovs_be32 igmp_group_ip4;    /* IGMP group IPv4 address.
                                  * Keep last for BUILD_ASSERT_DECL below. */
+
+    uint8_t next_base_layer;    /* Fields of encapsulated packet, if any,
+                                 * start at this layer */
+    uint8_t pad4[7];
 };
 BUILD_ASSERT_DECL(sizeof(struct flow) % sizeof(uint64_t) == 0);
 BUILD_ASSERT_DECL(sizeof(struct flow_tnl) % sizeof(uint64_t) == 0);
diff --git a/lib/match.c b/lib/match.c
index 6440d260495d..e09ae63430b7 100644
--- a/lib/match.c
+++ b/lib/match.c
@@ -1309,6 +1309,10 @@ match_format(const struct match *match, struct ds *s, int priority)
                             TCP_FLAGS(OVS_BE16_MAX));
     }
 
+    if (wc->masks.next_base_layer) {
+        ds_put_format(s, "next_base_layer=%"PRIu8",", f->next_base_layer);
+    }
+
     if (s->length > start_len) {
         ds_chomp(s, ',');
     }
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index b47ba0f8d430..aee5ad6cd2d0 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -5573,7 +5573,8 @@ get_etheraddr(const char *netdev_name, struct eth_addr *ea)
         return error;
     }
     hwaddr_family = ifr.ifr_hwaddr.sa_family;
-    if (hwaddr_family != AF_UNSPEC && hwaddr_family != ARPHRD_ETHER) {
+    if (hwaddr_family != AF_UNSPEC && hwaddr_family != ARPHRD_ETHER &&
+        hwaddr_family != ARPHRD_IPGRE) {
         VLOG_INFO("%s device has unknown hardware address family %d",
                   netdev_name, hwaddr_family);
         return EINVAL;
diff --git a/lib/netdev-vport.c b/lib/netdev-vport.c
index 88f5022f4bd5..78c8e9720de9 100644
--- a/lib/netdev-vport.c
+++ b/lib/netdev-vport.c
@@ -145,7 +145,7 @@ netdev_vport_is_layer3(const struct netdev *dev)
 {
     const char *type = netdev_get_type(dev);
 
-    return (!strcmp("lisp", type));
+    return (!strcmp("lisp", type) || !strcmp("l3gre", type));
 }
 
 static bool
@@ -943,12 +943,17 @@ ip_extract_tnl_md(struct dp_packet *packet, struct flow_tnl *tnl,
     return l4;
 }
 
+static ovs_be16
+header_eth_type(const void *header)
+{
+    const struct eth_header *eth = header;
+    return eth->eth_type;
+}
+
 static bool
 is_header_ipv6(const void *header)
 {
-    const struct eth_header *eth;
-    eth = header;
-    return eth->eth_type == htons(ETH_TYPE_IPV6);
+    return header_eth_type(header) == htons(ETH_TYPE_IPV6);
 }
 
 /* Pushes the 'size' bytes of 'header' into the headroom of 'packet',
@@ -973,6 +978,9 @@ push_ip_header(struct dp_packet *packet,
 
     memcpy(eth, header, size);
 
+    dp_packet_reset_offsets(packet);
+    packet->l3_ofs = sizeof (struct eth_header);
+
     if (is_header_ipv6(header)) {
         ip6 = ipv6_hdr(eth);
         *ip_tot_size -= IPV6_HEADER_LEN;
@@ -1120,7 +1128,7 @@ gre_header_len(ovs_be16 flags)
 
 static int
 parse_gre_header(struct dp_packet *packet,
-                 struct flow_tnl *tnl)
+                 struct flow_tnl *tnl, bool tap)
 {
     const struct gre_base_hdr *greh;
     ovs_16aligned_be32 *options;
@@ -1136,7 +1144,8 @@ parse_gre_header(struct dp_packet *packet,
         return -EINVAL;
     }
 
-    if (greh->protocol != htons(ETH_TYPE_TEB)) {
+    if ((tap && greh->protocol != htons(ETH_TYPE_TEB)) ||
+        (!tap && greh->protocol == htons(ETH_TYPE_TEB))) {
         return -EINVAL;
     }
 
@@ -1169,6 +1178,10 @@ parse_gre_header(struct dp_packet *packet,
         options++;
     }
 
+    if (!tap) {
+        packet->md.packet_ethertype = greh->protocol;
+    }
+
     return hlen;
 }
 
@@ -1182,7 +1195,7 @@ pkt_metadata_init_tnl(struct pkt_metadata *md)
 }
 
 static int
-netdev_gre_pop_header(struct dp_packet *packet)
+netdev_gre_pop_header__(struct dp_packet *packet, bool tap)
 {
     struct pkt_metadata *md = &packet->md;
     struct flow_tnl *tnl = &md->tunnel;
@@ -1196,7 +1209,7 @@ netdev_gre_pop_header(struct dp_packet *packet)
         return EINVAL;
     }
 
-    hlen = parse_gre_header(packet, tnl);
+    hlen = parse_gre_header(packet, tnl, tap);
     if (hlen < 0) {
         return -hlen;
     }
@@ -1206,6 +1219,31 @@ netdev_gre_pop_header(struct dp_packet *packet)
     return 0;
 }
 
+static int
+netdev_gretap_pop_header(struct dp_packet *packet)
+{
+    return netdev_gre_pop_header__(packet, true);
+}
+
+static int
+netdev_gre_pop_header(struct dp_packet *packet)
+{
+    int err;
+
+    err = netdev_gre_pop_header__(packet, false);
+    if (err) {
+        return err;
+    }
+
+    if (eth_type_mpls(packet->md.packet_ethertype)) {
+        packet->l2_5_ofs = 0;
+    } else {
+        packet->l3_ofs = 0;
+    }
+
+    return 0;
+}
+
 static void
 netdev_gre_push_header(struct dp_packet *packet,
                        const struct ovs_action_push_tnl *data)
@@ -1219,12 +1257,13 @@ netdev_gre_push_header(struct dp_packet *packet,
         ovs_be16 *csum_opt = (ovs_be16 *) (greh + 1);
         *csum_opt = csum(greh, ip_tot_size);
     }
+    packet->md.packet_ethertype = header_eth_type(data->header);
 }
 
 static int
-netdev_gre_build_header(const struct netdev *netdev,
-                        struct ovs_action_push_tnl *data,
-                        const struct flow *tnl_flow)
+netdev_gre_build_header__(const struct netdev *netdev,
+                          struct ovs_action_push_tnl *data,
+                          const struct flow *tnl_flow, ovs_be16 proto)
 {
     struct netdev_vport *dev = netdev_vport_cast(netdev);
     struct netdev_tunnel_config *tnl_cfg;
@@ -1251,7 +1290,7 @@ netdev_gre_build_header(const struct netdev *netdev,
         greh = (struct gre_base_hdr *) (ip + 1);
     }
 
-    greh->protocol = htons(ETH_TYPE_TEB);
+    greh->protocol = proto;
     greh->flags = 0;
 
     options = (ovs_16aligned_be32 *) (greh + 1);
@@ -1279,6 +1318,24 @@ netdev_gre_build_header(const struct netdev *netdev,
 }
 
 static int
+netdev_gretap_build_header(const struct netdev *netdev,
+                        struct ovs_action_push_tnl *data,
+                        const struct flow *tnl_flow)
+{
+    return netdev_gre_build_header__(netdev, data, tnl_flow,
+                                     htons(ETH_TYPE_TEB));
+}
+
+static int
+netdev_gre_build_header(const struct netdev *netdev,
+                        struct ovs_action_push_tnl *data,
+                        const struct flow *tnl_flow)
+{
+    return netdev_gre_build_header__(netdev, data, tnl_flow,
+                                     tnl_flow->dl_type);
+}
+
+static int
 netdev_vxlan_pop_header(struct dp_packet *packet)
 {
     struct pkt_metadata *md = &packet->md;
@@ -1555,9 +1612,12 @@ netdev_vport_tunnel_register(void)
         TUNNEL_CLASS("geneve", "genev_sys", netdev_geneve_build_header,
                                             push_udp_header,
                                             netdev_geneve_pop_header),
-        TUNNEL_CLASS("gre", "gre_sys", netdev_gre_build_header,
+        TUNNEL_CLASS("gre", "gre_sys", netdev_gretap_build_header,
                                        netdev_gre_push_header,
-                                       netdev_gre_pop_header),
+                                       netdev_gretap_pop_header),
+        TUNNEL_CLASS("l3gre", "l3gre_sys", netdev_gre_build_header,
+                                           netdev_gre_push_header,
+                                           netdev_gre_pop_header),
         TUNNEL_CLASS("ipsec_gre", "gre_sys", NULL, NULL, NULL),
         TUNNEL_CLASS("vxlan", "vxlan_sys", netdev_vxlan_build_header,
                                            push_udp_header,
diff --git a/lib/netdev-vport.h b/lib/netdev-vport.h
index be02cb569d96..6bd30adee321 100644
--- a/lib/netdev-vport.h
+++ b/lib/netdev-vport.h
@@ -20,6 +20,7 @@
 #include <stdbool.h>
 #include <stddef.h>
 #include "compiler.h"
+#include "openvswitch/types.h"
 
 struct dpif_netlink_vport;
 struct dpif_flow_stats;
diff --git a/lib/odp-execute.c b/lib/odp-execute.c
index 39fe1cec4018..248c7c7b4c65 100644
--- a/lib/odp-execute.c
+++ b/lib/odp-execute.c
@@ -342,6 +342,7 @@ odp_execute_set_action(struct dp_packet *packet, const struct nlattr *a)
     case OVS_KEY_ATTR_CT_ZONE:
     case OVS_KEY_ATTR_CT_MARK:
     case OVS_KEY_ATTR_CT_LABELS:
+    case OVS_KEY_ATTR_NEXT_BASE_LAYER:
     case __OVS_KEY_ATTR_MAX:
     default:
         OVS_NOT_REACHED();
@@ -446,6 +447,7 @@ odp_execute_masked_set_action(struct dp_packet *packet,
     case OVS_KEY_ATTR_ICMP:
     case OVS_KEY_ATTR_ICMPV6:
     case OVS_KEY_ATTR_TCP_FLAGS:
+    case OVS_KEY_ATTR_NEXT_BASE_LAYER:
     case __OVS_KEY_ATTR_MAX:
     default:
         OVS_NOT_REACHED();
diff --git a/lib/odp-util.c b/lib/odp-util.c
index 0d24327cc627..e9fca07a279d 100644
--- a/lib/odp-util.c
+++ b/lib/odp-util.c
@@ -166,6 +166,7 @@ ovs_key_attr_to_string(enum ovs_key_attr attr, char *namebuf, size_t bufsize)
     case OVS_KEY_ATTR_DP_HASH: return "dp_hash";
     case OVS_KEY_ATTR_RECIRC_ID: return "recirc_id";
     case OVS_KEY_ATTR_PACKET_ETHERTYPE: return "pkt_eth";
+    case OVS_KEY_ATTR_NEXT_BASE_LAYER: return "next_base_layer";
 
     case __OVS_KEY_ATTR_MAX:
     default:
@@ -1829,6 +1830,7 @@ static const struct attr_len_tbl ovs_flow_key_attr_lens[OVS_KEY_ATTR_MAX + 1] =
     [OVS_KEY_ATTR_CT_MARK]   = { .len = 4 },
     [OVS_KEY_ATTR_CT_LABELS] = { .len = sizeof(struct ovs_key_ct_labels) },
     [OVS_KEY_ATTR_PACKET_ETHERTYPE] = { .len = 2 },
+    [OVS_KEY_ATTR_NEXT_BASE_LAYER] = { .len = 1 },
 };
 
 /* Returns the correct length of the payload for a flow key attribute of the
@@ -2959,6 +2961,13 @@ format_odp_key_attr(const struct nlattr *a, const struct nlattr *ma,
         ds_chomp(ds, ',');
         break;
     }
+
+    case OVS_KEY_ATTR_NEXT_BASE_LAYER: {
+        const uint8_t *mask = ma ? nl_attr_get(ma) : NULL;
+        format_u8u(ds, "type", nl_attr_get_u8(a), mask, verbose);
+        break;
+    }
+
     case OVS_KEY_ATTR_UNSPEC:
     case __OVS_KEY_ATTR_MAX:
     default:
@@ -4389,6 +4398,11 @@ odp_flow_key_from_flow__(const struct odp_flow_key_parms *parms,
             sctp_key = nl_msg_put_unspec_uninit(buf, OVS_KEY_ATTR_SCTP,
                                                sizeof *sctp_key);
             get_tp_key(data, sctp_key);
+        } else if (flow->nw_proto == IPPROTO_GRE) {
+            if (!export_mask || data->next_base_layer == 0xff) {
+                nl_msg_put_u8(buf, OVS_KEY_ATTR_NEXT_BASE_LAYER,
+                              data->next_base_layer);
+            }
         } else if (flow->dl_type == htons(ETH_TYPE_IP)
                 && flow->nw_proto == IPPROTO_ICMP) {
             struct ovs_key_icmp *icmp_key;
@@ -4965,6 +4979,13 @@ parse_l2_5_onward(const struct nlattr *attrs[OVS_KEY_ATTR_MAX + 1],
             put_tp_key(sctp_key, flow);
             expected_bit = OVS_KEY_ATTR_SCTP;
         }
+    } else if (src_flow->nw_proto == IPPROTO_GRE
+               && (src_flow->dl_type == htons(ETH_TYPE_IP) ||
+                   src_flow->dl_type == htons(ETH_TYPE_IPV6))
+               && !(src_flow->nw_frag & FLOW_NW_FRAG_LATER)) {
+        if (present_attrs & (UINT64_C(1) << OVS_KEY_ATTR_NEXT_BASE_LAYER)) {
+            flow->next_base_layer = nl_attr_get_u8(attrs[OVS_KEY_ATTR_NEXT_BASE_LAYER]);
+        }
     } else if (src_flow->nw_proto == IPPROTO_ICMP
                && src_flow->dl_type == htons(ETH_TYPE_IP)
                && !(src_flow->nw_frag & FLOW_NW_FRAG_LATER)) {
diff --git a/lib/tnl-ports.c b/lib/tnl-ports.c
index e7f2066ab5c5..53adcb7a9a1a 100644
--- a/lib/tnl-ports.c
+++ b/lib/tnl-ports.c
@@ -27,6 +27,7 @@
 #include "hash.h"
 #include "list.h"
 #include "netdev.h"
+#include "netdev-vport.h"
 #include "ofpbuf.h"
 #include "ovs-thread.h"
 #include "odp-util.h"
@@ -52,6 +53,7 @@ static struct ovs_list addr_list;
 struct tnl_port {
     odp_port_t port;
     ovs_be16 udp_port;
+    bool is_layer3;
     char dev_name[IFNAMSIZ];
     struct ovs_list node;
 };
@@ -61,6 +63,7 @@ static struct ovs_list port_list;
 struct tnl_port_in {
     struct cls_rule cr;
     odp_port_t portno;
+    bool match_base_layer;
     struct ovs_refcount ref_cnt;
     char dev_name[IFNAMSIZ];
 };
@@ -82,7 +85,7 @@ tnl_port_free(struct tnl_port_in *p)
 
 static void
 tnl_port_init_flow(struct flow *flow, struct eth_addr mac,
-                   struct in6_addr *addr, ovs_be16 udp_port)
+                   struct in6_addr *addr, ovs_be16 udp_port, bool is_layer3)
 {
     memset(flow, 0, sizeof *flow);
 
@@ -99,20 +102,21 @@ tnl_port_init_flow(struct flow *flow, struct eth_addr mac,
         flow->nw_proto = IPPROTO_UDP;
     } else {
         flow->nw_proto = IPPROTO_GRE;
+        flow->next_base_layer = is_layer3 ? LAYER_3 : LAYER_2;
     }
     flow->tp_dst = udp_port;
 }
 
 static void
 map_insert(odp_port_t port, struct eth_addr mac, struct in6_addr *addr,
-           ovs_be16 udp_port, const char dev_name[])
+           ovs_be16 udp_port, const char dev_name[], bool is_layer3)
 {
     const struct cls_rule *cr;
     struct tnl_port_in *p;
     struct match match;
 
     memset(&match, 0, sizeof match);
-    tnl_port_init_flow(&match.flow, mac, addr, udp_port);
+    tnl_port_init_flow(&match.flow, mac, addr, udp_port, is_layer3);
 
     do {
         cr = classifier_lookup(&cls, CLS_MAX_VERSION, &match.flow, NULL);
@@ -133,6 +137,13 @@ map_insert(odp_port_t port, struct eth_addr mac, struct in6_addr *addr,
          * doesn't make sense to match on UDP port numbers. */
         if (udp_port) {
             match.wc.masks.tp_dst = OVS_BE16_MAX;
+        } else {
+            /* Match base layer for non-UDP tunnels as it may
+             * be used to differentiate them. For UDP tunnels the
+             * port number provides differentiation.
+             */
+            match.wc.masks.next_base_layer = UINT8_MAX;
+            p->match_base_layer = true;
         }
         if (IN6_IS_ADDR_V4MAPPED(addr)) {
             match.wc.masks.nw_dst = OVS_BE32_MAX;
@@ -151,15 +162,15 @@ map_insert(odp_port_t port, struct eth_addr mac, struct in6_addr *addr,
 }
 
 void
-tnl_port_map_insert(odp_port_t port,
-                    ovs_be16 udp_port, const char dev_name[])
+tnl_port_map_insert(odp_port_t port, ovs_be16 udp_port,
+                    const char dev_name[], bool is_layer3)
 {
     struct tnl_port *p;
     struct ip_device *ip_dev;
 
     ovs_mutex_lock(&mutex);
     LIST_FOR_EACH(p, node, &port_list) {
-        if (udp_port == p->udp_port) {
+        if (udp_port == p->udp_port && udp_port) {
              goto out;
         }
     }
@@ -167,6 +178,7 @@ tnl_port_map_insert(odp_port_t port,
     p = xzalloc(sizeof *p);
     p->port = port;
     p->udp_port = udp_port;
+    p->is_layer3 = is_layer3;
     ovs_strlcpy(p->dev_name, dev_name, sizeof p->dev_name);
     list_insert(&port_list, &p->node);
 
@@ -174,11 +186,11 @@ tnl_port_map_insert(odp_port_t port,
         if (ip_dev->addr4 != INADDR_ANY) {
             struct in6_addr addr4 = in6_addr_mapped_ipv4(ip_dev->addr4);
             map_insert(p->port, ip_dev->mac, &addr4,
-                       p->udp_port, p->dev_name);
+                       p->udp_port, p->dev_name, is_layer3);
         }
         if (ipv6_addr_is_set(&ip_dev->addr6)) {
             map_insert(p->port, ip_dev->mac, &ip_dev->addr6,
-                       p->udp_port, p->dev_name);
+                       p->udp_port, p->dev_name, is_layer3);
         }
     }
 
@@ -199,19 +211,20 @@ tnl_port_unref(const struct cls_rule *cr)
 }
 
 static void
-map_delete(struct eth_addr mac, struct in6_addr *addr, ovs_be16 udp_port)
+map_delete(struct eth_addr mac, struct in6_addr *addr, ovs_be16 udp_port,
+           bool is_layer3)
 {
     const struct cls_rule *cr;
     struct flow flow;
 
-    tnl_port_init_flow(&flow, mac, addr, udp_port);
+    tnl_port_init_flow(&flow, mac, addr, udp_port, is_layer3);
 
     cr = classifier_lookup(&cls, CLS_MAX_VERSION, &flow, NULL);
     tnl_port_unref(cr);
 }
 
 void
-tnl_port_map_delete(ovs_be16 udp_port)
+tnl_port_map_delete(ovs_be16 udp_port, bool is_layer3)
 {
     struct tnl_port *p, *next;
     struct ip_device *ip_dev;
@@ -232,10 +245,10 @@ tnl_port_map_delete(ovs_be16 udp_port)
     LIST_FOR_EACH(ip_dev, node, &addr_list) {
         if (ip_dev->addr4 != INADDR_ANY) {
             struct in6_addr addr4 = in6_addr_mapped_ipv4(ip_dev->addr4);
-            map_delete(ip_dev->mac, &addr4, udp_port);
+            map_delete(ip_dev->mac, &addr4, udp_port, is_layer3);
         }
         if (ipv6_addr_is_set(&ip_dev->addr6)) {
-            map_delete(ip_dev->mac, &ip_dev->addr6, udp_port);
+            map_delete(ip_dev->mac, &ip_dev->addr6, udp_port, is_layer3);
         }
     }
 
@@ -244,15 +257,35 @@ out:
     ovs_mutex_unlock(&mutex);
 }
 
-/* 'flow' is non-const to allow for temporary modifications during the lookup.
- * Any changes are restored before returning. */
+/* 'flow' is non-const to allow for:
+ * - Temporary modifications during the lookup
+ *    these are reverted before returning.
+ * - Setting matching on next_base_layer as required by the port looked up. */
 odp_port_t
 tnl_port_map_lookup(struct flow *flow, struct flow_wildcards *wc)
 {
     const struct cls_rule *cr = classifier_lookup(&cls, CLS_MAX_VERSION, flow,
                                                   wc);
+    enum base_layer next_base_layer_mask;
+    struct tnl_port_in *p;
+    odp_port_t portno;
+
+    /* next_base_layer should be matched when looking up tunnel port*/
+    next_base_layer_mask = wc->masks.base_layer;
+    wc->masks.next_base_layer = UINT8_MAX;
+
+    if (!cr) {
+        portno = ODPP_NONE;
+    } else {
+        p = tnl_port_cast(cr);
+        portno = p->portno;
+    }
+
+    if (!cr || !p->match_base_layer) {
+        wc->masks.next_base_layer = next_base_layer_mask;
+    }
 
-    return (cr) ? tnl_port_cast(cr)->portno : ODPP_NONE;
+    return portno;
 }
 
 static void
@@ -334,11 +367,11 @@ map_insert_ipdev(struct ip_device *ip_dev)
         if (ip_dev->addr4 != INADDR_ANY) {
             struct in6_addr addr4 = in6_addr_mapped_ipv4(ip_dev->addr4);
             map_insert(p->port, ip_dev->mac, &addr4,
-                       p->udp_port, p->dev_name);
+                       p->udp_port, p->dev_name, p->is_layer3);
         }
         if (ipv6_addr_is_set(&ip_dev->addr6)) {
             map_insert(p->port, ip_dev->mac, &ip_dev->addr6,
-                       p->udp_port, p->dev_name);
+                       p->udp_port, p->dev_name, p->is_layer3);
         }
     }
 }
@@ -386,15 +419,16 @@ insert_ipdev(const char dev_name[])
 static void
 delete_ipdev(struct ip_device *ip_dev)
 {
+    bool is_layer3 = netdev_vport_is_layer3(ip_dev->dev);
     struct tnl_port *p;
 
     LIST_FOR_EACH(p, node, &port_list) {
         if (ip_dev->addr4 != INADDR_ANY) {
             struct in6_addr addr4 = in6_addr_mapped_ipv4(ip_dev->addr4);
-            map_delete(ip_dev->mac, &addr4, p->udp_port);
+            map_delete(ip_dev->mac, &addr4, p->udp_port, is_layer3);
         }
         if (ipv6_addr_is_set(&ip_dev->addr6)) {
-            map_delete(ip_dev->mac, &ip_dev->addr6, p->udp_port);
+            map_delete(ip_dev->mac, &ip_dev->addr6, p->udp_port, is_layer3);
         }
     }
 
diff --git a/lib/tnl-ports.h b/lib/tnl-ports.h
index 4195e6a82e0f..f9958cec60af 100644
--- a/lib/tnl-ports.h
+++ b/lib/tnl-ports.h
@@ -27,9 +27,9 @@
 odp_port_t tnl_port_map_lookup(struct flow *flow, struct flow_wildcards *wc);
 
 void tnl_port_map_insert(odp_port_t port, ovs_be16 udp_port,
-                         const char dev_name[]);
+                         const char dev_name[], bool is_layer3);
 
-void tnl_port_map_delete(ovs_be16 udp_port);
+void tnl_port_map_delete(ovs_be16 udp_port, bool is_layer3);
 void tnl_port_map_insert_ipdev(const char dev[]);
 void tnl_port_map_delete_ipdev(const char dev[]);
 void tnl_port_map_run(void);
diff --git a/ofproto/ofproto-dpif-ipfix.c b/ofproto/ofproto-dpif-ipfix.c
index a610c536204f..6509f21ce0ec 100644
--- a/ofproto/ofproto-dpif-ipfix.c
+++ b/ofproto/ofproto-dpif-ipfix.c
@@ -588,7 +588,7 @@ dpif_ipfix_add_tunnel_port(struct dpif_ipfix *di, struct ofport *ofport,
     dip = xmalloc(sizeof *dip);
     dip->ofport = ofport;
     dip->odp_port = odp_port;
-    if (strcmp(type, "gre") == 0) {
+    if (strcmp(type, "gre") == 0 || strcmp(type, "l3gre") == 0) {
         /* 32-bit key gre */
         dip->tunnel_type = DPIF_IPFIX_TUNNEL_GRE;
         dip->tunnel_key_length = 4;
diff --git a/ofproto/ofproto-dpif-sflow.c b/ofproto/ofproto-dpif-sflow.c
index 33d8bec7495d..7c93f6ab18bd 100644
--- a/ofproto/ofproto-dpif-sflow.c
+++ b/ofproto/ofproto-dpif-sflow.c
@@ -584,7 +584,7 @@ static enum dpif_sflow_tunnel_type
 dpif_sflow_tunnel_type(struct ofport *ofport) {
     const char *type = netdev_get_type(ofport->netdev);
     if (type) {
-	if (strcmp(type, "gre") == 0) {
+	if (strcmp(type, "gre") == 0 || strcmp(type, "l3gre") == 0) {
 	    return DPIF_SFLOW_TUNNEL_GRE;
 	} else if (strcmp(type, "ipsec_gre") == 0) {
 	    return DPIF_SFLOW_TUNNEL_IPSEC_GRE;
@@ -1035,6 +1035,7 @@ sflow_read_set_action(const struct nlattr *attr,
     case OVS_KEY_ATTR_CT_MARK:
     case OVS_KEY_ATTR_CT_LABELS:
     case OVS_KEY_ATTR_UNSPEC:
+    case OVS_KEY_ATTR_NEXT_BASE_LAYER:
     case __OVS_KEY_ATTR_MAX:
     default:
         break;
diff --git a/ofproto/tunnel.c b/ofproto/tunnel.c
index 24b717a3ce86..322b14f44ea7 100644
--- a/ofproto/tunnel.c
+++ b/ofproto/tunnel.c
@@ -26,6 +26,7 @@
 #include "hash.h"
 #include "hmap.h"
 #include "netdev.h"
+#include "netdev-vport.h"
 #include "odp-util.h"
 #include "ofpbuf.h"
 #include "packets.h"
@@ -194,7 +195,8 @@ tnl_port_add__(const struct ofport_dpif *ofport, const struct netdev *netdev,
     tnl_port_mod_log(tnl_port, "adding");
 
     if (native_tnl) {
-        tnl_port_map_insert(odp_port, cfg->dst_port, name);
+        tnl_port_map_insert(odp_port, cfg->dst_port, name,
+                            netdev_vport_is_layer3(netdev));
     }
     return true;
 }
@@ -261,7 +263,8 @@ tnl_port_del__(const struct ofport_dpif *ofport) OVS_REQ_WRLOCK(rwlock)
             netdev_get_tunnel_config(tnl_port->netdev);
         struct hmap **map;
 
-        tnl_port_map_delete(cfg->dst_port);
+        tnl_port_map_delete(cfg->dst_port,
+                            netdev_vport_is_layer3(tnl_port->netdev));
         tnl_port_mod_log(tnl_port, "removing");
         map = tnl_match_map(&tnl_port->match);
         hmap_remove(*map, &tnl_port->match_node);
diff --git a/tests/tunnel-push-pop-ipv6.at b/tests/tunnel-push-pop-ipv6.at
index 8f6506a716f9..139e29714dfd 100644
--- a/tests/tunnel-push-pop-ipv6.at
+++ b/tests/tunnel-push-pop-ipv6.at
@@ -12,6 +12,8 @@ AT_CHECK([ovs-vsctl add-port int-br t2 -- set Interface t2 type=vxlan \
                        options:remote_ip=2001:cafe::93 options:out_key=flow options:csum=true ofport_request=4\
                     -- add-port int-br t4 -- set Interface t4 type=geneve \
                        options:remote_ip=flow options:key=123 ofport_request=5\
+                    -- add-port int-br t5 -- set Interface t5 type=l3gre \
+                       options:remote_ip=2001:cafe::92 options:key=455 ofport_request=6\
                        ], [0])
 
 AT_CHECK([ovs-appctl dpif/show], [0], [dnl
@@ -21,10 +23,11 @@ dummy at ovs-dummy: hit:0 missed:0
 		p0 1/1: (dummy)
 	int-br:
 		int-br 65534/2: (dummy)
-		t1 3/3: (gre: key=456, remote_ip=2001:cafe::92)
+		t1 3/4: (gre: key=456, remote_ip=2001:cafe::92)
 		t2 2/4789: (vxlan: key=123, remote_ip=2001:cafe::92)
 		t3 4/4789: (vxlan: csum=true, out_key=flow, remote_ip=2001:cafe::93)
 		t4 5/6081: (geneve: key=123, remote_ip=flow)
+		t5 6/3: (l3gre: key=455, remote_ip=2001:cafe::92)
 ])
 
 dnl First setup dummy interface IP address, then add the route
@@ -52,7 +55,8 @@ IP                                            MAC                 Bridge
 AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl
 Listening ports:
 genev_sys_6081 (6081)
-gre_sys (3)
+gre_sys (4)
+l3gre_sys (3)
 vxlan_sys_4789 (4789)
 ])
 
@@ -65,7 +69,7 @@ AT_CHECK([tail -1 stdout], [0],
 dnl Check GRE tunnel pop
 AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x86dd),ipv6(src=2001:cafe::92,dst=2001:cafe::88,label=0,proto=47,tclass=0x0,hlimit=64)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
-  [Datapath actions: tnl_pop(3)
+  [Datapath actions: tnl_pop(4)
 ])
 
 dnl Check Geneve tunnel pop
@@ -92,7 +96,7 @@ dnl Check GRE tunnel push
 AT_CHECK([ovs-ofctl add-flow int-br action=3])
 AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
-  [Datapath actions: tnl_push(tnl_port(3),header(size=62,type=3,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0x2000,proto=0x6558),key=0x1c8)),out_port(100))
+  [Datapath actions: tnl_push(tnl_port(4),header(size=62,type=3,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x86dd),ipv6(src=2001:cafe::88,dst=2001:cafe::92,label=0,proto=47,tclass=0x0,hlimit=64),gre((flags=0x2000,proto=0x6558),key=0x1c8)),out_port(100))
 ])
 
 dnl Check Geneve tunnel push
@@ -118,12 +122,12 @@ AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  3'], [0], [dnl
   port  3: rx pkts=1, bytes=98, drop=0, errs=0, frame=0, over=0, crc=0
 ])
 
-dnl Check GRE only accepts encapsulated Ethernet frames
-AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6486dd60000000006a2f402001cafe0000000000000000000000922001cafe00000000000000000000008820000800000001c8fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
+dnl Check decapsulation of L3GRE packet
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6486dd60000000005a2f402001cafe0000000000000000000000922001cafe00000000000000000000008820000800000001c745000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
 ovs-appctl time/warp 1000
 
-AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  3'], [0], [dnl
-  port  3: rx pkts=1, bytes=98, drop=0, errs=0, frame=0, over=0, crc=0
+AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  6'], [0], [dnl
+  port  6: rx pkts=1, bytes=84, drop=0, errs=0, frame=0, over=0, crc=0
 ])
 
 dnl Check decapsulation of Geneve packet with options
diff --git a/tests/tunnel-push-pop.at b/tests/tunnel-push-pop.at
index 242ffaf1bc46..952b5a9f8583 100644
--- a/tests/tunnel-push-pop.at
+++ b/tests/tunnel-push-pop.at
@@ -12,6 +12,8 @@ AT_CHECK([ovs-vsctl add-port int-br t2 -- set Interface t2 type=vxlan \
                        options:remote_ip=1.1.2.93 options:out_key=flow options:csum=true ofport_request=4\
                     -- add-port int-br t4 -- set Interface t4 type=geneve \
                        options:remote_ip=flow options:key=123 ofport_request=5\
+                    -- add-port int-br t5 -- set Interface t5 type=l3gre \
+                       options:remote_ip=1.1.2.92 options:key=455 ofport_request=6\
                        ], [0])
 
 AT_CHECK([ovs-appctl dpif/show], [0], [dnl
@@ -21,10 +23,11 @@ dummy at ovs-dummy: hit:0 missed:0
 		p0 1/1: (dummy)
 	int-br:
 		int-br 65534/2: (dummy)
-		t1 3/3: (gre: key=456, remote_ip=1.1.2.92)
+		t1 3/4: (gre: key=456, remote_ip=1.1.2.92)
 		t2 2/4789: (vxlan: key=123, remote_ip=1.1.2.92)
 		t3 4/4789: (vxlan: csum=true, out_key=flow, remote_ip=1.1.2.93)
 		t4 5/6081: (geneve: key=123, remote_ip=flow)
+		t5 6/3: (l3gre: key=455, remote_ip=1.1.2.92)
 ])
 
 dnl First setup dummy interface IP address, then add the route
@@ -50,7 +53,8 @@ IP                                            MAC                 Bridge
 AT_CHECK([ovs-appctl tnl/ports/show |sort], [0], [dnl
 Listening ports:
 genev_sys_6081 (6081)
-gre_sys (3)
+gre_sys (4)
+l3gre_sys (3)
 vxlan_sys_4789 (4789)
 ])
 
@@ -63,7 +67,7 @@ AT_CHECK([tail -1 stdout], [0],
 dnl Check GRE tunnel pop
 AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(1),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.2.92,dst=1.1.2.88,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
-  [Datapath actions: tnl_pop(3)
+  [Datapath actions: tnl_pop(4)
 ])
 
 dnl Check Geneve tunnel pop
@@ -90,7 +94,14 @@ dnl Check GRE tunnel push
 AT_CHECK([ovs-ofctl add-flow int-br action=3])
 AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
 AT_CHECK([tail -1 stdout], [0],
-  [Datapath actions: tnl_push(tnl_port(3),header(size=42,type=3,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x40),gre((flags=0x2000,proto=0x6558),key=0x1c8)),out_port(100))
+  [Datapath actions: tnl_push(tnl_port(4),header(size=42,type=3,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x40),gre((flags=0x2000,proto=0x6558),key=0x1c8)),out_port(100))
+])
+
+dnl Check L3GRE tunnel push
+AT_CHECK([ovs-ofctl add-flow int-br action=6])
+AT_CHECK([ovs-appctl ofproto/trace ovs-dummy 'in_port(2),eth(src=f8:bc:12:44:34:b6,dst=aa:55:aa:55:00:00),eth_type(0x0800),ipv4(src=1.1.3.88,dst=1.1.3.112,proto=47,tos=0,ttl=64,frag=no)'], [0], [stdout])
+AT_CHECK([tail -1 stdout], [0],
+  [Datapath actions: pop_eth,tnl_push(tnl_port(3),header(size=42,type=3,eth(dst=f8:bc:12:44:34:b6,src=aa:55:aa:55:00:00,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x40),gre((flags=0x2000,proto=0x800),key=0x1c7)),out_port(100))
 ])
 
 dnl Check Geneve tunnel push
@@ -116,12 +127,20 @@ AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  3'], [0], [dnl
   port  3: rx pkts=1, bytes=98, drop=0, errs=0, frame=0, over=0, crc=0
 ])
 
-dnl Check GRE only accepts encapsulated Ethernet frames
-AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6408004500007e79464000402fba550101025c0101025820000800000001c8fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
+dnl Check decapsulation of L3GRE packet
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6408004500007079464000402fba630101025c0101025820000800000001c745000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
 ovs-appctl time/warp 1000
 
-AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  3'], [0], [dnl
+AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  6'], [0], [dnl
+  port  6: rx pkts=1, bytes=84, drop=0, errs=0, frame=0, over=0, crc=0
+])
+
+dnl Check GREL3 only accepts non-fragmented packets?
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 'aa55aa550000001b213cab6408004500007e79464000402fba550101025c0101025820000800000001c7fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
+
+AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  [[36]]' | sort], [0], [dnl
   port  3: rx pkts=1, bytes=98, drop=0, errs=0, frame=0, over=0, crc=0
+  port  6: rx pkts=1, bytes=84, drop=0, errs=0, frame=0, over=0, crc=0
 ])
 
 dnl Check decapsulation of Geneve packet with options
-- 
2.1.4




More information about the dev mailing list