TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test_progs \
        test_align test_verifier_log test_dev_cgroup test_tcpbpf_user \
        test_sock test_btf test_sockmap test_lirc_mode2_user get_cgroup_id_user \
-       test_socket_cookie test_cgroup_storage test_select_reuseport test_section_names
+       test_socket_cookie test_cgroup_storage test_select_reuseport test_section_names \
+       test_netcnt
 
 TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test_obj_id.o \
        test_pkt_md_access.o test_xdp_redirect.o test_xdp_meta.o sockmap_parse_prog.o     \
        test_get_stack_rawtp.o test_sockmap_kern.o test_sockhash_kern.o \
        test_lwt_seg6local.o sendmsg4_prog.o sendmsg6_prog.o test_lirc_mode2_kern.o \
        get_cgroup_id_kern.o socket_cookie_prog.o test_select_reuseport_kern.o \
-       test_skb_cgroup_id_kern.o bpf_flow.o
+       test_skb_cgroup_id_kern.o bpf_flow.o netcnt_prog.o
 
 # Order correspond to 'make run_tests' order
 TEST_PROGS := test_kmod.sh \
 $(OUTPUT)/test_progs: trace_helpers.c
 $(OUTPUT)/get_cgroup_id_user: cgroup_helpers.c
 $(OUTPUT)/test_cgroup_storage: cgroup_helpers.c
+$(OUTPUT)/test_netcnt: cgroup_helpers.c
 
 .PHONY: force
 
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/bpf.h>
+#include <linux/version.h>
+
+#include "bpf_helpers.h"
+#include "netcnt_common.h"
+
+#define MAX_BPS        (3 * 1024 * 1024)
+
+#define REFRESH_TIME_NS        100000000
+#define NS_PER_SEC     1000000000
+
+struct bpf_map_def SEC("maps") percpu_netcnt = {
+       .type = BPF_MAP_TYPE_PERCPU_CGROUP_STORAGE,
+       .key_size = sizeof(struct bpf_cgroup_storage_key),
+       .value_size = sizeof(struct percpu_net_cnt),
+};
+
+struct bpf_map_def SEC("maps") netcnt = {
+       .type = BPF_MAP_TYPE_CGROUP_STORAGE,
+       .key_size = sizeof(struct bpf_cgroup_storage_key),
+       .value_size = sizeof(struct net_cnt),
+};
+
+SEC("cgroup/skb")
+int bpf_nextcnt(struct __sk_buff *skb)
+{
+       struct percpu_net_cnt *percpu_cnt;
+       char fmt[] = "%d %llu %llu\n";
+       struct net_cnt *cnt;
+       __u64 ts, dt;
+       int ret;
+
+       cnt = bpf_get_local_storage(&netcnt, 0);
+       percpu_cnt = bpf_get_local_storage(&percpu_netcnt, 0);
+
+       percpu_cnt->packets++;
+       percpu_cnt->bytes += skb->len;
+
+       if (percpu_cnt->packets > MAX_PERCPU_PACKETS) {
+               __sync_fetch_and_add(&cnt->packets,
+                                    percpu_cnt->packets);
+               percpu_cnt->packets = 0;
+
+               __sync_fetch_and_add(&cnt->bytes,
+                                    percpu_cnt->bytes);
+               percpu_cnt->bytes = 0;
+       }
+
+       ts = bpf_ktime_get_ns();
+       dt = ts - percpu_cnt->prev_ts;
+
+       dt *= MAX_BPS;
+       dt /= NS_PER_SEC;
+
+       if (cnt->bytes + percpu_cnt->bytes - percpu_cnt->prev_bytes < dt)
+               ret = 1;
+       else
+               ret = 0;
+
+       if (dt > REFRESH_TIME_NS) {
+               percpu_cnt->prev_ts = ts;
+               percpu_cnt->prev_packets = cnt->packets;
+               percpu_cnt->prev_bytes = cnt->bytes;
+       }
+
+       return !!ret;
+}
+
+char _license[] SEC("license") = "GPL";
+__u32 _version SEC("version") = LINUX_VERSION_CODE;
 
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/sysinfo.h>
+#include <sys/time.h>
+
+#include <linux/bpf.h>
+#include <bpf/bpf.h>
+#include <bpf/libbpf.h>
+
+#include "cgroup_helpers.h"
+#include "bpf_rlimit.h"
+#include "netcnt_common.h"
+
+#define BPF_PROG "./netcnt_prog.o"
+#define TEST_CGROUP "/test-network-counters/"
+
+static int bpf_find_map(const char *test, struct bpf_object *obj,
+                       const char *name)
+{
+       struct bpf_map *map;
+
+       map = bpf_object__find_map_by_name(obj, name);
+       if (!map) {
+               printf("%s:FAIL:map '%s' not found\n", test, name);
+               return -1;
+       }
+       return bpf_map__fd(map);
+}
+
+int main(int argc, char **argv)
+{
+       struct percpu_net_cnt *percpu_netcnt;
+       struct bpf_cgroup_storage_key key;
+       int map_fd, percpu_map_fd;
+       int error = EXIT_FAILURE;
+       struct net_cnt netcnt;
+       struct bpf_object *obj;
+       int prog_fd, cgroup_fd;
+       unsigned long packets;
+       unsigned long bytes;
+       int cpu, nproc;
+       __u32 prog_cnt;
+
+       nproc = get_nprocs_conf();
+       percpu_netcnt = malloc(sizeof(*percpu_netcnt) * nproc);
+       if (!percpu_netcnt) {
+               printf("Not enough memory for per-cpu area (%d cpus)\n", nproc);
+               goto err;
+       }
+
+       if (bpf_prog_load(BPF_PROG, BPF_PROG_TYPE_CGROUP_SKB,
+                         &obj, &prog_fd)) {
+               printf("Failed to load bpf program\n");
+               goto out;
+       }
+
+       if (setup_cgroup_environment()) {
+               printf("Failed to load bpf program\n");
+               goto err;
+       }
+
+       /* Create a cgroup, get fd, and join it */
+       cgroup_fd = create_and_get_cgroup(TEST_CGROUP);
+       if (!cgroup_fd) {
+               printf("Failed to create test cgroup\n");
+               goto err;
+       }
+
+       if (join_cgroup(TEST_CGROUP)) {
+               printf("Failed to join cgroup\n");
+               goto err;
+       }
+
+       /* Attach bpf program */
+       if (bpf_prog_attach(prog_fd, cgroup_fd, BPF_CGROUP_INET_EGRESS, 0)) {
+               printf("Failed to attach bpf program");
+               goto err;
+       }
+
+       assert(system("ping localhost -6 -c 10000 -f -q > /dev/null") == 0);
+
+       if (bpf_prog_query(cgroup_fd, BPF_CGROUP_INET_EGRESS, 0, NULL, NULL,
+                          &prog_cnt)) {
+               printf("Failed to query attached programs");
+               goto err;
+       }
+
+       map_fd = bpf_find_map(__func__, obj, "netcnt");
+       if (map_fd < 0) {
+               printf("Failed to find bpf map with net counters");
+               goto err;
+       }
+
+       percpu_map_fd = bpf_find_map(__func__, obj, "percpu_netcnt");
+       if (percpu_map_fd < 0) {
+               printf("Failed to find bpf map with percpu net counters");
+               goto err;
+       }
+
+       if (bpf_map_get_next_key(map_fd, NULL, &key)) {
+               printf("Failed to get key in cgroup storage\n");
+               goto err;
+       }
+
+       if (bpf_map_lookup_elem(map_fd, &key, &netcnt)) {
+               printf("Failed to lookup cgroup storage\n");
+               goto err;
+       }
+
+       if (bpf_map_lookup_elem(percpu_map_fd, &key, &percpu_netcnt[0])) {
+               printf("Failed to lookup percpu cgroup storage\n");
+               goto err;
+       }
+
+       /* Some packets can be still in per-cpu cache, but not more than
+        * MAX_PERCPU_PACKETS.
+        */
+       packets = netcnt.packets;
+       bytes = netcnt.bytes;
+       for (cpu = 0; cpu < nproc; cpu++) {
+               if (percpu_netcnt[cpu].packets > MAX_PERCPU_PACKETS) {
+                       printf("Unexpected percpu value: %llu\n",
+                              percpu_netcnt[cpu].packets);
+                       goto err;
+               }
+
+               packets += percpu_netcnt[cpu].packets;
+               bytes += percpu_netcnt[cpu].bytes;
+       }
+
+       /* No packets should be lost */
+       if (packets != 10000) {
+               printf("Unexpected packet count: %lu\n", packets);
+               goto err;
+       }
+
+       /* Let's check that bytes counter matches the number of packets
+        * multiplied by the size of ipv6 ICMP packet.
+        */
+       if (bytes != packets * 104) {
+               printf("Unexpected bytes count: %lu\n", bytes);
+               goto err;
+       }
+
+       error = 0;
+       printf("test_netcnt:PASS\n");
+
+err:
+       cleanup_cgroup_environment();
+       free(percpu_netcnt);
+
+out:
+       return error;
+}