[ovs-dev,”patch_v2”] conntrack: Support global invalid packet stats.

Message ID 1530843826-13160-1-git-send-email-dlu998@gmail.com
State Deferred
Headers show
Series
  • [ovs-dev,”patch_v2”] conntrack: Support global invalid packet stats.
Related show

Commit Message

Darrell Ball July 6, 2018, 2:23 a.m.
Only standard sanity failures, which are normally rare, are tracked.

Signed-off-by: Darrell Ball <dlu998@gmail.com>
---
 NEWS                |   2 +
 lib/conntrack.c     | 114 +++++++++++++++++++++++++++++++++++++++++++++++++---
 lib/conntrack.h     |  15 +++++++
 lib/ct-dpif.c       |  27 +++++++++++++
 lib/ct-dpif.h       |  15 +++++++
 lib/dpctl.c         |  66 ++++++++++++++++++++++++++++++
 lib/dpctl.man       |   3 +-
 lib/dpif-netdev.c   |  29 +++++++++++++
 lib/dpif-netlink.c  |   1 +
 lib/dpif-provider.h |   6 +++
 10 files changed, 272 insertions(+), 6 deletions(-)

Comments

Ben Pfaff Sept. 5, 2018, 9:26 p.m. | #1
On Thu, Jul 05, 2018 at 07:23:46PM -0700, Darrell Ball wrote:
> Only standard sanity failures, which are normally rare, are tracked.
> 
> Signed-off-by: Darrell Ball <dlu998@gmail.com>

I'm unsure of the status of this patch.  At one point, I think you
were positive about the idea of using coverage counters.  Is this patch
still proposed for review?
Darrell Ball Sept. 5, 2018, 9:37 p.m. | #2
On 9/5/18, 2:26 PM, "ovs-dev-bounces@openvswitch.org on behalf of Ben Pfaff" <ovs-dev-bounces@openvswitch.org on behalf of blp@ovn.org> wrote:

    On Thu, Jul 05, 2018 at 07:23:46PM -0700, Darrell Ball wrote:
    > Only standard sanity failures, which are normally rare, are tracked.
    > 
    > Signed-off-by: Darrell Ball <dlu998@gmail.com>
    
    I'm unsure of the status of this patch.  At one point, I think you
    were positive about the idea of using coverage counters.  Is this patch
    still proposed for review?


Ohh, it seems I forgot to update this in this thread.
I had wanted to wait till fragmentation goes in, because there is overlap in the counters


    _______________________________________________
    dev mailing list
    dev@openvswitch.org
    https://na01.safelinks.protection.outlook.com/?url=https%3A%2F%2Fmail.openvswitch.org%2Fmailman%2Flistinfo%2Fovs-dev&amp;data=02%7C01%7Cdball%40vmware.com%7C501eba6a126145b21fa508d61376415d%7Cb39138ca3cee4b4aa4d6cd83d9dd62f0%7C1%7C0%7C636717795989363382&amp;sdata=Z3UGvWhd4LjCuzJ3jJhak%2FC259HlzwDcg%2BolOr%2F3TPk%3D&amp;reserved=0
Ben Pfaff Sept. 5, 2018, 10:06 p.m. | #3
On Wed, Sep 05, 2018 at 09:37:08PM +0000, Darrell Ball wrote:
> 
> 
> On 9/5/18, 2:26 PM, "ovs-dev-bounces@openvswitch.org on behalf of Ben Pfaff" <ovs-dev-bounces@openvswitch.org on behalf of blp@ovn.org> wrote:
> 
>     On Thu, Jul 05, 2018 at 07:23:46PM -0700, Darrell Ball wrote:
>     > Only standard sanity failures, which are normally rare, are tracked.
>     > 
>     > Signed-off-by: Darrell Ball <dlu998@gmail.com>
>     
>     I'm unsure of the status of this patch.  At one point, I think you
>     were positive about the idea of using coverage counters.  Is this patch
>     still proposed for review?
> 
> 
> Ohh, it seems I forgot to update this in this thread.
> I had wanted to wait till fragmentation goes in, because there is overlap in the counters

OK, so this patch is deferred for now?  I'll assume that you'll post v3
when it's ready, then.

Patch

diff --git a/NEWS b/NEWS
index cd15a33..84b0b8d 100644
--- a/NEWS
+++ b/NEWS
@@ -40,6 +40,8 @@  Post-v2.9.0
          ovs-appctl dpif-netdev/pmd-perf-show
      * Supervision of PMD performance metrics and logging of suspicious
        iterations
+     * ovs-appctl dpctl/ct-stats-show now prints global conntrack invalid
+       marked sanity failure packet statistics.
    - ERSPAN:
      * Implemented ERSPAN protocol (draft-foschiano-erspan-00.txt) for
        both kernel datapath and userspace datapath.
diff --git a/lib/conntrack.c b/lib/conntrack.c
index 97fd46a..30fd506 100644
--- a/lib/conntrack.c
+++ b/lib/conntrack.c
@@ -226,6 +226,27 @@  long long ct_timeout_val[] = {
  * are accepted; this is for CT_CONN_TYPE_DEFAULT connections. */
 #define DEFAULT_N_CONN_LIMIT 3000000
 
+/* IPv4 sanity invalid packets. */
+static atomic_count min_hdr_err_v4;
+static atomic_count size_err_v4;
+static atomic_count cksum_err_v4;
+static atomic_count fragment_v4;
+
+/* IPv6 sanity invalid packets. */
+static atomic_count min_hdr_err_v6;
+static atomic_count hdr_parse_err_v6;
+static atomic_count fragment_v6;
+
+/* L4 sanity invalid packets. */
+static atomic_count hdr_size_err_tcp;
+static atomic_count size_err_tcp;
+static atomic_count cksum_err_tcp;
+static atomic_count hdr_size_err_udp;
+static atomic_count size_err_udp;
+static atomic_count cksum_err_udp;
+static atomic_count cksum_err_icmp;
+static atomic_count cksum_err_icmp6;
+
 /* Does a member by member comparison of two conn_keys; this
  * function must be kept in sync with struct conn_key; returns 0
  * if the keys are equal or 1 if the keys are not equal. */
@@ -337,6 +358,22 @@  conntrack_init(struct conntrack *ct)
     ct->hash_basis = random_uint32();
     atomic_count_init(&ct->n_conn, 0);
     atomic_init(&ct->n_conn_limit, DEFAULT_N_CONN_LIMIT);
+    atomic_count_init(&min_hdr_err_v4, 0);
+    atomic_count_init(&size_err_v4, 0);
+    atomic_count_init(&cksum_err_v4, 0);
+    atomic_count_init(&fragment_v4, 0);
+    atomic_count_init(&min_hdr_err_v6, 0);
+    atomic_count_init(&hdr_parse_err_v6, 0);
+    atomic_count_init(&fragment_v6, 0);
+    atomic_count_init(&hdr_size_err_tcp, 0);
+    atomic_count_init(&size_err_tcp, 0);
+    atomic_count_init(&cksum_err_tcp, 0);
+    atomic_count_init(&hdr_size_err_udp, 0);
+    atomic_count_init(&size_err_udp, 0);
+    atomic_count_init(&cksum_err_udp, 0);
+    atomic_count_init(&cksum_err_icmp, 0);
+    atomic_count_init(&cksum_err_icmp6, 0);
+
     latch_init(&ct->clean_thread_exit);
     ct->clean_thread = ovs_thread_create("ct_clean", clean_thread_main, ct);
 }
@@ -1504,6 +1541,7 @@  extract_l3_ipv4(struct conn_key *key, const void *data, size_t size,
                 const char **new_data, bool validate_checksum)
 {
     if (OVS_UNLIKELY(size < IP_HEADER_LEN)) {
+        atomic_count_inc(&min_hdr_err_v4);
         return false;
     }
 
@@ -1511,18 +1549,22 @@  extract_l3_ipv4(struct conn_key *key, const void *data, size_t size,
     size_t ip_len = IP_IHL(ip->ip_ihl_ver) * 4;
 
     if (OVS_UNLIKELY(ip_len < IP_HEADER_LEN)) {
+        atomic_count_inc(&min_hdr_err_v4);
         return false;
     }
 
     if (OVS_UNLIKELY(size < ip_len)) {
+        atomic_count_inc(&size_err_v4);
         return false;
     }
 
     if (IP_IS_FRAGMENT(ip->ip_frag_off)) {
+        atomic_count_inc(&fragment_v4);
         return false;
     }
 
     if (validate_checksum && csum(data, ip_len) != 0) {
+        atomic_count_inc(&cksum_err_v4);
         return false;
     }
 
@@ -1547,6 +1589,7 @@  extract_l3_ipv6(struct conn_key *key, const void *data, size_t size,
     const struct ovs_16aligned_ip6_hdr *ip6 = data;
 
     if (OVS_UNLIKELY(size < sizeof *ip6)) {
+        atomic_count_inc(&min_hdr_err_v6);
         return false;
     }
 
@@ -1556,10 +1599,12 @@  extract_l3_ipv6(struct conn_key *key, const void *data, size_t size,
     uint8_t nw_frag = 0;
 
     if (!parse_ipv6_ext_hdrs(&data, &size, &nw_proto, &nw_frag)) {
+        atomic_count_inc(&hdr_parse_err_v6);
         return false;
     }
 
     if (nw_frag) {
+        atomic_count_inc(&fragment_v4);
         return false;
     }
 
@@ -1599,15 +1644,21 @@  check_l4_tcp(const struct conn_key *key, const void *data, size_t size,
 {
     const struct tcp_header *tcp = data;
     if (size < sizeof *tcp) {
+        atomic_count_inc(&hdr_size_err_tcp);
         return false;
     }
 
     size_t tcp_len = TCP_OFFSET(tcp->tcp_ctl) * 4;
     if (OVS_UNLIKELY(tcp_len < TCP_HEADER_LEN || tcp_len > size)) {
+        atomic_count_inc(&size_err_tcp);
         return false;
     }
 
-    return validate_checksum ? checksum_valid(key, data, size, l3) : true;
+    if (validate_checksum && !checksum_valid(key, data, size, l3)) {
+        atomic_count_inc(&cksum_err_tcp);
+        return false;
+    }
+    return true;
 }
 
 static inline bool
@@ -1616,30 +1667,44 @@  check_l4_udp(const struct conn_key *key, const void *data, size_t size,
 {
     const struct udp_header *udp = data;
     if (size < sizeof *udp) {
+        atomic_count_inc(&hdr_size_err_udp);
         return false;
     }
 
     size_t udp_len = ntohs(udp->udp_len);
     if (OVS_UNLIKELY(udp_len < UDP_HEADER_LEN || udp_len > size)) {
+        atomic_count_inc(&size_err_udp);
         return false;
     }
 
     /* Validation must be skipped if checksum is 0 on IPv4 packets */
-    return (udp->udp_csum == 0 && key->dl_type == htons(ETH_TYPE_IP))
-           || (validate_checksum ? checksum_valid(key, data, size, l3) : true);
+    if (!(udp->udp_csum == 0 && key->dl_type == htons(ETH_TYPE_IP)) &&
+        (validate_checksum && !checksum_valid(key, data, size, l3))) {
+        atomic_count_inc(&cksum_err_udp);
+        return false;
+    }
+    return true;
 }
 
 static inline bool
 check_l4_icmp(const void *data, size_t size, bool validate_checksum)
 {
-    return validate_checksum ? csum(data, size) == 0 : true;
+    if (validate_checksum && csum(data, size) != 0) {
+        atomic_count_inc(&cksum_err_icmp);
+        return false;
+    }
+    return true;
 }
 
 static inline bool
 check_l4_icmp6(const struct conn_key *key, const void *data, size_t size,
                const void *l3, bool validate_checksum)
 {
-    return validate_checksum ? checksum_valid(key, data, size, l3) : true;
+    if (validate_checksum && !checksum_valid(key, data, size, l3)) {
+        atomic_count_inc(&cksum_err_icmp6);
+        return false;
+    }
+    return true;
 }
 
 static inline bool
@@ -1950,6 +2015,7 @@  conn_key_extract(struct conntrack *ct, struct dp_packet *pkt, ovs_be16 dl_type,
         bool hwol_bad_l3_csum = dp_packet_ip_checksum_bad(pkt);
         if (hwol_bad_l3_csum) {
             ok = false;
+            atomic_count_inc(&cksum_err_v4);
         } else {
             bool hwol_good_l3_csum = dp_packet_ip_checksum_valid(pkt);
             /* Validate the checksum only when hwol is not supported. */
@@ -2598,6 +2664,44 @@  conntrack_get_nconns(struct conntrack *ct, uint32_t *nconns)
     return 0;
 }
 
+/* Reports stats for invalid marked packets due to standard packet
+ * sanity failures. */
+int
+conntrack_get_invl_stats(unsigned int *ct_min_hdr_err_v4,
+                         unsigned int *ct_size_err_v4,
+                         unsigned int *ct_cksum_err_v4,
+                         unsigned int *ct_fragment_v4,
+                         unsigned int *ct_min_hdr_err_v6,
+                         unsigned int *ct_hdr_parse_err_v6,
+                         unsigned int *ct_fragment_v6,
+                         unsigned int *ct_hdr_size_err_tcp,
+                         unsigned int *ct_size_err_tcp,
+                         unsigned int *ct_cksum_err_tcp,
+                         unsigned int *ct_hdr_size_err_udp,
+                         unsigned int *ct_size_err_udp,
+                         unsigned int *ct_cksum_err_udp,
+                         unsigned int *ct_cksum_err_icmp,
+                         unsigned int *ct_cksum_err_icmp6)
+{
+    *ct_min_hdr_err_v4 = atomic_count_get(&min_hdr_err_v4);
+    *ct_size_err_v4 = atomic_count_get(&size_err_v4);
+    *ct_cksum_err_v4 = atomic_count_get(&cksum_err_v4);
+    *ct_fragment_v4 = atomic_count_get(&fragment_v4);
+    *ct_min_hdr_err_v6 = atomic_count_get(&min_hdr_err_v6);
+    *ct_hdr_parse_err_v6 = atomic_count_get(&hdr_parse_err_v6);
+    *ct_fragment_v6 = atomic_count_get(&fragment_v6);
+    *ct_hdr_size_err_tcp = atomic_count_get(&hdr_size_err_tcp);
+    *ct_size_err_tcp = atomic_count_get(&size_err_tcp);
+    *ct_cksum_err_tcp = atomic_count_get(&cksum_err_tcp);
+    *ct_hdr_size_err_udp = atomic_count_get(&hdr_size_err_udp);
+    *ct_size_err_udp = atomic_count_get(&size_err_udp);
+    *ct_cksum_err_udp = atomic_count_get(&cksum_err_udp);
+    *ct_cksum_err_icmp = atomic_count_get(&cksum_err_icmp);
+    *ct_cksum_err_icmp6 = atomic_count_get(&cksum_err_icmp6);
+
+    return 0;
+}
+
 /* 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 e3a5dcc..0fa9a7e 100644
--- a/lib/conntrack.h
+++ b/lib/conntrack.h
@@ -122,6 +122,21 @@  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_get_invl_stats(unsigned int *ct_min_hdr_err_v4,
+                             unsigned int *ct_size_err_v4,
+                             unsigned int *ct_cksum_err_v4,
+                             unsigned int *fragment_v4,
+                             unsigned int *ct_min_hdr_err_v6,
+                             unsigned int *ct_hdr_parse_err_v6,
+                             unsigned int *fragment_v6,
+                             unsigned int *ct_hdr_size_err_tcp,
+                             unsigned int *ct_size_err_tcp,
+                             unsigned int *ct_cksum_err_tcp,
+                             unsigned int *ct_hdr_size_err_udp,
+                             unsigned int *ct_size_err_udp,
+                             unsigned int *ct_cksum_err_udp,
+                             unsigned int *ct_cksum_err_icmp,
+                             unsigned int *ct_cksum_err_icmp6);
 
 /* 'struct ct_lock' is a wrapper for an adaptive mutex.  It's useful to try
  * different types of locks (e.g. spinlocks) */
diff --git a/lib/ct-dpif.c b/lib/ct-dpif.c
index 5fa3a97..e4de7de 100644
--- a/lib/ct-dpif.c
+++ b/lib/ct-dpif.c
@@ -164,6 +164,33 @@  ct_dpif_get_nconns(struct dpif *dpif, uint32_t *nconns)
             : EOPNOTSUPP);
 }
 
+int
+ct_dpif_get_invl_stats(struct dpif *dpif, unsigned int *min_hdr_err_v4,
+                       unsigned int *size_err_v4,
+                       unsigned int *cksum_err_v4,
+                       unsigned int *fragment_v4,
+                       unsigned int *min_hdr_err_v6,
+                       unsigned int *hdr_parse_err_v6,
+                       unsigned int *fragment_v6,
+                       unsigned int *hdr_size_err_tcp,
+                       unsigned int *size_err_tcp,
+                       unsigned int *cksum_err_tcp,
+                       unsigned int *hdr_size_err_udp,
+                       unsigned int *size_err_udp,
+                       unsigned int *cksum_err_udp,
+                       unsigned int *cksum_err_icmp,
+                       unsigned int *cksum_err_icmp6)
+{
+    return (dpif->dpif_class->ct_get_invl_stats
+            ? dpif->dpif_class->ct_get_invl_stats(dpif, min_hdr_err_v4,
+                  size_err_v4, cksum_err_v4, fragment_v4, min_hdr_err_v6,
+                  hdr_parse_err_v6, fragment_v6, hdr_size_err_tcp,
+                  size_err_tcp, cksum_err_tcp, hdr_size_err_udp,
+                  size_err_udp, cksum_err_udp, cksum_err_icmp,
+                  cksum_err_icmp6)
+            : EOPNOTSUPP);
+}
+
 void
 ct_dpif_entry_uninit(struct ct_dpif_entry *entry)
 {
diff --git a/lib/ct-dpif.h b/lib/ct-dpif.h
index 09e7698..13beb0b 100644
--- a/lib/ct-dpif.h
+++ b/lib/ct-dpif.h
@@ -200,6 +200,21 @@  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_get_invl_stats(struct dpif *dpif, unsigned int *min_hdr_err_v4,
+                           unsigned int *size_err_v4,
+                           unsigned int *cksum_err_v4,
+                           unsigned int *fragment_v4,
+                           unsigned int *min_hdr_err_v6,
+                           unsigned int *hdr_parse_err_v6,
+                           unsigned int *fragment_v6,
+                           unsigned int *hdr_size_err_tcp,
+                           unsigned int *size_err_tcp,
+                           unsigned int *cksum_err_tcp,
+                           unsigned int *hdr_size_err_udp,
+                           unsigned int *size_err_udp,
+                           unsigned int *cksum_err_udp,
+                           unsigned int *cksum_err_icmp,
+                           unsigned int *cksum_err_icmp6);
 void ct_dpif_entry_uninit(struct ct_dpif_entry *);
 void ct_dpif_format_entry(const struct ct_dpif_entry *, struct ds *,
                           bool verbose, bool print_stats);
diff --git a/lib/dpctl.c b/lib/dpctl.c
index 4f1e443..45f82fd 100644
--- a/lib/dpctl.c
+++ b/lib/dpctl.c
@@ -1381,6 +1381,71 @@  error:
     return error;
 }
 
+/* Prints nothing for unsupported datapath types. */
+static void
+ct_print_invalid_stats(struct dpctl_params *dpctl_p, struct dpif *dpif)
+{
+    if (dpif) {
+        unsigned int min_hdr_err_v4;
+        unsigned int size_err_v4;
+        unsigned int cksum_err_v4;
+        unsigned int fragment_v4;
+        unsigned int min_hdr_err_v6;
+        unsigned int hdr_parse_err_v6;
+        unsigned int fragment_v6;
+        unsigned int hdr_size_err_tcp;
+        unsigned int size_err_tcp;
+        unsigned int cksum_err_tcp;
+        unsigned int hdr_size_err_udp;
+        unsigned int size_err_udp;
+        unsigned int cksum_err_udp;
+        unsigned int cksum_err_icmp;
+        unsigned int cksum_err_icmp6;
+        int error = ct_dpif_get_invl_stats(dpif, &min_hdr_err_v4, &size_err_v4,
+                                           &cksum_err_v4, &fragment_v4,
+                                           &min_hdr_err_v6, &hdr_parse_err_v6,
+                                           &fragment_v6, &hdr_size_err_tcp,
+                                           &size_err_tcp, &cksum_err_tcp,
+                                           &hdr_size_err_udp, &size_err_udp,
+                                           &cksum_err_udp, &cksum_err_icmp,
+                                           &cksum_err_icmp6);
+
+        if (!error) {
+            dpctl_print(dpctl_p, "\nConnTracker Invalid Sanity Stats:\n");
+            dpctl_print(dpctl_p, "v4 minimum header errors: %u\n",
+                        min_hdr_err_v4);
+            dpctl_print(dpctl_p, "v4 packet size errors: %u\n",
+                        size_err_v4);
+            dpctl_print(dpctl_p, "v4 checksum errors: %u\n",
+                        cksum_err_v4);
+            dpctl_print(dpctl_p, "v4 fragments: %u\n",
+                                    fragment_v4);
+            dpctl_print(dpctl_p, "v6 minimum header errors: %u\n",
+                        min_hdr_err_v6);
+            dpctl_print(dpctl_p, "v6 parse errors: %u\n",
+                        hdr_parse_err_v6);
+            dpctl_print(dpctl_p, "v6 fragments: %u\n",
+                                    fragment_v6);
+            dpctl_print(dpctl_p, "tcp header size errors: %u\n",
+                        cksum_err_tcp);
+            dpctl_print(dpctl_p, "tcp size errors: %u\n",
+                        cksum_err_tcp);
+            dpctl_print(dpctl_p, "tcp checksum errors: %u\n",
+                        cksum_err_tcp);
+            dpctl_print(dpctl_p, "udp header size errors: %u\n",
+                        cksum_err_tcp);
+            dpctl_print(dpctl_p, "udp size errors: %u\n",
+                        cksum_err_tcp);
+            dpctl_print(dpctl_p, "udp checksum errors: %u\n",
+                        cksum_err_udp);
+            dpctl_print(dpctl_p, "icmp checksum errors: %u\n",
+                        cksum_err_icmp);
+            dpctl_print(dpctl_p, "icmp6 checksum errors: %u\n",
+                        cksum_err_icmp6);
+        }
+    }
+}
+
 static int
 dpctl_ct_stats_show(int argc, const char *argv[],
                      struct dpctl_params *dpctl_p)
@@ -1514,6 +1579,7 @@  dpctl_ct_stats_show(int argc, const char *argv[],
     }
 
     ct_dpif_dump_done(dump);
+    ct_print_invalid_stats(dpctl_p, dpif);
     dpif_close(dpif);
     return error;
 }
diff --git a/lib/dpctl.man b/lib/dpctl.man
index 5d987e6..4f62397 100644
--- a/lib/dpctl.man
+++ b/lib/dpctl.man
@@ -244,7 +244,8 @@  An example of an IPv6 TCP \fIct-tuple\fR:
 Displays the number of connections grouped by protocol used by \fIdp\fR.
 If \fBzone=\fIzone\fR is specified, numbers refer to the connections in
 \fIzone\fR.  With \fB\-\-more\fR, groups by connection state for each
-protocol.
+protocol.  Prints global invalid packet stats for the userspace connection
+tracker; typically, a rule is in place to drop invalid flagged packets.
 .
 .TP
 \*(DX\fBct\-bkts\fR [\fIdp\fR] [\fBgt=\fIthreshold\fR]
diff --git a/lib/dpif-netdev.c b/lib/dpif-netdev.c
index 9390fff..f19d32b 100644
--- a/lib/dpif-netdev.c
+++ b/lib/dpif-netdev.c
@@ -6044,6 +6044,34 @@  dpif_netdev_ct_get_nconns(struct dpif *dpif, uint32_t *nconns)
     return conntrack_get_nconns(&dp->conntrack, nconns);
 }
 
+static int
+dpif_netdev_ct_get_invl_stats(struct dpif *dpif OVS_UNUSED,
+                              unsigned int *ct_min_hdr_err_v4,
+                              unsigned int *ct_size_err_v4,
+                              unsigned int *ct_cksum_err_v4,
+                              unsigned int *ct_fragment_v4,
+                              unsigned int *ct_min_hdr_err_v6,
+                              unsigned int *ct_hdr_parse_err_v6,
+                              unsigned int *ct_fragment_v6,
+                              unsigned int *ct_hdr_size_err_tcp,
+                              unsigned int *ct_size_err_tcp,
+                              unsigned int *ct_cksum_err_tcp,
+                              unsigned int *ct_hdr_size_err_udp,
+                              unsigned int *ct_size_err_udp,
+                              unsigned int *ct_cksum_err_udp,
+                              unsigned int *ct_cksum_err_icmp,
+                              unsigned int *ct_cksum_err_icmp6)
+{
+    return conntrack_get_invl_stats(ct_min_hdr_err_v4, ct_size_err_v4,
+                                    ct_cksum_err_v4, ct_fragment_v4,
+                                    ct_min_hdr_err_v6, ct_hdr_parse_err_v6,
+                                    ct_fragment_v6, ct_hdr_size_err_tcp,
+                                    ct_size_err_tcp, ct_cksum_err_tcp,
+                                    ct_hdr_size_err_udp, ct_size_err_udp,
+                                    ct_cksum_err_udp, ct_cksum_err_icmp,
+                                    ct_cksum_err_icmp6);
+}
+
 const struct dpif_class dpif_netdev_class = {
     "netdev",
     dpif_netdev_init,
@@ -6092,6 +6120,7 @@  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_get_invl_stats,
     dpif_netdev_meter_get_features,
     dpif_netdev_meter_set,
     dpif_netdev_meter_get,
diff --git a/lib/dpif-netlink.c b/lib/dpif-netlink.c
index aa9bbd9..6d07d15 100644
--- a/lib/dpif-netlink.c
+++ b/lib/dpif-netlink.c
@@ -3006,6 +3006,7 @@  const struct dpif_class dpif_netlink_class = {
     NULL,                       /* ct_set_maxconns */
     NULL,                       /* ct_get_maxconns */
     NULL,                       /* ct_get_nconns */
+    NULL,                       /* ct_get_invl_stats */
     dpif_netlink_meter_get_features,
     dpif_netlink_meter_set,
     dpif_netlink_meter_get,
diff --git a/lib/dpif-provider.h b/lib/dpif-provider.h
index 62b3598..1692ac7 100644
--- a/lib/dpif-provider.h
+++ b/lib/dpif-provider.h
@@ -443,6 +443,12 @@  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);
+    int (*ct_get_invl_stats)(struct dpif *, unsigned int *,
+         unsigned int *, unsigned int *, unsigned int *,
+         unsigned int *, unsigned int *, unsigned int *,
+         unsigned int *, unsigned int *, unsigned int *,
+         unsigned int *, unsigned int *, unsigned int *,
+         unsigned int *, unsigned int *);
 
     /* Meters */