tunneling: Userspace datapath support for Geneve options.
authorJesse Gross <jesse@nicira.com>
Mon, 22 Jun 2015 21:23:37 +0000 (14:23 -0700)
committerJesse Gross <jesse@nicira.com>
Fri, 26 Jun 2015 21:18:07 +0000 (14:18 -0700)
Currently the userspace datapath only supports Geneve in a
basic mode - without options - since the rest of userspace
previously didn't support options either. This enables the
userspace datapath to send and receive options as well.

The receive path for extracting the tunnel options isn't entirely
optimal because it does a lookup on the options on a per-packet
basis, rather than per-flow like the kernel does. This is not
as straightforward to do in the userspace datapath since there
is no translation step between packet formats used in packet vs.
flow lookup. This can be optimized in the future and in the
meantime option support is still useful for testing and simulation.

Signed-off-by: Jesse Gross <jesse@nicira.com>
Acked-by: Ben Pfaff <blp@nicira.com>
datapath/linux/compat/include/linux/openvswitch.h
lib/netdev-vport.c
lib/odp-util.c
lib/packets.h
lib/tun-metadata.c
lib/tun-metadata.h
tests/odp.at
tests/tunnel-push-pop.at

index 6cca501..323d158 100644 (file)
@@ -618,7 +618,7 @@ struct ovs_action_hash {
 };
 
 #ifndef __KERNEL__
-#define TNL_PUSH_HEADER_SIZE 128
+#define TNL_PUSH_HEADER_SIZE 512
 
 /*
  * struct ovs_action_push_tnl - %OVS_ACTION_ATTR_TUNNEL_PUSH
index ea9abf9..259d0ed 100644 (file)
@@ -1199,6 +1199,7 @@ netdev_geneve_pop_header(struct dp_packet *packet)
     struct flow_tnl *tnl = &md->tunnel;
     struct genevehdr *gnh;
     unsigned int hlen;
+    int err;
 
     memset(md, 0, sizeof *md);
     if (GENEVE_BASE_HLEN > dp_packet_size(packet)) {
@@ -1224,12 +1225,6 @@ netdev_geneve_pop_header(struct dp_packet *packet)
         return EINVAL;
     }
 
-    if (gnh->opt_len && gnh->critical) {
-        VLOG_WARN_RL(&err_rl, "unknown geneve critical options: %"PRIu8" bytes\n",
-                     gnh->opt_len * 4);
-        return EINVAL;
-    }
-
     if (gnh->proto_type != htons(ETH_TYPE_TEB)) {
         VLOG_WARN_RL(&err_rl, "unknown geneve encapsulated protocol: %#x\n",
                      ntohs(gnh->proto_type));
@@ -1240,6 +1235,13 @@ netdev_geneve_pop_header(struct dp_packet *packet)
     tnl->tun_id = htonll(ntohl(get_16aligned_be32(&gnh->vni)) >> 8);
     tnl->flags |= FLOW_TNL_F_KEY;
 
+    err = tun_metadata_from_geneve_header(gnh->options, gnh->opt_len * 4,
+                                          &tnl->metadata);
+    if (err) {
+        VLOG_WARN_RL(&err_rl, "invalid geneve options");
+        return err;
+    }
+
     dp_packet_reset_packet(packet, hlen);
 
     return 0;
@@ -1253,6 +1255,8 @@ netdev_geneve_build_header(const struct netdev *netdev,
     struct netdev_vport *dev = netdev_vport_cast(netdev);
     struct netdev_tunnel_config *tnl_cfg;
     struct genevehdr *gnh;
+    int opt_len;
+    bool crit_opt;
 
     /* XXX: RCUfy tnl_cfg. */
     ovs_mutex_lock(&dev->mutex);
@@ -1260,12 +1264,19 @@ netdev_geneve_build_header(const struct netdev *netdev,
 
     gnh = udp_build_header(tnl_cfg, tnl_flow, data);
 
-    gnh->oam = !!(tnl_flow->tunnel.flags & FLOW_TNL_F_OAM);
-    gnh->proto_type = htons(ETH_TYPE_TEB);
     put_16aligned_be32(&gnh->vni, htonl(ntohll(tnl_flow->tunnel.tun_id) << 8));
 
     ovs_mutex_unlock(&dev->mutex);
-    data->header_len = GENEVE_BASE_HLEN;
+
+    opt_len = tun_metadata_to_geneve_header(&tnl_flow->tunnel.metadata,
+                                            gnh->options, &crit_opt);
+
+    gnh->opt_len = opt_len / 4;
+    gnh->oam = !!(tnl_flow->tunnel.flags & FLOW_TNL_F_OAM);
+    gnh->critical = crit_opt ? 1 : 0;
+    gnh->proto_type = htons(ETH_TYPE_TEB);
+
+    data->header_len = GENEVE_BASE_HLEN + opt_len;
     data->tnl_type = OVS_VPORT_TYPE_GENEVE;
     return 0;
 }
index efdc651..c70ee0f 100644 (file)
@@ -69,6 +69,17 @@ static void format_odp_key_attr(const struct nlattr *a,
                                 const struct hmap *portno_names, struct ds *ds,
                                 bool verbose);
 
+struct geneve_scan {
+    struct geneve_opt d[63];
+    int len;
+};
+
+static int scan_geneve(const char *s, struct geneve_scan *key,
+                       struct geneve_scan *mask);
+static void format_geneve_opts(const struct geneve_opt *opt,
+                               const struct geneve_opt *mask, int opts_len,
+                               struct ds *, bool verbose);
+
 static struct nlattr *generate_all_wildcard_mask(const struct attr_len_tbl tbl[],
                                                  int max, struct ofpbuf *,
                                                  const struct nlattr *key);
@@ -581,9 +592,19 @@ format_odp_tnl_push_header(struct ds *ds, struct ovs_action_push_tnl *data)
 
         gnh = format_udp_tnl_push_header(ds, ip);
 
-        ds_put_format(ds, "geneve(%svni=0x%"PRIx32")",
+        ds_put_format(ds, "geneve(%s%svni=0x%"PRIx32,
                       gnh->oam ? "oam," : "",
+                      gnh->critical ? "crit," : "",
                       ntohl(get_16aligned_be32(&gnh->vni)) >> 8);
+        if (gnh->opt_len) {
+            ds_put_cstr(ds, ",options(");
+            format_geneve_opts(gnh->options, NULL, gnh->opt_len * 4,
+                               ds, false);
+            ds_put_char(ds, ')');
+        }
+
+        ds_put_char(ds, ')');
     } else if (data->tnl_type == OVS_VPORT_TYPE_GRE) {
         const struct gre_base_hdr *greh;
         ovs_16aligned_be32 *options;
@@ -939,17 +960,41 @@ ovs_parse_tnl_push(const char *s, struct ovs_action_push_tnl *data)
             struct genevehdr *gnh = (struct genevehdr *) (udp + 1);
 
             memset(gnh, 0, sizeof *gnh);
+            header_len = sizeof *eth + sizeof *ip +
+                         sizeof *udp + sizeof *gnh;
+
             if (ovs_scan_len(s, &n, "oam,")) {
                 gnh->oam = 1;
             }
-            if (!ovs_scan_len(s, &n, "vni=0x%"SCNx32"))", &vni)) {
+            if (ovs_scan_len(s, &n, "crit,")) {
+                gnh->critical = 1;
+            }
+            if (!ovs_scan_len(s, &n, "vni=%"SCNi32, &vni)) {
                 return -EINVAL;
             }
+            if (ovs_scan_len(s, &n, ",options(")) {
+                struct geneve_scan options;
+                int len;
+
+                memset(&options, 0, sizeof options);
+                len = scan_geneve(s + n, &options, NULL);
+                if (!len) {
+                    return -EINVAL;
+                }
+
+                memcpy(gnh->options, options.d, options.len);
+                gnh->opt_len = options.len / 4;
+                header_len += options.len;
+
+                n += len;
+            }
+            if (!ovs_scan_len(s, &n, "))")) {
+                return -EINVAL;
+            }
+
             gnh->proto_type = htons(ETH_TYPE_TEB);
             put_16aligned_be32(&gnh->vni, htonl(vni << 8));
             tnl_type = OVS_VPORT_TYPE_GENEVE;
-            header_len = sizeof *eth + sizeof *ip +
-                         sizeof *udp + sizeof *gnh;
         } else {
             return -EINVAL;
         }
@@ -1873,21 +1918,10 @@ format_odp_tun_vxlan_opt(const struct nlattr *attr,
 #define MASK(PTR, FIELD) PTR ? &PTR->FIELD : NULL
 
 static void
-format_odp_tun_geneve(const struct nlattr *attr,
-                      const struct nlattr *mask_attr, struct ds *ds,
-                      bool verbose)
+format_geneve_opts(const struct geneve_opt *opt,
+                   const struct geneve_opt *mask, int opts_len,
+                   struct ds *ds, bool verbose)
 {
-    int opts_len = nl_attr_get_size(attr);
-    const struct geneve_opt *opt = nl_attr_get(attr);
-    const struct geneve_opt *mask = mask_attr ?
-                                    nl_attr_get(mask_attr) : NULL;
-
-    if (mask && nl_attr_get_size(attr) != nl_attr_get_size(mask_attr)) {
-        ds_put_format(ds, "value len %"PRIuSIZE" different from mask len %"PRIuSIZE,
-                      nl_attr_get_size(attr), nl_attr_get_size(mask_attr));
-        return;
-    }
-
     while (opts_len > 0) {
         unsigned int len;
         uint8_t data_len, data_len_mask;
@@ -1937,6 +1971,25 @@ format_odp_tun_geneve(const struct nlattr *attr,
     };
 }
 
+static void
+format_odp_tun_geneve(const struct nlattr *attr,
+                      const struct nlattr *mask_attr, struct ds *ds,
+                      bool verbose)
+{
+    int opts_len = nl_attr_get_size(attr);
+    const struct geneve_opt *opt = nl_attr_get(attr);
+    const struct geneve_opt *mask = mask_attr ?
+                                    nl_attr_get(mask_attr) : NULL;
+
+    if (mask && nl_attr_get_size(attr) != nl_attr_get_size(mask_attr)) {
+        ds_put_format(ds, "value len %"PRIuSIZE" different from mask len %"PRIuSIZE,
+                      nl_attr_get_size(attr), nl_attr_get_size(mask_attr));
+        return;
+    }
+
+    format_geneve_opts(opt, mask, opts_len, ds, verbose);
+}
+
 static void
 format_odp_tun_attr(const struct nlattr *attr, const struct nlattr *mask_attr,
                     struct ds *ds, bool verbose)
@@ -2875,11 +2928,6 @@ scan_vxlan_gbp(const char *s, uint32_t *key, uint32_t *mask)
     return 0;
 }
 
-struct geneve_scan {
-    struct geneve_opt d[63];
-    int len;
-};
-
 static int
 scan_geneve(const char *s, struct geneve_scan *key, struct geneve_scan *mask)
 {
index 04ee914..c401d4e 100644 (file)
@@ -754,6 +754,7 @@ static inline bool dl_type_is_ip_any(ovs_be16 dl_type)
 
 /* Tunnel header */
 #define GENEVE_MAX_OPT_SIZE 124
+#define GENEVE_TOT_OPT_SIZE 252
 
 #define GENEVE_CRIT_OPT_TYPE (1 << 7)
 
index d9ce9f2..7d82fb7 100644 (file)
@@ -578,55 +578,15 @@ tun_metadata_del_entry(struct tun_table *map, uint8_t idx)
     memset(&entry->loc, 0, sizeof entry->loc);
 }
 
-int
-tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
-                                const struct nlattr *flow_attrs,
-                                size_t flow_attr_len,
-                                const struct tun_metadata *flow_metadata,
-                                struct tun_metadata *metadata)
+static int
+tun_metadata_from_geneve__(struct tun_table *map, const struct geneve_opt *opt,
+                           const struct geneve_opt *flow_opt, int opts_len,
+                           struct tun_metadata *metadata)
 {
-    bool is_mask = !!flow_attrs;
-    struct tun_table *map;
-    const struct nlattr *flow;
-    int opts_len;
-    const struct geneve_opt *flow_opt;
-    const struct geneve_opt *opt = nl_attr_get(attr);
-
-    if (!is_mask) {
-        map = ovsrcu_get(struct tun_table *, &metadata_tab);
-        metadata->tab = map;
-    } else {
-        map = flow_metadata->tab;
-    }
-
     if (!map) {
         return 0;
     }
 
-    if (is_mask) {
-        const struct nlattr *tnl_key;
-        int mask_len = nl_attr_get_size(attr);
-
-        tnl_key = nl_attr_find__(flow_attrs, flow_attr_len, OVS_KEY_ATTR_TUNNEL);
-        if (!tnl_key) {
-            return mask_len ? EINVAL : 0;
-        }
-
-        flow = nl_attr_find_nested(tnl_key, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
-        if (!flow) {
-            return mask_len ? EINVAL : 0;
-        }
-
-        if (mask_len != nl_attr_get_size(flow)) {
-            return EINVAL;
-        }
-    } else {
-        flow = attr;
-    }
-
-    opts_len = nl_attr_get_size(flow);
-    flow_opt = nl_attr_get(flow);
-
     while (opts_len > 0) {
         int len;
         struct tun_meta_entry *entry;
@@ -662,27 +622,74 @@ tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
     return 0;
 }
 
-void
-tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow,
-                                   struct ofpbuf *b)
+int
+tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
+                                const struct nlattr *flow_attrs,
+                                size_t flow_attr_len,
+                                const struct tun_metadata *flow_metadata,
+                                struct tun_metadata *metadata)
 {
     struct tun_table *map;
-    size_t nlattr_offset;
-    int i;
+    bool is_mask = !!flow_attrs;
+    const struct nlattr *flow;
 
-    if (!flow->opt_map) {
-        return;
+    if (is_mask) {
+        const struct nlattr *tnl_key;
+        int mask_len = nl_attr_get_size(attr);
+
+        tnl_key = nl_attr_find__(flow_attrs, flow_attr_len, OVS_KEY_ATTR_TUNNEL);
+        if (!tnl_key) {
+            return mask_len ? EINVAL : 0;
+        }
+
+        flow = nl_attr_find_nested(tnl_key, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
+        if (!flow) {
+            return mask_len ? EINVAL : 0;
+        }
+
+        if (mask_len != nl_attr_get_size(flow)) {
+            return EINVAL;
+        }
+    } else {
+        flow = attr;
     }
 
+    if (!is_mask) {
+        map = ovsrcu_get(struct tun_table *, &metadata_tab);
+        metadata->tab = map;
+    } else {
+        map = flow_metadata->tab;
+    }
+
+    return tun_metadata_from_geneve__(map, nl_attr_get(attr), nl_attr_get(flow),
+                                      nl_attr_get_size(flow), metadata);
+}
+
+int
+tun_metadata_from_geneve_header(const struct geneve_opt *opts, int opt_len,
+                                struct tun_metadata *metadata)
+{
+    struct tun_table *map;
+
+    map = ovsrcu_get(struct tun_table *, &metadata_tab);
+    metadata->tab = map;
+
+    return tun_metadata_from_geneve__(map, opts, opts, opt_len, metadata);
+}
+
+static void
+tun_metadata_to_geneve__(const struct tun_metadata *flow, struct ofpbuf *b,
+                         bool *crit_opt)
+{
+    struct tun_table *map;
+    int i;
+
     map = flow->tab;
     if (!map) {
         map = ovsrcu_get(struct tun_table *, &metadata_tab);
     }
 
-    /* For all intents and purposes, the Geneve options are nested
-     * attributes even if this doesn't show up directly to netlink. It's
-     * similar enough that we can use the same mechanism. */
-    nlattr_offset = nl_msg_start_nested(b, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
+    *crit_opt = false;
 
     ULLONG_FOR_EACH_1 (i, flow->opt_map) {
         struct tun_meta_entry *entry = &map->entries[i];
@@ -698,11 +705,43 @@ tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow,
         opt->r3 = 0;
 
         memcpy_from_metadata(opt + 1, flow, &entry->loc);
+        *crit_opt |= !!(opt->type & GENEVE_CRIT_OPT_TYPE);
     }
+}
+
+void
+tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow,
+                                   struct ofpbuf *b)
+{
+    size_t nlattr_offset;
+    bool crit_opt;
+
+    if (!flow->opt_map) {
+        return;
+    }
+
+    /* For all intents and purposes, the Geneve options are nested
+     * attributes even if this doesn't show up directly to netlink. It's
+     * similar enough that we can use the same mechanism. */
+    nlattr_offset = nl_msg_start_nested(b, OVS_TUNNEL_KEY_ATTR_GENEVE_OPTS);
+
+    tun_metadata_to_geneve__(flow, b, &crit_opt);
 
     nl_msg_end_nested(b, nlattr_offset);
 }
 
+int
+tun_metadata_to_geneve_header(const struct tun_metadata *flow,
+                              struct geneve_opt *opts, bool *crit_opt)
+{
+    struct ofpbuf b;
+
+    ofpbuf_use_stack(&b, opts, GENEVE_TOT_OPT_SIZE);
+    tun_metadata_to_geneve__(flow, &b, crit_opt);
+
+    return b.size;
+}
+
 void
 tun_metadata_to_geneve_nlattr_mask(const struct ofpbuf *key,
                                    const struct tun_metadata *mask,
index 76a72e8..a1fbc0a 100644 (file)
@@ -30,6 +30,7 @@ union mf_value;
 struct ofputil_geneve_table_mod;
 struct ofputil_geneve_table_reply;
 struct tun_table;
+struct geneve_opt;
 
 #define TUN_METADATA_NUM_OPTS 64
 #define TUN_METADATA_TOT_OPT_SIZE 256
@@ -95,12 +96,18 @@ int tun_metadata_from_geneve_nlattr(const struct nlattr *attr,
                                     size_t flow_attr_len,
                                     const struct tun_metadata *flow_metadata,
                                     struct tun_metadata *metadata);
+int tun_metadata_from_geneve_header(const struct geneve_opt *, int opt_len,
+                                    struct tun_metadata *metadata);
+
 void tun_metadata_to_geneve_nlattr_flow(const struct tun_metadata *flow,
                                         struct ofpbuf *);
 void tun_metadata_to_geneve_nlattr_mask(const struct ofpbuf *key,
                                         const struct tun_metadata *mask,
                                         const struct tun_metadata *flow,
                                         struct ofpbuf *);
+int tun_metadata_to_geneve_header(const struct tun_metadata *flow,
+                                  struct geneve_opt *, bool *crit_opt);
+
 void tun_metadata_to_nx_match(struct ofpbuf *b, enum ofp_version oxm,
                               const struct match *);
 void tun_metadata_match_format(struct ds *, const struct match *);
index 4dcf0b0..090b976 100644 (file)
@@ -291,6 +291,7 @@ tnl_push(tnl_port(4),header(size=42,type=3,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:1
 tnl_push(tnl_port(4),header(size=46,type=3,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=47,tos=0,ttl=64,frag=0x40),gre((flags=0xa000,proto=0x6558),csum=0x0,key=0x1e241)),out_port(1))
 tnl_push(tnl_port(6),header(size=50,type=4,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=4789,csum=0x0),vxlan(flags=0x8000000,vni=0x1c7)),out_port(1))
 tnl_push(tnl_port(6),header(size=50,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0x0),geneve(oam,vni=0x1c7)),out_port(1))
+tnl_push(tnl_port(6),header(size=58,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0x0),geneve(crit,vni=0x1c7,options({class=0xffff,type=0x80,len=4,0xa}))),out_port(1))
 tnl_push(tnl_port(6),header(size=50,type=5,eth(dst=f8:bc:12:44:34:b6,src=f8:bc:12:46:58:e0,dl_type=0x0800),ipv4(src=1.1.2.88,dst=1.1.2.92,proto=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0xffff),geneve(vni=0x1c7)),out_port(1))
 ])
 AT_CHECK_UNQUOTED([ovstest test-odp parse-actions < actions.txt], [0],
index d9e8496..bd95c8e 100644 (file)
@@ -98,6 +98,14 @@ AT_CHECK([tail -1 stdout], [0],
   [Datapath actions: tnl_push(tnl_port(6081),header(size=50,type=5,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=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0x0),geneve(vni=0x7b)),out_port(100))
 ])
 
+dnl Check Geneve tunnel push with options
+AT_CHECK([ovs-ofctl add-geneve-map int-br "{class=0xffff,type=0x80,len=4}->tun_metadata0"])
+AT_CHECK([ovs-ofctl add-flow int-br "actions=set_field:1.1.2.92->tun_dst,set_field:0xa->tun_metadata0,5"])
+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(6081),header(size=58,type=5,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=17,tos=0,ttl=64,frag=0x40),udp(src=0,dst=6081,csum=0x0),geneve(crit,vni=0x7b,options({class=0xffff,type=0x80,len=4,0xa}))),out_port(100))
+])
+
 dnl Check decapsulation of GRE packet
 AT_CHECK([ovs-appctl netdev-dummy/receive p0 '001b213cac30001b213cab6408004500007e79464000402f99080101025c0101025820006558000001c8fe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
 ovs-appctl time/warp 1000
@@ -114,5 +122,18 @@ 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 decapsulation of Geneve packet with options
+AT_CHECK([ovs-ofctl del-flows int-br])
+AT_CHECK([ovs-ofctl add-flow int-br "tun_metadata0=0xa/0xf,actions=5"])
+AT_CHECK([ovs-appctl netdev-dummy/receive p0 '001b213cac30001b213cab64080045000096794640004011ba630101025c01010258308817c1008200000400655800007b00ffff80010000000affff00010000000bfe71d883724fbeb6f4e1494a080045000054ba200000400184861e0000011e00000200004227e75400030af3195500000000f265010000000000101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f3031323334353637'])
+
+ovs-appctl time/warp 1000
+AT_CHECK([ovs-ofctl dump-ports int-br | grep 'port  5'], [0], [dnl
+  port  5: rx pkts=1, bytes=98, drop=0, errs=0, frame=0, over=0, crc=0
+])
+AT_CHECK([ovs-appctl dpif/dump-flows int-br], [0], [dnl
+tunnel(tun_id=0x7b,src=1.1.2.92,dst=1.1.2.88,ttl=64,geneve({class=0xffff,type=0x80,len=4,0xa/0xf}),flags(-df-csum+key)),skb_mark(0),recirc_id(0),in_port(6081),eth_type(0x0800),ipv4(frag=no), packets:0, bytes:0, used:never, actions:drop
+])
+
 OVS_VSWITCHD_STOP
 AT_CLEANUP