Introduce new IPsec (per-CPU) statistics and refactor ESP input
authormpi <mpi@openbsd.org>
Tue, 10 Jul 2018 11:34:12 +0000 (11:34 +0000)
committermpi <mpi@openbsd.org>
Tue, 10 Jul 2018 11:34:12 +0000 (11:34 +0000)
callbacks to be able to count dropped packet.

Having more generic statistics will help troubleshooting problems
with specific tunnels.  Per-TDB counters are coming once all the
refactoring bits are in.

ok markus@

sys/netinet/in.h
sys/netinet/ip_esp.c
sys/netinet/ip_input.c
sys/netinet/ip_ipsp.h
sys/netinet/ipsec_input.c

index c864c56..7a83851 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: in.h,v 1.130 2018/06/07 08:46:24 bluhm Exp $  */
+/*     $OpenBSD: in.h,v 1.131 2018/07/10 11:34:12 mpi Exp $    */
 /*     $NetBSD: in.h,v 1.20 1996/02/13 23:41:47 christos Exp $ */
 
 /*
@@ -660,6 +660,7 @@ struct ip_mreq {
 #define IPCTL_IPPORT_HILASTAUTO        10
 #define        IPCTL_IPPORT_MAXQUEUE   11
 #define        IPCTL_ENCDEBUG          12
+#define IPCTL_IPSEC_STATS      13
 #define IPCTL_IPSEC_EXPIRE_ACQUIRE 14   /* How long to wait for key mgmt. */
 #define IPCTL_IPSEC_EMBRYONIC_SA_TIMEOUT       15 /* new SA lifetime */
 #define IPCTL_IPSEC_REQUIRE_PFS 16
@@ -703,7 +704,7 @@ struct ip_mreq {
        { "porthilast", CTLTYPE_INT }, \
        { "maxqueue", CTLTYPE_INT }, \
        { "encdebug", CTLTYPE_INT }, \
-       { 0, 0 }, \
+       { 0, 0 /* ipsecstat */ }, \
        { "ipsec-expire-acquire", CTLTYPE_INT }, \
        { "ipsec-invalid-life", CTLTYPE_INT }, \
        { "ipsec-pfs", CTLTYPE_INT }, \
@@ -746,7 +747,7 @@ struct ip_mreq {
        &ipport_hilastauto, \
        &ip_maxqueue, \
        NULL /* encdebug */, \
-       NULL, \
+       NULL /* ipsecstat */, \
        NULL /* ipsec_expire_acquire */, \
        NULL /* ipsec_keep_invalid */, \
        NULL /* ipsec_require_pfs */, \
index 31499cd..ac80fa4 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ip_esp.c,v 1.154 2018/05/09 16:00:28 bluhm Exp $ */
+/*     $OpenBSD: ip_esp.c,v 1.155 2018/07/10 11:34:12 mpi Exp $ */
 /*
  * The authors of this code are John Ioannidis (ji@tla.org),
  * Angelos D. Keromytis (kermit@csd.uch.gr) and
@@ -70,7 +70,6 @@
 #include "bpfilter.h"
 
 void esp_output_cb(struct cryptop *);
-void esp_input_cb(struct cryptop *);
 
 #ifdef ENCDEBUG
 #define DPRINTF(x)     if (encdebug) printf x
@@ -492,7 +491,7 @@ esp_input(struct mbuf *m, struct tdb *tdb, int skip, int protoff)
        crp->crp_ilen = m->m_pkthdr.len; /* Total input length */
        crp->crp_flags = CRYPTO_F_IMBUF;
        crp->crp_buf = (caddr_t)m;
-       crp->crp_callback = esp_input_cb;
+       crp->crp_callback = ipsec_input_cb;
        crp->crp_sid = tdb->tdb_cryptoid;
        crp->crp_opaque = (caddr_t)tc;
 
@@ -531,59 +530,26 @@ esp_input(struct mbuf *m, struct tdb *tdb, int skip, int protoff)
 /*
  * ESP input callback, called directly by the crypto driver.
  */
-void
-esp_input_cb(struct cryptop *crp)
+int
+esp_input_cb(struct tdb *tdb, struct tdb_crypto *tc, struct mbuf *m)
 {
        u_int8_t lastthree[3], aalg[AH_HMAC_MAX_HASHLEN];
        int hlen, roff, skip, protoff;
-       struct mbuf *m1, *mo, *m;
+       struct mbuf *m1, *mo;
        struct auth_hash *esph;
-       struct tdb_crypto *tc;
-       struct tdb *tdb;
        u_int32_t btsx, esn;
        caddr_t ptr;
 #ifdef ENCDEBUG
        char buf[INET6_ADDRSTRLEN];
 #endif
 
-       tc = (struct tdb_crypto *) crp->crp_opaque;
        skip = tc->tc_skip;
        protoff = tc->tc_protoff;
 
-       m = (struct mbuf *) crp->crp_buf;
-       if (m == NULL) {
-               /* Shouldn't happen... */
-               DPRINTF(("%s: bogus returned buffer from crypto\n", __func__));
-               espstat_inc(esps_crypto);
-               goto droponly;
-       }
-
-       NET_LOCK();
-
-       tdb = gettdb(tc->tc_rdomain, tc->tc_spi, &tc->tc_dst, tc->tc_proto);
-       if (tdb == NULL) {
-               DPRINTF(("%s: TDB is expired while in crypto", __func__));
-               espstat_inc(esps_notdb);
-               goto baddone;
-       }
+       NET_ASSERT_LOCKED();
 
        esph = (struct auth_hash *) tdb->tdb_authalgxform;
 
-       /* Check for crypto errors */
-       if (crp->crp_etype) {
-               if (crp->crp_etype == EAGAIN) {
-                       /* Reset the session ID */
-                       if (tdb->tdb_cryptoid != 0)
-                               tdb->tdb_cryptoid = crp->crp_sid;
-                       NET_UNLOCK();
-                       crypto_dispatch(crp);
-                       return;
-               }
-               DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype));
-               espstat_inc(esps_noxform);
-               goto baddone;
-       }
-
        /* If authentication was performed, check now. */
        if (esph != NULL) {
                /* Copy the authenticator from the packet */
@@ -749,20 +715,15 @@ esp_input_cb(struct cryptop *crp)
        m_copyback(m, protoff, sizeof(u_int8_t), lastthree + 2, M_NOWAIT);
 
        /* Release the crypto descriptors */
-       crypto_freereq(crp);
        free(tc, M_XDATA, 0);
 
        /* Back to generic IPsec input processing */
-       ipsec_common_input_cb(m, tdb, skip, protoff);
-       NET_UNLOCK();
-       return;
+       return ipsec_common_input_cb(m, tdb, skip, protoff);
 
  baddone:
-       NET_UNLOCK();
- droponly:
        m_freem(m);
-       crypto_freereq(crp);
        free(tc, M_XDATA, 0);
+       return -1;
 }
 
 /*
index d02d5be..4585b7f 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ip_input.c,v 1.337 2018/05/21 15:52:22 bluhm Exp $    */
+/*     $OpenBSD: ip_input.c,v 1.338 2018/07/10 11:34:12 mpi Exp $      */
 /*     $NetBSD: ip_input.c,v 1.30 1996/03/16 23:53:58 christos Exp $   */
 
 /*
@@ -1623,6 +1623,7 @@ ip_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp,
                return (error);
 #ifdef IPSEC
        case IPCTL_ENCDEBUG:
+       case IPCTL_IPSEC_STATS:
        case IPCTL_IPSEC_EXPIRE_ACQUIRE:
        case IPCTL_IPSEC_EMBRYONIC_SA_TIMEOUT:
        case IPCTL_IPSEC_REQUIRE_PFS:
index 1d7fb6c..a3944c1 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ip_ipsp.h,v 1.189 2017/11/20 14:14:26 mpi Exp $       */
+/*     $OpenBSD: ip_ipsp.h,v 1.190 2018/07/10 11:34:12 mpi Exp $       */
 /*
  * The authors of this code are John Ioannidis (ji@tla.org),
  * Angelos D. Keromytis (kermit@csd.uch.gr),
 #ifndef _NETINET_IPSP_H_
 #define _NETINET_IPSP_H_
 
-struct m_tag;
-
 /* IPSP global definitions. */
 
 #include <sys/types.h>
-#include <sys/queue.h>
 #include <netinet/in.h>
-#include <net/radix.h>
 
 union sockaddr_union {
        struct sockaddr         sa;
@@ -125,9 +121,58 @@ struct sockaddr_encap {
 #define        IPSP_DIRECTION_IN       0x1
 #define        IPSP_DIRECTION_OUT      0x2
 
+struct ipsecstat {
+       uint64_t        ipsec_ipackets;         /* Input IPsec packets */
+       uint64_t        ipsec_opackets;         /* Output IPsec packets */
+       uint64_t        ipsec_ibytes;           /* Input bytes */
+       uint64_t        ipsec_obytes;           /* Output bytes */
+       uint64_t        ipsec_idecompbytes;     /* Input bytes, decompressed */
+       uint64_t        ipsec_ouncompbytes;     /* Output bytes, uncompressed */
+       uint64_t        ipsec_idrops;           /* Dropped on input */
+       uint64_t        ipsec_odrops;           /* Dropped on output */
+       uint64_t        ipsec_crypto;           /* Crypto processing failure */
+       uint64_t        ipsec_notdb;            /* Expired while in crypto */
+       uint64_t        ipsec_noxform;          /* Crypto error */
+};
+
 #ifdef _KERNEL
+
 #include <sys/timeout.h>
 #include <sys/tree.h>
+#include <sys/queue.h>
+#include <net/radix.h>
+#include <sys/percpu.h>
+
+enum ipsec_counters {
+       ipsec_ipackets,
+       ipsec_opackets,
+       ipsec_ibytes,
+       ipsec_obytes,
+       ipsec_idecompbytes,
+       ipsec_ouncompbytes,
+       ipsec_idrops,
+       ipsec_odrops,
+       ipsec_crypto,
+       ipsec_notdb,
+       ipsec_noxform,
+       ipsec_ncounters
+};
+
+extern struct cpumem *ipseccounters;
+
+static inline void
+ipsecstat_inc(enum ipsec_counters c)
+{
+       counters_inc(ipseccounters, c);
+}
+
+static inline void
+ipsecstat_add(enum ipsec_counters c, uint64_t v)
+{
+       counters_add(ipseccounters, c, v);
+}
+
+struct m_tag;
 
 #define        sen_data                Sen.Data
 #define        sen_ip_src              Sen.Sip4.Src
@@ -424,6 +469,7 @@ extern int ipsec_exp_first_use;             /* seconds between 1st asso & expire */
  * Names for IPsec sysctl objects
  */
 #define        IPSEC_ENCDEBUG                  IPCTL_ENCDEBUG                  /* 12 */
+#define        IPSEC_STATS                     IPCTL_IPSEC_STATS               /* 13 */
 #define IPSEC_EXPIRE_ACQUIRE           IPCTL_IPSEC_EXPIRE_ACQUIRE      /* 14 */
 #define IPSEC_EMBRYONIC_SA_TIMEOUT     IPCTL_IPSEC_EMBRYONIC_SA_TIMEOUT/* 15 */
 #define IPSEC_REQUIRE_PFS              IPCTL_IPSEC_REQUIRE_PFS         /* 16 */
@@ -451,7 +497,7 @@ extern int ipsec_exp_first_use;             /* seconds between 1st asso & expire */
        NULL, \
        NULL, \
        &encdebug, \
-       NULL, \
+       NULL, /* ipsecstat */ \
        &ipsec_expire_acquire, \
        &ipsec_keep_invalid, \
        &ipsec_require_pfs, \
@@ -482,6 +528,8 @@ extern struct comp_algo comp_algo_deflate;
 
 extern TAILQ_HEAD(ipsec_policy_head, ipsec_policy) ipsec_policy_head;
 
+struct cryptop;
+
 /* Misc. */
 #ifdef ENCDEBUG
 const char *ipsp_address(union sockaddr_union *, char *, socklen_t);
@@ -540,6 +588,7 @@ int esp_attach(void);
 int    esp_init(struct tdb *, struct xformsw *, struct ipsecinit *);
 int    esp_zeroize(struct tdb *);
 int    esp_input(struct mbuf *, struct tdb *, int, int);
+int    esp_input_cb(struct tdb *, struct tdb_crypto *, struct mbuf *);
 int    esp_output(struct mbuf *, struct tdb *, struct mbuf **, int, int);
 int    esp_sysctl(int *, u_int, void *, size_t *, void *, size_t);
 
@@ -592,7 +641,8 @@ void        ipsp_ids_free(struct ipsec_ids *);
 void   ipsec_init(void);
 int    ipsec_sysctl(int *, u_int, void *, size_t *, void *, size_t);
 int    ipsec_common_input(struct mbuf *, int, int, int, int, int);
-void   ipsec_common_input_cb(struct mbuf *, struct tdb *, int, int);
+void   ipsec_input_cb(struct cryptop *);
+int    ipsec_common_input_cb(struct mbuf *, struct tdb *, int, int);
 int    ipsec_delete_policy(struct ipsec_policy *);
 ssize_t        ipsec_hdrsz(struct tdb *);
 void   ipsec_adjust_mtu(struct mbuf *, u_int32_t);
index 112a553..50bba53 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: ipsec_input.c,v 1.163 2018/05/14 15:24:23 bluhm Exp $ */
+/*     $OpenBSD: ipsec_input.c,v 1.164 2018/07/10 11:34:12 mpi Exp $   */
 /*
  * The authors of this code are John Ioannidis (ji@tla.org),
  * Angelos D. Keromytis (kermit@csd.uch.gr) and
@@ -77,6 +77,9 @@
 
 #include <net/if_enc.h>
 
+#include <crypto/cryptodev.h>
+#include <crypto/xform.h>
+
 #include "bpfilter.h"
 
 void ipsec_common_ctlinput(u_int, int, struct sockaddr *, void *, int);
@@ -112,6 +115,7 @@ int *ipcompctl_vars[IPCOMPCTL_MAXID] = IPCOMPCTL_VARS;
 struct cpumem *espcounters;
 struct cpumem *ahcounters;
 struct cpumem *ipcompcounters;
+struct cpumem *ipseccounters;
 
 char ipsec_def_enc[20];
 char ipsec_def_auth[20];
@@ -122,6 +126,7 @@ int *ipsecctl_vars[IPSEC_MAXID] = IPSECCTL_VARS;
 int esp_sysctl_espstat(void *, size_t *, void *);
 int ah_sysctl_ahstat(void *, size_t *, void *);
 int ipcomp_sysctl_ipcompstat(void *, size_t *, void *);
+int ipsec_sysctl_ipsecstat(void *, size_t *, void *);
 
 void
 ipsec_init(void)
@@ -129,6 +134,7 @@ ipsec_init(void)
        espcounters = counters_alloc(esps_ncounters);
        ahcounters = counters_alloc(ahs_ncounters);
        ipcompcounters = counters_alloc(ipcomps_ncounters);
+       ipseccounters = counters_alloc(ipsec_ncounters);
 
        strlcpy(ipsec_def_enc, IPSEC_DEFAULT_DEF_ENC, sizeof(ipsec_def_enc));
        strlcpy(ipsec_def_auth, IPSEC_DEFAULT_DEF_AUTH, sizeof(ipsec_def_auth));
@@ -167,6 +173,8 @@ ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto,
 
        NET_ASSERT_LOCKED();
 
+       ipsecstat_inc(ipsec_ipackets);
+       ipsecstat_add(ipsec_ibytes, m->m_pkthdr.len);
        IPSEC_ISTAT(esps_input, ahs_input, ipcomps_input);
 
        if (m == NULL) {
@@ -322,6 +330,8 @@ ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto,
         * everything else.
         */
        error = (*(tdbp->tdb_xform->xf_input))(m, tdbp, skip, protoff);
+       if (error)
+               ipsecstat_inc(ipsec_idrops);
        return error;
 
  drop:
@@ -329,11 +339,79 @@ ipsec_common_input(struct mbuf *m, int skip, int protoff, int af, int sproto,
        return error;
 }
 
+void
+ipsec_input_cb(struct cryptop *crp)
+{
+       struct tdb_crypto *tc = (struct tdb_crypto *) crp->crp_opaque;
+       struct mbuf *m = (struct mbuf *) crp->crp_buf;
+       struct tdb *tdb;
+       int error;
+
+       if (m == NULL) {
+               DPRINTF(("%s: bogus returned buffer from crypto\n", __func__));
+               ipsecstat_inc(ipsec_crypto);
+               goto droponly;
+       }
+
+
+       NET_LOCK();
+       tdb = gettdb(tc->tc_rdomain, tc->tc_spi, &tc->tc_dst, tc->tc_proto);
+       if (tdb == NULL) {
+               DPRINTF(("%s: TDB is expired while in crypto", __func__));
+               ipsecstat_inc(ipsec_notdb);
+               goto baddone;
+       }
+
+       /* Check for crypto errors */
+       if (crp->crp_etype) {
+               if (crp->crp_etype == EAGAIN) {
+                       /* Reset the session ID */
+                       if (tdb->tdb_cryptoid != 0)
+                               tdb->tdb_cryptoid = crp->crp_sid;
+                       NET_UNLOCK();
+                       crypto_dispatch(crp);
+                       return;
+               }
+               DPRINTF(("%s: crypto error %d\n", __func__, crp->crp_etype));
+               ipsecstat_inc(ipsec_noxform);
+               goto baddone;
+       }
+
+       /* Release the crypto descriptors */
+       crypto_freereq(crp);
+
+       switch (tdb->tdb_sproto) {
+       case IPPROTO_ESP:
+               error = esp_input_cb(tdb, tc, m);
+               break;
+       case IPPROTO_AH:
+               break;
+       case IPPROTO_IPCOMP:
+               break;
+       default:
+               panic("%s: unknown/unsupported security protocol %d",
+                   __func__, tdb->tdb_sproto);
+       }
+
+       NET_UNLOCK();
+       if (error)
+               ipsecstat_inc(ipsec_idrops);
+       return;
+
+ baddone:
+       NET_UNLOCK();
+ droponly:
+       ipsecstat_inc(ipsec_idrops);
+       free(tc, M_XDATA, 0);
+       m_freem(m);
+       crypto_freereq(crp);
+}
+
 /*
  * IPsec input callback, called by the transform callback. Takes care of
  * filtering and other sanity checks on the processed packet.
  */
-void
+int
 ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
 {
        int af, sproto;
@@ -364,7 +442,7 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
        if (m == NULL) {
                /* The called routine will print a message if necessary */
                IPSEC_ISTAT(esps_badkcr, ahs_badkcr, ipcomps_badkcr);
-               return;
+               return -1;
        }
 
        /* Fix IPv4 header */
@@ -374,7 +452,7 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
                            __func__, ipsp_address(&tdbp->tdb_dst,
                            buf, sizeof(buf)), ntohl(tdbp->tdb_spi)));
                        IPSEC_ISTAT(esps_hdrops, ahs_hdrops, ipcomps_hdrops);
-                       return;
+                       return -1;
                }
 
                ip = mtod(m, struct ip *);
@@ -389,7 +467,7 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
                                m_freem(m);
                                IPSEC_ISTAT(esps_hdrops, ahs_hdrops,
                                    ipcomps_hdrops);
-                               return;
+                               return -1;
                        }
                        /* ipn will now contain the inner IPv4 header */
                        m_copydata(m, skip, sizeof(struct ip),
@@ -403,7 +481,7 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
                                m_freem(m);
                                IPSEC_ISTAT(esps_hdrops, ahs_hdrops,
                                    ipcomps_hdrops);
-                               return;
+                               return -1;
                        }
                        /* ip6n will now contain the inner IPv6 header. */
                        m_copydata(m, skip, sizeof(struct ip6_hdr),
@@ -424,7 +502,7 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
                            buf, sizeof(buf)), ntohl(tdbp->tdb_spi)));
 
                        IPSEC_ISTAT(esps_hdrops, ahs_hdrops, ipcomps_hdrops);
-                       return;
+                       return -1;
                }
 
                ip6 = mtod(m, struct ip6_hdr *);
@@ -439,7 +517,7 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
                                m_freem(m);
                                IPSEC_ISTAT(esps_hdrops, ahs_hdrops,
                                    ipcomps_hdrops);
-                               return;
+                               return -1;
                        }
                        /* ipn will now contain the inner IPv4 header */
                        m_copydata(m, skip, sizeof(struct ip), (caddr_t) &ipn);
@@ -451,7 +529,7 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
                                m_freem(m);
                                IPSEC_ISTAT(esps_hdrops, ahs_hdrops,
                                    ipcomps_hdrops);
-                               return;
+                               return -1;
                        }
                        /* ip6n will now contain the inner IPv6 header. */
                        m_copydata(m, skip, sizeof(struct ip6_hdr),
@@ -475,7 +553,7 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
                                m_freem(m);
                                IPSEC_ISTAT(esps_hdrops, ahs_hdrops,
                                    ipcomps_hdrops);
-                               return;
+                               return -1;
                        }
                        cksum = 0;
                        m_copyback(m, skip + offsetof(struct udphdr, uh_sum),
@@ -494,7 +572,7 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
                                m_freem(m);
                                IPSEC_ISTAT(esps_hdrops, ahs_hdrops,
                                    ipcomps_hdrops);
-                               return;
+                               return -1;
                        }
                        cksum = 0;
                        m_copyback(m, skip + offsetof(struct tcphdr, th_sum),
@@ -524,7 +602,7 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
                        m_freem(m);
                        DPRINTF(("%s: failed to get tag\n", __func__));
                        IPSEC_ISTAT(esps_hdrops, ahs_hdrops, ipcomps_hdrops);
-                       return;
+                       return -1;
                }
 
                tdbi = (struct tdb_ident *)(mtag + 1);
@@ -566,6 +644,8 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
        if (tdbp->tdb_flags & TDBF_TUNNELING)
                m->m_flags |= M_TUNNEL;
 
+       ipsecstat_add(ipsec_idecompbytes, m->m_pkthdr.len);
+
 #if NBPFILTER > 0
        if ((encif = enc_getif(tdbp->tdb_rdomain, tdbp->tdb_tap)) != NULL) {
                encif->if_ipackets++;
@@ -597,20 +677,21 @@ ipsec_common_input_cb(struct mbuf *m, struct tdb *tdbp, int skip, int protoff)
                /* This is the enc0 interface unless for ipcomp. */
                if ((ifp = if_get(m->m_pkthdr.ph_ifidx)) == NULL) {
                        m_freem(m);
-                       return;
+                       return -1;
                }
                if (pf_test(af, PF_IN, ifp, &m) != PF_PASS) {
                        if_put(ifp);
                        m_freem(m);
-                       return;
+                       return -1;
                }
                if_put(ifp);
                if (m == NULL)
-                       return;
+                       return -1;
        }
 #endif
        /* Call the appropriate IPsec transform callback. */
        ip_deliver(&m, &skip, prot, af);
+       return 0;
 #undef IPSEC_ISTAT
 }
 
@@ -639,6 +720,8 @@ ipsec_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp, void *newp,
                    ipsec_def_comp, sizeof(ipsec_def_comp));
                NET_UNLOCK();
                return (error);
+       case IPCTL_IPSEC_STATS:
+               return (ipsec_sysctl_ipsecstat(oldp, oldlenp, newp));
        default:
                if (name[0] < IPSEC_MAXID) {
                        NET_LOCK();
@@ -762,6 +845,18 @@ ipcomp_sysctl_ipcompstat(void *oldp, size_t *oldlenp, void *newp)
            sizeof(ipcompstat)));
 }
 
+int
+ipsec_sysctl_ipsecstat(void *oldp, size_t *oldlenp, void *newp)
+{
+       struct ipsecstat ipsecstat;
+
+       CTASSERT(sizeof(ipsecstat) == (ipsec_ncounters * sizeof(uint64_t)));
+       memset(&ipsecstat, 0, sizeof ipsecstat);
+       counters_read(ipseccounters, (uint64_t *)&ipsecstat, ipsec_ncounters);
+       return (sysctl_rdstruct(oldp, oldlenp, newp, &ipsecstat,
+           sizeof(ipsecstat)));
+}
+
 /* IPv4 AH wrapper. */
 int
 ah4_input(struct mbuf **mp, int *offp, int proto, int af)