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