*/
        SKB_DROP_REASON_TCP_MD5FAILURE,
        /**
-        * @SKB_DROP_REASON_TCP_AONOTFOUND: no TCP-AO hash and one was expected
+        * @SKB_DROP_REASON_TCP_AONOTFOUND: no TCP-AO hash and one was expected,
+        * corresponding to LINUX_MIB_TCPAOREQUIRED
         */
        SKB_DROP_REASON_TCP_AONOTFOUND,
        /**
         * @SKB_DROP_REASON_TCP_AOUNEXPECTED: TCP-AO hash is present and it
-        * was not expected.
+        * was not expected, corresponding to LINUX_MIB_TCPAOKEYNOTFOUND
         */
        SKB_DROP_REASON_TCP_AOUNEXPECTED,
-       /** @SKB_DROP_REASON_TCP_AOKEYNOTFOUND: TCP-AO key is unknown */
+       /**
+        * @SKB_DROP_REASON_TCP_AOKEYNOTFOUND: TCP-AO key is unknown,
+        * corresponding to LINUX_MIB_TCPAOKEYNOTFOUND
+        */
        SKB_DROP_REASON_TCP_AOKEYNOTFOUND,
-       /** @SKB_DROP_REASON_TCP_AOFAILURE: TCP-AO hash is wrong */
+       /**
+        * @SKB_DROP_REASON_TCP_AOFAILURE: TCP-AO hash is wrong,
+        * corresponding to LINUX_MIB_TCPAOBAD
+        */
        SKB_DROP_REASON_TCP_AOFAILURE,
        /**
         * @SKB_DROP_REASON_SOCKET_BACKLOG: failed to add skb to socket backlog (
 
 }
 
 static inline bool tcp_ao_required(struct sock *sk, const void *saddr,
-                                  int family)
+                                  int family, bool stat_inc)
 {
 #ifdef CONFIG_TCP_AO
        struct tcp_ao_info *ao_info;
                return false;
 
        ao_key = tcp_ao_do_lookup(sk, saddr, family, -1, -1);
-       if (ao_info->ao_required || ao_key)
+       if (ao_info->ao_required || ao_key) {
+               if (stat_inc) {
+                       NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAOREQUIRED);
+                       atomic64_inc(&ao_info->counters.ao_required);
+               }
                return true;
+       }
 #endif
        return false;
 }
                return SKB_DROP_REASON_TCP_AUTH_HDR;
 
        if (req) {
-               if (tcp_rsk_used_ao(req) != !!aoh)
+               if (tcp_rsk_used_ao(req) != !!aoh) {
+                       NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAOBAD);
                        return SKB_DROP_REASON_TCP_AOFAILURE;
+               }
        }
 
        /* sdif set, means packet ingressed via a device
                 * the last key is impossible to remove, so there's
                 * always at least one current_key.
                 */
-               if (tcp_ao_required(sk, saddr, family))
+               if (tcp_ao_required(sk, saddr, family, true))
                        return SKB_DROP_REASON_TCP_AONOTFOUND;
                if (unlikely(tcp_md5_do_lookup(sk, l3index, saddr, family))) {
                        NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPMD5NOTFOUND);
 
        u8      rnext_keyid;
 };
 
+struct tcp_ao_counters {
+       atomic64_t      pkt_good;
+       atomic64_t      pkt_bad;
+       atomic64_t      key_not_found;
+       atomic64_t      ao_required;
+};
+
 struct tcp_ao_key {
        struct hlist_node       node;
        union tcp_ao_addr       addr;
        u8                      rcvid;
        u8                      maclen;
        struct rcu_head         rcu;
+       atomic64_t              pkt_good;
+       atomic64_t              pkt_bad;
        u8                      traffic_keys[];
 };
 
         */
        struct tcp_ao_key       *current_key;
        struct tcp_ao_key       *rnext_key;
+       struct tcp_ao_counters  counters;
        u32                     ao_required     :1,
                                __unused        :31;
        __be32                  lisn;
 
        LINUX_MIB_TCPMIGRATEREQSUCCESS,         /* TCPMigrateReqSuccess */
        LINUX_MIB_TCPMIGRATEREQFAILURE,         /* TCPMigrateReqFailure */
        LINUX_MIB_TCPPLBREHASH,                 /* TCPPLBRehash */
+       LINUX_MIB_TCPAOREQUIRED,                /* TCPAORequired */
+       LINUX_MIB_TCPAOBAD,                     /* TCPAOBad */
+       LINUX_MIB_TCPAOKEYNOTFOUND,             /* TCPAOKeyNotFound */
+       LINUX_MIB_TCPAOGOOD,                    /* TCPAOGood */
        __LINUX_MIB_MAX
 };
 
 
        __u32   set_current     :1,     /* corresponding ::current_key */
                set_rnext       :1,     /* corresponding ::rnext */
                ao_required     :1,     /* don't accept non-AO connects */
-               reserved        :29;    /* must be 0 */
+               set_counters    :1,     /* set/clear ::pkt_* counters */
+               reserved        :28;    /* must be 0 */
+       __u16   reserved2;              /* padding, must be 0 */
        __u8    current_key;            /* KeyID to set as Current_key */
        __u8    rnext;                  /* KeyID to set as Rnext_key */
+       __u64   pkt_good;               /* verified segments */
+       __u64   pkt_bad;                /* failed verification */
+       __u64   pkt_key_not_found;      /* could not find a key to verify */
+       __u64   pkt_ao_required;        /* segments missing TCP-AO sign */
 } __attribute__((aligned(8)));
 
 /* setsockopt(fd, IPPROTO_TCP, TCP_ZEROCOPY_RECEIVE, ...) */
 
        SNMP_MIB_ITEM("TCPMigrateReqSuccess", LINUX_MIB_TCPMIGRATEREQSUCCESS),
        SNMP_MIB_ITEM("TCPMigrateReqFailure", LINUX_MIB_TCPMIGRATEREQFAILURE),
        SNMP_MIB_ITEM("TCPPLBRehash", LINUX_MIB_TCPPLBREHASH),
+       SNMP_MIB_ITEM("TCPAORequired", LINUX_MIB_TCPAOREQUIRED),
+       SNMP_MIB_ITEM("TCPAOBad", LINUX_MIB_TCPAOBAD),
+       SNMP_MIB_ITEM("TCPAOKeyNotFound", LINUX_MIB_TCPAOKEYNOTFOUND),
+       SNMP_MIB_ITEM("TCPAOGood", LINUX_MIB_TCPAOGOOD),
        SNMP_MIB_SENTINEL
 };
 
 
        *new_key = *key;
        INIT_HLIST_NODE(&new_key->node);
        tcp_sigpool_get(new_key->tcp_sigpool_id);
+       atomic64_set(&new_key->pkt_good, 0);
+       atomic64_set(&new_key->pkt_bad, 0);
 
        return new_key;
 }
        const struct tcphdr *th = tcp_hdr(skb);
        void *hash_buf = NULL;
 
-       if (maclen != tcp_ao_maclen(key))
+       if (maclen != tcp_ao_maclen(key)) {
+               NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAOBAD);
+               atomic64_inc(&info->counters.pkt_bad);
+               atomic64_inc(&key->pkt_bad);
                return SKB_DROP_REASON_TCP_AOFAILURE;
+       }
 
        hash_buf = kmalloc(tcp_ao_digest_size(key), GFP_ATOMIC);
        if (!hash_buf)
        tcp_ao_hash_skb(family, hash_buf, key, sk, skb, traffic_key,
                        (phash - (u8 *)th), sne);
        if (memcmp(phash, hash_buf, maclen)) {
+               NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAOBAD);
+               atomic64_inc(&info->counters.pkt_bad);
+               atomic64_inc(&key->pkt_bad);
                kfree(hash_buf);
                return SKB_DROP_REASON_TCP_AOFAILURE;
        }
+       NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAOGOOD);
+       atomic64_inc(&info->counters.pkt_good);
+       atomic64_inc(&key->pkt_good);
        kfree(hash_buf);
        return SKB_NOT_DROPPED_YET;
 }
        u32 sne = 0;
 
        info = rcu_dereference(tcp_sk(sk)->ao_info);
-       if (!info)
+       if (!info) {
+               NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAOKEYNOTFOUND);
                return SKB_DROP_REASON_TCP_AOUNEXPECTED;
+       }
 
        if (unlikely(th->syn)) {
                sisn = th->seq;
        return ret;
 
 key_not_found:
+       NET_INC_STATS(sock_net(sk), LINUX_MIB_TCPAOKEYNOTFOUND);
+       atomic64_inc(&info->counters.key_not_found);
        return SKB_DROP_REASON_TCP_AOKEYNOTFOUND;
 }
 
        key->keyflags   = cmd.keyflags;
        key->sndid      = cmd.sndid;
        key->rcvid      = cmd.rcvid;
+       atomic64_set(&key->pkt_good, 0);
+       atomic64_set(&key->pkt_bad, 0);
 
        ret = tcp_ao_parse_crypto(&cmd, key);
        if (ret < 0)
                        return -EINVAL;
        }
 
-       if (cmd.reserved != 0)
+       if (cmd.reserved != 0 || cmd.reserved2 != 0)
                return -EINVAL;
 
        ao_info = setsockopt_ao_info(sk);
                        goto out;
                }
        }
+       if (cmd.set_counters) {
+               atomic64_set(&ao_info->counters.pkt_good, cmd.pkt_good);
+               atomic64_set(&ao_info->counters.pkt_bad, cmd.pkt_bad);
+               atomic64_set(&ao_info->counters.key_not_found, cmd.pkt_key_not_found);
+               atomic64_set(&ao_info->counters.ao_required, cmd.pkt_ao_required);
+       }
 
        ao_info->ao_required = cmd.ao_required;
        if (new_current)
 
        /* Don't allow keys for peers that have a matching TCP-AO key.
         * See the comment in tcp_ao_add_cmd()
         */
-       if (tcp_ao_required(sk, addr, AF_INET))
+       if (tcp_ao_required(sk, addr, AF_INET, false))
                return -EKEYREJECTED;
 
        return tcp_md5_do_add(sk, addr, AF_INET, prefixlen, l3index, flags,
 
                /* Don't allow keys for peers that have a matching TCP-AO key.
                 * See the comment in tcp_ao_add_cmd()
                 */
-               if (tcp_ao_required(sk, addr, AF_INET))
+               if (tcp_ao_required(sk, addr, AF_INET, false))
                        return -EKEYREJECTED;
                return tcp_md5_do_add(sk, addr,
                                      AF_INET, prefixlen, l3index, flags,
        /* Don't allow keys for peers that have a matching TCP-AO key.
         * See the comment in tcp_ao_add_cmd()
         */
-       if (tcp_ao_required(sk, addr, AF_INET6))
+       if (tcp_ao_required(sk, addr, AF_INET6, false))
                return -EKEYREJECTED;
 
        return tcp_md5_do_add(sk, addr, AF_INET6, prefixlen, l3index, flags,