From: Jason Wang <[email protected]>

Add packet and byte counters to NetFilterState to track traffic
passing through netfilters. The counters are updated in
qemu_netfilter_receive() for both TX and RX directions.

New QMP command 'query-netfilter-stats' and HMP command
'info netfilter-stats' are added to query the statistics.

The NetFilterStats structure includes:
- name: filter name
- netdev: attached netdev id
- type: filter type (e.g., filter-mirror)
- packets-tx/rx: packet counters
- bytes-tx/rx: byte counters

Example QMP usage:
  {"execute": "query-netfilter-stats"}

Example HMP usage:
  (qemu) info netfilter-stats

Signed-off-by: Jason Wang <[email protected]>
Signed-off-by: Cindy Lu <[email protected]>
---
 hmp-commands-info.hx  | 13 +++++++++
 include/monitor/hmp.h |  1 +
 include/net/filter.h  |  6 +++++
 net/filter.c          | 16 ++++++++++-
 net/net-hmp-cmds.c    | 28 +++++++++++++++++++
 net/net.c             | 42 +++++++++++++++++++++++++++++
 qapi/net.json         | 63 +++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 168 insertions(+), 1 deletion(-)

diff --git a/hmp-commands-info.hx b/hmp-commands-info.hx
index 41674dcbe1..d67df3f98a 100644
--- a/hmp-commands-info.hx
+++ b/hmp-commands-info.hx
@@ -44,6 +44,19 @@ SRST
     Show the network state.
 ERST
 
+    {
+        .name       = "netfilter-stats",
+        .args_type  = "",
+        .params     = "",
+        .help       = "show netfilter statistics",
+        .cmd        = hmp_info_netfilter_stats,
+    },
+
+SRST
+  ``info netfilter-stats``
+    Show netfilter statistics (packets and bytes counters).
+ERST
+
     {
         .name       = "chardev",
         .args_type  = "",
diff --git a/include/monitor/hmp.h b/include/monitor/hmp.h
index 83721b5ffc..7150ca8514 100644
--- a/include/monitor/hmp.h
+++ b/include/monitor/hmp.h
@@ -56,6 +56,7 @@ void hmp_cont(Monitor *mon, const QDict *qdict);
 void hmp_system_wakeup(Monitor *mon, const QDict *qdict);
 void hmp_nmi(Monitor *mon, const QDict *qdict);
 void hmp_info_network(Monitor *mon, const QDict *qdict);
+void hmp_info_netfilter_stats(Monitor *mon, const QDict *qdict);
 void hmp_set_link(Monitor *mon, const QDict *qdict);
 void hmp_balloon(Monitor *mon, const QDict *qdict);
 void hmp_loadvm(Monitor *mon, const QDict *qdict);
diff --git a/include/net/filter.h b/include/net/filter.h
index f15f7932b2..04786e4e87 100644
--- a/include/net/filter.h
+++ b/include/net/filter.h
@@ -60,6 +60,12 @@ struct NetFilterState {
     char *position;
     bool insert_before_flag;
     QTAILQ_ENTRY(NetFilterState) next;
+
+    /* statistics */
+    uint64_t bytes_tx;
+    uint64_t bytes_rx;
+    uint64_t packets_tx;
+    uint64_t packets_rx;
 };
 
 ssize_t qemu_netfilter_receive(NetFilterState *nf,
diff --git a/net/filter.c b/net/filter.c
index c7cc6615dc..76345c1a9d 100644
--- a/net/filter.c
+++ b/net/filter.c
@@ -33,13 +33,27 @@ ssize_t qemu_netfilter_receive(NetFilterState *nf,
                                int iovcnt,
                                NetPacketSent *sent_cb)
 {
+    ssize_t ret;
+    size_t size;
+
     if (qemu_can_skip_netfilter(nf)) {
         return 0;
     }
     if (nf->direction == direction ||
         nf->direction == NET_FILTER_DIRECTION_ALL) {
-        return NETFILTER_GET_CLASS(OBJECT(nf))->receive_iov(
+        /* Update statistics */
+        size = iov_size(iov, iovcnt);
+        if (direction == NET_FILTER_DIRECTION_TX) {
+            nf->packets_tx++;
+            nf->bytes_tx += size;
+        } else {
+            nf->packets_rx++;
+            nf->bytes_rx += size;
+        }
+
+        ret = NETFILTER_GET_CLASS(OBJECT(nf))->receive_iov(
                                    nf, sender, flags, iov, iovcnt, sent_cb);
+        return ret;
     }
 
     return 0;
diff --git a/net/net-hmp-cmds.c b/net/net-hmp-cmds.c
index e7c55d2787..9bdd6d233f 100644
--- a/net/net-hmp-cmds.c
+++ b/net/net-hmp-cmds.c
@@ -168,3 +168,31 @@ void netdev_del_completion(ReadLineState *rs, int nb_args, 
const char *str)
         }
     }
 }
+
+void hmp_info_netfilter_stats(Monitor *mon, const QDict *qdict)
+{
+    NetFilterStatsList *stats_list, *entry;
+    Error *err = NULL;
+
+    stats_list = qmp_query_netfilter_stats(NULL, &err);
+    if (hmp_handle_error(mon, err)) {
+        return;
+    }
+
+    if (!stats_list) {
+        monitor_printf(mon, "No netfilters found\n");
+        return;
+    }
+
+    for (entry = stats_list; entry; entry = entry->next) {
+        NetFilterStats *stats = entry->value;
+        monitor_printf(mon, "%s: netdev=%s type=%s\n",
+                       stats->name, stats->netdev, stats->type);
+        monitor_printf(mon, "  tx: %" PRIu64 " packets, %" PRIu64 " bytes\n",
+                       stats->packets_tx, stats->bytes_tx);
+        monitor_printf(mon, "  rx: %" PRIu64 " packets, %" PRIu64 " bytes\n",
+                       stats->packets_rx, stats->bytes_rx);
+    }
+
+    qapi_free_NetFilterStatsList(stats_list);
+}
diff --git a/net/net.c b/net/net.c
index 27e0d27807..40966fbf8a 100644
--- a/net/net.c
+++ b/net/net.c
@@ -1604,6 +1604,48 @@ RxFilterInfoList *qmp_query_rx_filter(const char *name, 
Error **errp)
     return filter_list;
 }
 
+NetFilterStatsList *qmp_query_netfilter_stats(const char *name, Error **errp)
+{
+    NetClientState *nc;
+    NetFilterState *nf;
+    NetFilterStatsList *stats_list = NULL, **tail = &stats_list;
+    bool found = false;
+
+    QTAILQ_FOREACH(nc, &net_clients, next) {
+        QTAILQ_FOREACH(nf, &nc->filters, next) {
+            NetFilterStats *stats;
+            const char *filter_name;
+
+            filter_name = object_get_canonical_path_component(OBJECT(nf));
+            if (name && strcmp(filter_name, name) != 0) {
+                continue;
+            }
+
+            found = true;
+            stats = g_new0(NetFilterStats, 1);
+            stats->name = g_strdup(filter_name);
+            stats->netdev = g_strdup(nf->netdev_id);
+            stats->type = g_strdup(object_get_typename(OBJECT(nf)));
+            stats->packets_tx = nf->packets_tx;
+            stats->packets_rx = nf->packets_rx;
+            stats->bytes_tx = nf->bytes_tx;
+            stats->bytes_rx = nf->bytes_rx;
+
+            QAPI_LIST_APPEND(tail, stats);
+
+            if (name) {
+                return stats_list;
+            }
+        }
+    }
+
+    if (name && !found) {
+        error_setg(errp, "netfilter '%s' not found", name);
+    }
+
+    return stats_list;
+}
+
 void colo_notify_filters_event(int event, Error **errp)
 {
     NetClientState *nc;
diff --git a/qapi/net.json b/qapi/net.json
index 118bd34965..277cdd9e32 100644
--- a/qapi/net.json
+++ b/qapi/net.json
@@ -1191,3 +1191,66 @@
 ##
 { 'event': 'NETDEV_VHOST_USER_DISCONNECTED',
   'data': { 'netdev-id': 'str' } }
+
+##
+# @NetFilterStats:
+#
+# Statistics for a network filter.
+#
+# @name: the name of the network filter
+#
+# @netdev: the netdev id attached to
+#
+# @type: the filter type
+#
+# @packets-tx: number of transmitted packets
+#
+# @packets-rx: number of received packets
+#
+# @bytes-tx: number of transmitted bytes
+#
+# @bytes-rx: number of received bytes
+#
+# Since: 10.1
+##
+{ 'struct': 'NetFilterStats',
+  'data': {
+    'name':       'str',
+    'netdev':     'str',
+    'type':       'str',
+    'packets-tx': 'uint64',
+    'packets-rx': 'uint64',
+    'bytes-tx':   'uint64',
+    'bytes-rx':   'uint64' } }
+
+##
+# @query-netfilter-stats:
+#
+# Return statistics for network filters.
+#
+# @name: Optional filter name to query.  If not specified, returns
+#     stats for all filters.
+#
+# Returns: A list of @NetFilterStats for each filter.
+#
+# Since: 10.1
+#
+# .. qmp-example::
+#
+#     -> { "execute": "query-netfilter-stats" }
+#     <- { "return": [
+#             {
+#                 "name": "f0",
+#                 "netdev": "net0",
+#                 "type": "filter-mirror",
+#                 "packets-tx": 12345,
+#                 "packets-rx": 0,
+#                 "bytes-tx": 1234567,
+#                 "bytes-rx": 0
+#             }
+#           ]
+#        }
+##
+{ 'command': 'query-netfilter-stats',
+  'data': { '*name': 'str' },
+  'returns': ['NetFilterStats'] }
-- 
2.52.0


Reply via email to