[ovs-dev,v2] conntrack: Add option to enable TCP liberal mode.
diff mbox series

Message ID 1559222986-2134-1-git-send-email-dlu998@gmail.com
State New
Headers show
Series
  • [ovs-dev,v2] conntrack: Add option to enable TCP liberal mode.
Related show

Commit Message

Darrell Ball May 30, 2019, 1:29 p.m. UTC
Reported-at: https://mail.openvswitch.org/pipermail/ovs-dev/2019-May/359188.html
Signed-off-by: Darrell Ball <dlu998@gmail.com>
---

v2: Remove presently redundant check.

 NEWS                    |   1 +
 lib/conntrack-private.h |   6 ++-
 lib/conntrack-tcp.c     |  20 +++----
 lib/conntrack.c         |  15 ++++++
 lib/conntrack.h         |   2 +
 lib/ct-dpif.c           |  16 ++++++
 lib/ct-dpif.h           |   2 +
 lib/dpctl.c             |  64 +++++++++++++++++++++-
 lib/dpctl.man           |  16 ++++++
 lib/dpif-netdev.c       |  18 +++++++
 lib/dpif-netlink.c      |   2 +
 lib/dpif-provider.h     |   5 ++
 tests/ofproto-dpif.at   | 138 ++++++++++++++++++++++++++++++++++++++++++++++++
 13 files changed, 293 insertions(+), 12 deletions(-)

Patch
diff mbox series

diff --git a/NEWS b/NEWS
index 19cebf8..625a15b 100644
--- a/NEWS
+++ b/NEWS
@@ -23,6 +23,7 @@  Post-v2.11.0
      * New action "check_pkt_len".
      * Port configuration with "other-config:priority-tags" now has a mode
        that retains the 802.1Q header even if VLAN and priority are both zero.
+     * Add option to enable, disable and query TCP liberal mode in conntrack.
    - OVSDB:
      * OVSDB clients can now resynchronize with clustered servers much more
        quickly after a brief disconnection, saving bandwidth and CPU time.
diff --git a/lib/conntrack-private.h b/lib/conntrack-private.h
index 51b7d7f..c142032 100644
--- a/lib/conntrack-private.h
+++ b/lib/conntrack-private.h
@@ -170,8 +170,10 @@  struct conntrack {
     struct hindex alg_expectation_refs OVS_GUARDED; /* For lookup from
                                                      * control context.  */
 
-    /* Fragmentation handling context. */
-    struct ipf *ipf;
+    struct ipf *ipf; /* Fragmentation handling context. */
+    atomic_bool tcp_liberal; /* TCP liberal mode sequence number verification;
+                                when enabled, this disables most sequence
+                                number verification; disabled by default. */
 };
 
 /* Lock acquisition order:
diff --git a/lib/conntrack-tcp.c b/lib/conntrack-tcp.c
index 397aca1..e95d2f3 100644
--- a/lib/conntrack-tcp.c
+++ b/lib/conntrack-tcp.c
@@ -272,16 +272,18 @@  tcp_conn_update(struct conntrack *ct, struct conn *conn_,
 
     int ackskew = check_ackskew ? dst->seqlo - ack : 0;
 #define MAXACKWINDOW (0xffff + 1500)    /* 1500 is an arbitrary fudge factor */
-    if (SEQ_GEQ(src->seqhi, end)
-        /* Last octet inside other's window space */
-        && SEQ_GEQ(seq, src->seqlo - (dst->max_win << dws))
-        /* Retrans: not more than one window back */
-        && (ackskew >= -MAXACKWINDOW)
-        /* Acking not more than one reassembled fragment backwards */
-        && (ackskew <= (MAXACKWINDOW << sws))
-        /* Acking not more than one window forward */
+    if (((SEQ_GEQ(src->seqhi, end)
+          /* Last octet inside other's window space */
+          && SEQ_GEQ(seq, src->seqlo - (dst->max_win << dws))
+          /* Retrans: not more than one window back */
+          && ackskew >= -MAXACKWINDOW
+          /* Acking not more than one reassembled fragment backwards */
+          && ackskew <= (MAXACKWINDOW << sws))
+         || conntrack_get_tcp_liberal(ct))
+          /* Acking not more than one window forward */
         && ((tcp_flags & TCP_RST) == 0 || orig_seq == src->seqlo
-            || (orig_seq == src->seqlo + 1) || (orig_seq + 1 == src->seqlo))) {
+             || (orig_seq == src->seqlo + 1)
+             || (orig_seq + 1 == src->seqlo))) {
         /* Require an exact/+1 sequence match on resets when possible */
 
         /* update max window */
diff --git a/lib/conntrack.c b/lib/conntrack.c
index d7d48a4..0ed647a 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -2383,6 +2383,21 @@  conntrack_get_nconns(struct conntrack *ct, uint32_t *nconns)
     return 0;
 }
 
+int
+conntrack_set_tcp_liberal(struct conntrack *ct, bool enabled)
+{
+    atomic_store_relaxed(&ct->tcp_liberal, enabled);
+    return 0;
+}
+
+bool
+conntrack_get_tcp_liberal(struct conntrack *ct)
+{
+    bool enabled;
+    atomic_read_relaxed(&ct->tcp_liberal, &enabled);
+    return enabled;
+}
+
 /* This function must be called with the ct->resources read lock taken. */
 static struct alg_exp_node *
 expectation_lookup(struct hmap *alg_expectations, const struct conn_key *key,
diff --git a/lib/conntrack.h b/lib/conntrack.h
index 2012150..05d05c0 100644
--- a/lib/conntrack.h
+++ b/lib/conntrack.h
@@ -118,6 +118,8 @@  int conntrack_flush_tuple(struct conntrack *, const struct ct_dpif_tuple *,
 int conntrack_set_maxconns(struct conntrack *ct, uint32_t maxconns);
 int conntrack_get_maxconns(struct conntrack *ct, uint32_t *maxconns);
 int conntrack_get_nconns(struct conntrack *ct, uint32_t *nconns);
+int conntrack_set_tcp_liberal(struct conntrack *ct, bool enabled);
+bool conntrack_get_tcp_liberal(struct conntrack *ct);
 struct ipf *conntrack_ipf_ctx(struct conntrack *ct);
 
 #endif /* conntrack.h */
diff --git a/lib/ct-dpif.c b/lib/ct-dpif.c
index 5d8a75d..74ee1c7 100644
--- a/lib/ct-dpif.c
+++ b/lib/ct-dpif.c
@@ -165,6 +165,22 @@  ct_dpif_get_nconns(struct dpif *dpif, uint32_t *nconns)
 }
 
 int
+ct_dpif_set_tcp_liberal(struct dpif *dpif, bool enabled)
+{
+    return (dpif->dpif_class->ct_set_tcp_liberal
+            ? dpif->dpif_class->ct_set_tcp_liberal(dpif, enabled)
+            : EOPNOTSUPP);
+}
+
+int
+ct_dpif_get_tcp_liberal(struct dpif *dpif, bool *enabled)
+{
+    return (dpif->dpif_class->ct_get_tcp_liberal
+            ? dpif->dpif_class->ct_get_tcp_liberal(dpif, enabled)
+            : EOPNOTSUPP);
+}
+
+int
 ct_dpif_set_limits(struct dpif *dpif, const uint32_t *default_limit,
                    const struct ovs_list *zone_limits)
 {
diff --git a/lib/ct-dpif.h b/lib/ct-dpif.h
index 14178bb..70e619e 100644
--- a/lib/ct-dpif.h
+++ b/lib/ct-dpif.h
@@ -234,6 +234,8 @@  int ct_dpif_flush(struct dpif *, const uint16_t *zone,
 int ct_dpif_set_maxconns(struct dpif *dpif, uint32_t maxconns);
 int ct_dpif_get_maxconns(struct dpif *dpif, uint32_t *maxconns);
 int ct_dpif_get_nconns(struct dpif *dpif, uint32_t *nconns);
+int ct_dpif_set_tcp_liberal(struct dpif *dpif, bool enabled);
+int ct_dpif_get_tcp_liberal(struct dpif *dpif, bool *enabled);
 int ct_dpif_set_limits(struct dpif *dpif, const uint32_t *default_limit,
                        const struct ovs_list *);
 int ct_dpif_get_limits(struct dpif *dpif, uint32_t *default_limit,
diff --git a/lib/dpctl.c b/lib/dpctl.c
index 9c4eb65..66a4327 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -1760,6 +1760,62 @@  dpctl_ct_get_nconns(int argc, const char *argv[],
 }
 
 static int
+dpctl_ct_set_tcp_liberal__(int argc, const char *argv[],
+                           struct dpctl_params *dpctl_p, bool enabled)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 3, &dpif);
+    if (!error) {
+        error = ct_dpif_set_tcp_liberal(dpif, enabled);
+        if (!error) {
+            dpctl_print(dpctl_p,
+                        "%s TCP liberal mode successful",
+                        enabled ? "enabling" : "disabling");
+        } else {
+            dpctl_error(dpctl_p, error,
+                        "%s TCP liberal mode failed",
+                        enabled ? "enabling" : "disabling");
+        }
+        dpif_close(dpif);
+    }
+    return error;
+}
+
+static int
+dpctl_ct_enable_tcp_liberal(int argc, const char *argv[],
+                            struct dpctl_params *dpctl_p)
+{
+    return dpctl_ct_set_tcp_liberal__(argc, argv, dpctl_p, true);
+}
+
+static int
+dpctl_ct_disable_tcp_liberal(int argc, const char *argv[],
+                             struct dpctl_params *dpctl_p)
+{
+    return dpctl_ct_set_tcp_liberal__(argc, argv, dpctl_p, false);
+}
+
+static int
+dpctl_ct_get_tcp_liberal(int argc, const char *argv[],
+                         struct dpctl_params *dpctl_p)
+{
+    struct dpif *dpif;
+    int error = opt_dpif_open(argc, argv, dpctl_p, 2, &dpif);
+    if (!error) {
+        bool enabled;
+        error = ct_dpif_get_tcp_liberal(dpif, &enabled);
+        if (!error) {
+            dpctl_print(dpctl_p, "TCP liberal mode: %s\n",
+                        enabled ? "enabled" : "disabled");
+        } else {
+            dpctl_error(dpctl_p, error, "TCP liberal mode query failed");
+        }
+        dpif_close(dpif);
+    }
+    return error;
+}
+
+static int
 dpctl_ct_set_limits(int argc, const char *argv[],
                     struct dpctl_params *dpctl_p)
 {
@@ -2428,9 +2484,15 @@  static const struct dpctl_command all_commands[] = {
     { "ct-stats-show", "[dp] [zone=N]",
       0, 3, dpctl_ct_stats_show, DP_RO },
     { "ct-bkts", "[dp] [gt=N]", 0, 2, dpctl_ct_bkts, DP_RO },
-    { "ct-set-maxconns", "[dp] maxconns", 1, 2, dpctl_ct_set_maxconns, DP_RW },
+    { "ct-set-maxconns", "[dp] maxconns", 1, 2, dpctl_ct_set_maxconns,
+       DP_RW },
     { "ct-get-maxconns", "[dp]", 0, 1, dpctl_ct_get_maxconns, DP_RO },
     { "ct-get-nconns", "[dp]", 0, 1, dpctl_ct_get_nconns, DP_RO },
+    { "ct-enable-tcp-liberal", "[dp]", 0, 1, dpctl_ct_enable_tcp_liberal,
+       DP_RW },
+    { "ct-disable-tcp-liberal", "[dp]", 0, 1, dpctl_ct_disable_tcp_liberal,
+       DP_RW },
+    { "ct-get-tcp-liberal", "[dp]", 0, 1, dpctl_ct_get_tcp_liberal, DP_RO },
     { "ct-set-limits", "[dp] [default=L] [zone=N,limit=L]...", 1, INT_MAX,
         dpctl_ct_set_limits, DP_RO },
     { "ct-del-limits", "[dp] zone=N1[,N2]...", 1, 2, dpctl_ct_del_limits,
diff --git a/lib/dpctl.man b/lib/dpctl.man
index 1ff3511..9127bd2 100644
--- a/lib/dpctl.man
+++ b/lib/dpctl.man
@@ -318,6 +318,22 @@  Prints the current number of connection tracker entries on \fIdp\fR.
 Only supported for userspace datapath.
 .
 .TP
+\*(DX\fBct\-enable\-tcp\-liberal\fR [\fIdp\fR]
+.TQ
+\*(DX\fBct\-disable\-tcp\-liberal\fR [\fIdp\fR]
+Enables or disables TCP liberal mode for the userspace connection tracker.
+If enabled, TCP sequence number verification will be mostly disabled, except
+for TCP resets.  This is disabled by default to enforce better security and
+should only be enabled if absolutely required.  This command is only
+supported for the userspace datapath, but can be otherwise enabled in the
+Linux kernel datapath.
+.
+.TP
+\*(DX\fBct\-get\-tcp\-liberal\fR [\fIdp\fR]
+Prints whether TCP liberal mode is enabled or disabled on \fIdp\fR.
+Only supported for userspace datapath.
+.
+.TP
 \*(DX\fBct\-set\-limits\fR [\fIdp\fR] [\fBdefault=\fIdefault_limit\fR] [\fBzone=\fIzone\fR,\fBlimit=\fIlimit\fR]...
 Sets the maximum allowed number of connections in a connection tracking
 zone.  A specific \fIzone\fR may be set to \fIlimit\fR, and multiple zones
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index df63f01..79627b7 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -7352,6 +7352,22 @@  dpif_netdev_ct_get_nconns(struct dpif *dpif, uint32_t *nconns)
 }
 
 static int
+dpif_netdev_ct_set_tcp_liberal(struct dpif *dpif, bool enabled)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+
+    return conntrack_set_tcp_liberal(dp->conntrack, enabled);
+}
+
+static int
+dpif_netdev_ct_get_tcp_liberal(struct dpif *dpif, bool *enabled)
+{
+    struct dp_netdev *dp = get_dp_netdev(dpif);
+    *enabled = conntrack_get_tcp_liberal(dp->conntrack);
+    return 0;
+}
+
+static int
 dpif_netdev_ipf_set_enabled(struct dpif *dpif, bool v6, bool enable)
 {
     struct dp_netdev *dp = get_dp_netdev(dpif);
@@ -7454,6 +7470,8 @@  const struct dpif_class dpif_netdev_class = {
     dpif_netdev_ct_set_maxconns,
     dpif_netdev_ct_get_maxconns,
     dpif_netdev_ct_get_nconns,
+    dpif_netdev_ct_set_tcp_liberal,
+    dpif_netdev_ct_get_tcp_liberal,
     NULL,                       /* ct_set_limits */
     NULL,                       /* ct_get_limits */
     NULL,                       /* ct_del_limits */
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index c554666..f97c118 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -3429,6 +3429,8 @@  const struct dpif_class dpif_netlink_class = {
     NULL,                       /* ct_set_maxconns */
     NULL,                       /* ct_get_maxconns */
     NULL,                       /* ct_get_nconns */
+    NULL,                       /* ct_set_tcp_liberal */
+    NULL,                       /* ct_get_tcp_liberal */
     dpif_netlink_ct_set_limits,
     dpif_netlink_ct_get_limits,
     dpif_netlink_ct_del_limits,
diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h
index b2a4dff..d18dbfa 100644
--- a/lib/dpif-provider.h
+++ b/lib/dpif-provider.h
@@ -462,6 +462,11 @@  struct dpif_class {
     int (*ct_get_maxconns)(struct dpif *, uint32_t *maxconns);
     /* Get number of connections tracked. */
     int (*ct_get_nconns)(struct dpif *, uint32_t *nconns);
+    /* Enable or disable TCP liberal mode. */
+    int (*ct_set_tcp_liberal)(struct dpif *, bool enabled);
+    /* Get the TCP liberal mode configuration. */
+    int (*ct_get_tcp_liberal)(struct dpif *, bool *enabled);
+
 
     /* Connection tracking per zone limit */
 
diff --git a/tests/ofproto-dpif.at b/tests/ofproto-dpif.at
index 9640d1c..458cf56 100644
--- a/tests/ofproto-dpif.at
+++ b/tests/ofproto-dpif.at
@@ -10301,6 +10301,144 @@  n_packets=0
 OVS_VSWITCHD_STOP
 AT_CLEANUP
 
+AT_SETUP([ofproto-dpif - conntrack - tcp liberal])
+OVS_VSWITCHD_START
+
+add_of_ports br0 1 2
+
+dnl Allow new connections on p1->p2. Allow only established connections p2->p1
+AT_DATA([flows.txt], [dnl
+dnl Table 0
+dnl
+table=0,priority=10,in_port=1,ip,action=ct(commit,table=1)
+table=0,priority=10,in_port=2,ip,action=ct(table=1)
+table=0,priority=1,action=drop
+dnl
+dnl Table 1
+dnl
+dnl The following two flows are separated to explicitly count the packets
+dnl that create a new connection
+table=1,priority=100,cookie=0x1,in_port=1,ip,ct_state=+trk+new-inv-rpl,action=2
+table=1,priority=100,cookie=0x2,in_port=1,ip,ct_state=+trk-new-inv-rpl,action=2
+dnl
+table=1,priority=100,cookie=0x3,in_port=2,ip,ct_state=+trk+est+rpl-new-inv,action=1
+table=1,cookie=0x4,ip,ct_state=+trk+inv,action=drop
+])
+
+AT_CHECK([ovs-ofctl add-flows br0 flows.txt])
+
+dnl Send 9 packets; one packet will be marked invalid.
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a50540000000908004500002ca4e5400040067fe20a0101010a0101020001000259b5d93f0000000060027210dd190000020405b4'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a08004500002c00004000400624c80a0101020a010101000200017c35468459b5d940601272101a4f0000020405b4'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e6400040067fe50a0101010a0101020001000259b5d9407c35468550107210320c0000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a0800450000303b5f40004006e9640a0101020a010101000200010000000000000000501872106a7300007061796c6f61640a'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e7400040067fe40a0101010a0101020001000259b5d9407c35468d5010721032040000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a0800450000283b6040004006e96b0a0101020a010101000200017c35468d59b5d9405011721032030000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e8400040067fe30a0101010a0101020001000259b5d9407c35468e5010721032030000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e9400040067fe20a0101010a0101020001000259b5d9407c35468e5011721032020000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a0800450000283b6140004006e96a0a0101020a010101000200017c35468e59b5d9415010721032020000'])
+
+AT_CHECK([ovs-appctl revalidator/purge])
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x1 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=1
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x2 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=4
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x3 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=3
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x4 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=1
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+AT_CHECK([ovs-appctl dpctl/ct-get-tcp-liberal], [], [dnl
+TCP liberal mode: disabled
+])
+
+AT_CHECK([ovs-appctl dpctl/ct-enable-tcp-liberal], [], [dnl
+enabling TCP liberal mode successful
+])
+
+AT_CHECK([ovs-appctl dpctl/ct-get-tcp-liberal], [], [dnl
+TCP liberal mode: enabled
+])
+
+dnl Send exactly the same 9 packets to confirm no additional packets are marked invalid.
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a50540000000908004500002ca4e5400040067fe20a0101010a0101020001000259b5d93f0000000060027210dd190000020405b4'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a08004500002c00004000400624c80a0101020a010101000200017c35468459b5d940601272101a4f0000020405b4'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e6400040067fe50a0101010a0101020001000259b5d9407c35468550107210320c0000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a0800450000303b5f40004006e9640a0101020a010101000200010000000000000000501872106a7300007061796c6f61640a'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e7400040067fe40a0101010a0101020001000259b5d9407c35468d5010721032040000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a0800450000283b6040004006e96b0a0101020a010101000200017c35468d59b5d9405011721032030000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e8400040067fe30a0101010a0101020001000259b5d9407c35468e5010721032030000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e9400040067fe20a0101010a0101020001000259b5d9407c35468e5011721032020000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a0800450000283b6140004006e96a0a0101020a010101000200017c35468e59b5d9415010721032020000'])
+
+AT_CHECK([ovs-appctl revalidator/purge])
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x1 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=2
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x2 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=8
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x3 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=7
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x4 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=1
+])
+
+AT_CHECK([ovs-appctl dpctl/ct-disable-tcp-liberal], [], [dnl
+disabling TCP liberal mode successful
+])
+
+AT_CHECK([ovs-appctl dpctl/ct-get-tcp-liberal], [], [dnl
+TCP liberal mode: disabled
+])
+
+AT_CHECK([ovs-appctl dpctl/flush-conntrack])
+
+dnl Send exactly the same 9 packets after disabling TCP liberal mode to
+dnl confirm one more packet is marked invalid.
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a50540000000908004500002ca4e5400040067fe20a0101010a0101020001000259b5d93f0000000060027210dd190000020405b4'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a08004500002c00004000400624c80a0101020a010101000200017c35468459b5d940601272101a4f0000020405b4'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e6400040067fe50a0101010a0101020001000259b5d9407c35468550107210320c0000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a0800450000303b5f40004006e9640a0101020a010101000200010000000000000000501872106a7300007061796c6f61640a'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e7400040067fe40a0101010a0101020001000259b5d9407c35468d5010721032040000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a0800450000283b6040004006e96b0a0101020a010101000200017c35468d59b5d9405011721032030000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e8400040067fe30a0101010a0101020001000259b5d9407c35468e5010721032030000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p1 '50540000000a505400000009080045000028a4e9400040067fe20a0101010a0101020001000259b5d9407c35468e5011721032020000'])
+AT_CHECK([ovs-appctl netdev-dummy/receive p2 '50540000000950540000000a0800450000283b6140004006e96a0a0101020a010101000200017c35468e59b5d9415010721032020000'])
+
+AT_CHECK([ovs-appctl revalidator/purge])
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x1 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=3
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x2 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=12
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x3 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=10
+])
+
+AT_CHECK([ovs-ofctl dump-flows br0 | grep cookie=0x4 | grep -o "n_packets=[[0-9]]*"], [0], [dnl
+n_packets=2
+])
+
+OVS_VSWITCHD_STOP
+AT_CLEANUP
+
 dnl This is a truncated version of "ofproto-dpif - conntrack - controller",
 dnl with extra send-to-controller actions following ct_clear to show that
 dnl the connection tracking data has been cleared.