Add a structured x-query-network QMP command that returns network
configuration as a NetworkInfo struct with pre-grouped hub information
and a flat list of non-hub clients. Each hub includes its ports with
inlined peer client info, and each client includes attached netfilter
details.
Refactor hmp_info_network() to consume only the QMP result instead of
directly accessing internal net_clients state. This decouples HMP from
internal data structures, making it possible to remove HMP support
without losing programmatic network introspection.
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
qapi/net.json | 122 +++++++++++++++++++++++++++++++++++++++++++++++++++++
include/net/net.h | 3 +-
net/hub.h | 4 +-
net/hub.c | 29 +++++++------
net/net-hmp-cmds.c | 59 +++++++++++++++++++-------
net/net.c | 70 +++++++++++++++++++++++-------
6 files changed, 243 insertions(+), 44 deletions(-)
diff --git a/qapi/net.json b/qapi/net.json
index dd56215fd15..c329da9cec8 100644
--- a/qapi/net.json
+++ b/qapi/net.json
@@ -1235,3 +1235,125 @@
'returns': ['UsernetInfo'],
'if': 'CONFIG_SLIRP',
'features': [ 'unstable' ] }
+
+##
+# @NetFilterInfo:
+#
+# Information about a netfilter attached to a network client.
+#
+# @name: filter object name (QOM path component)
+#
+# @type: QOM type name (e.g. "filter-mirror")
+#
+# @info: filter properties as comma-separated key=value pairs
+# (excluding @type)
+#
+# Since: 11.1
+##
+{ 'struct': 'NetFilterInfo',
+ 'data': {
+ 'name': 'str',
+ 'type': 'str',
+ 'info': 'str' } }
+
+##
+# @NetworkClientInfo:
+#
+# Information about a network client.
+#
+# @name: unique network client identifier
+#
+# @queue-index: index of this queue (0 for single-queue clients)
+#
+# @type: network client driver type
+#
+# @info-str: driver-specific formatted information string (e.g.
+# "model=e1000,macaddr=52:54:00:12:34:56")
+#
+# @peer: the connected peer client (always a leaf; the peer's own
+# peer field is never populated)
+#
+# @filters: attached netfilters
+#
+# Since: 11.1
+##
+{ 'struct': 'NetworkClientInfo',
+ 'data': {
+ 'name': 'str',
+ 'queue-index': 'uint32',
+ 'type': 'NetClientDriver',
+ 'info-str': 'str',
+ '*peer': 'NetworkClientInfo',
+ 'filters': ['NetFilterInfo'] } }
+
+##
+# @NetHubPortInfo:
+#
+# Information about a hub port.
+#
+# @name: hub port identifier
+#
+# @peer: the network client connected through this port
+#
+# Since: 11.1
+##
+{ 'struct': 'NetHubPortInfo',
+ 'data': {
+ 'name': 'str',
+ '*peer': 'NetworkClientInfo' } }
+
+##
+# @NetHubInfo:
+#
+# Information about a network hub.
+#
+# @id: hub identifier
+#
+# @ports: list of ports on this hub
+#
+# Since: 11.1
+##
+{ 'struct': 'NetHubInfo',
+ 'data': {
+ 'id': 'int',
+ 'ports': ['NetHubPortInfo'] } }
+
+##
+# @NetworkInfo:
+#
+# Information about the network configuration.
+#
+# @hubs: network hubs and their ports
+#
+# @clients: network clients not associated with a hub
+#
+# Since: 11.1
+##
+{ 'struct': 'NetworkInfo',
+ 'data': {
+ 'hubs': ['NetHubInfo'],
+ 'clients': ['NetworkClientInfo'] } }
+
+##
+# @x-query-network:
+#
+# Query the network configuration including hubs and clients.
+#
+# Features:
+#
+# @unstable: This command is meant for debugging.
+#
+# Returns: @NetworkInfo describing hubs and clients.
+#
+# Since: 11.1
+#
+# .. qmp-example::
+#
+# -> { "execute": "x-query-network" }
+# <- { "return": { "clients": [ { "name": "st0",
+# "queue-index": 0, "type": "stream",
+# "info-str": "listening" } ] } }
+##
+{ 'command': 'x-query-network',
+ 'returns': 'NetworkInfo',
+ 'features': [ 'unstable' ] }
diff --git a/include/net/net.h b/include/net/net.h
index 45bc86fc86b..0f0b524df00 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -274,7 +274,6 @@ DeviceState *qemu_create_nic_device(const char *typename, bool match_default,
void qemu_create_nic_bus_devices(BusState *bus, const char *parent_type,
const char *default_model,
const char *alias, const char *alias_target);
-void print_net_client(Monitor *mon, NetClientState *nc);
void net_socket_rs_init(SocketReadState *rs,
SocketReadStateFinalize *finalize,
bool vnet_hdr);
@@ -325,6 +324,8 @@ void netdev_add(QemuOpts *opts, Error **errp);
int net_hub_id_for_client(NetClientState *nc, int *id);
+NetworkClientInfo *net_client_info(NetClientState *nc);
+
#define DEFAULT_NETWORK_SCRIPT CONFIG_SYSCONFDIR "/qemu-ifup"
#define DEFAULT_NETWORK_DOWN_SCRIPT CONFIG_SYSCONFDIR "/qemu-ifdown"
#define DEFAULT_BRIDGE_HELPER CONFIG_QEMU_HELPERDIR "/qemu-bridge-helper"
diff --git a/net/hub.h b/net/hub.h
index ce45f7b399d..cab6b1459b1 100644
--- a/net/hub.h
+++ b/net/hub.h
@@ -15,10 +15,12 @@
#ifndef NET_HUB_H
#define NET_HUB_H
+#include "qapi/qapi-types-net.h"
+
NetClientState *net_hub_add_port(int hub_id, const char *name,
NetClientState *hubpeer);
-void net_hub_info(Monitor *mon);
void net_hub_check_clients(void);
bool net_hub_flush(NetClientState *nc);
+NetHubInfoList *net_hub_query_info(void);
#endif /* NET_HUB_H */
diff --git a/net/hub.c b/net/hub.c
index ee5881f6d5c..b53a4cc872c 100644
--- a/net/hub.c
+++ b/net/hub.c
@@ -14,7 +14,7 @@
#include "qemu/osdep.h"
#include "qapi/error.h"
-#include "monitor/monitor.h"
+#include "qapi/util.h"
#include "net/net.h"
#include "clients.h"
#include "hub.h"
@@ -199,26 +199,31 @@ NetClientState *net_hub_add_port(int hub_id, const char *name,
return &port->nc;
}
-/**
- * Print hub configuration
- */
-void net_hub_info(Monitor *mon)
+NetHubInfoList *net_hub_query_info(void)
{
+ NetHubInfoList *head = NULL, **tail = &head;
NetHub *hub;
- NetHubPort *port;
QLIST_FOREACH(hub, &hubs, next) {
- monitor_printf(mon, "hub %d\n", hub->id);
+ NetHubInfo *hi = g_new0(NetHubInfo, 1);
+ NetHubPortInfoList **ptail = &hi->ports;
+ NetHubPort *port;
+
+ hi->id = hub->id;
+
QLIST_FOREACH(port, &hub->ports, next) {
- monitor_printf(mon, " \\ %s", port->nc.name);
+ NetHubPortInfo *pi = g_new0(NetHubPortInfo, 1);
+ pi->name = g_strdup(port->nc.name);
if (port->nc.peer) {
- monitor_printf(mon, ": ");
- print_net_client(mon, port->nc.peer);
- } else {
- monitor_printf(mon, "\n");
+ pi->peer = net_client_info(port->nc.peer);
}
+ QAPI_LIST_APPEND(ptail, pi);
}
+
+ QAPI_LIST_APPEND(tail, hi);
}
+
+ return head;
}
/**
diff --git a/net/net-hmp-cmds.c b/net/net-hmp-cmds.c
index 2b24c9e6049..52ac8da770d 100644
--- a/net/net-hmp-cmds.c
+++ b/net/net-hmp-cmds.c
@@ -19,37 +19,66 @@
#include "monitor/hmp-completion.h"
#include "monitor/monitor.h"
#include "net/net.h"
-#include "net/hub.h"
#include "qapi/clone-visitor.h"
#include "qapi/qapi-commands-net.h"
#include "qapi/qapi-visit-net.h"
+#include "qapi/error.h"
#include "qobject/qdict.h"
#include "qemu/config-file.h"
#include "qemu/help_option.h"
#include "qemu/option.h"
-void hmp_info_network(Monitor *mon, const QDict *qdict)
+static void hmp_print_client_info(Monitor *mon, NetworkClientInfo *ci)
{
- NetClientState *nc, *peer;
- NetClientDriver type;
+ NetFilterInfoList *f;
+
+ monitor_printf(mon, "%s: index=%" PRIu32 ",type=%s,%s\n",
+ ci->name, ci->queue_index,
+ NetClientDriver_str(ci->type), ci->info_str);
+ if (ci->filters) {
+ monitor_printf(mon, "filters:\n");
+ for (f = ci->filters; f; f = f->next) {
+ monitor_printf(mon, " - %s: type=%s%s%s\n",
+ f->value->name, f->value->type,
+ f->value->info[0] ? "," : "", f->value->info);
+ }
+ }
+}
- net_hub_info(mon);
+void hmp_info_network(Monitor *mon, const QDict *qdict)
+{
+ Error *err = NULL;
+ g_autoptr(NetworkInfo) info = qmp_x_query_network(&err);
+ NetHubInfoList *h;
+ NetworkClientInfoList *entry;
- QTAILQ_FOREACH(nc, &net_clients, next) {
- peer = nc->peer;
- type = nc->info->type;
+ if (hmp_handle_error(mon, err)) {
+ return;
+ }
- /* Skip if already printed in hub info */
- if (net_hub_id_for_client(nc, NULL) == 0) {
- continue;
+ for (h = info->hubs; h; h = h->next) {
+ NetHubPortInfoList *p;
+
+ monitor_printf(mon, "hub %d\n", (int)h->value->id);
+ for (p = h->value->ports; p; p = p->next) {
+ if (p->value->peer) {
+ monitor_printf(mon, " \\ %s: ", p->value->name);
+ hmp_print_client_info(mon, p->value->peer);
+ } else {
+ monitor_printf(mon, " \\ %s\n", p->value->name);
+ }
}
+ }
+
+ for (entry = info->clients; entry; entry = entry->next) {
+ NetworkClientInfo *ci = entry->value;
- if (!peer || type == NET_CLIENT_DRIVER_NIC) {
- print_net_client(mon, nc);
+ if (!ci->peer || ci->type == NET_CLIENT_DRIVER_NIC) {
+ hmp_print_client_info(mon, ci);
} /* else it's a netdev connected to a NIC, printed with the NIC */
- if (peer && type == NET_CLIENT_DRIVER_NIC) {
+ if (ci->peer && ci->type == NET_CLIENT_DRIVER_NIC) {
monitor_printf(mon, " \\ ");
- print_net_client(mon, peer);
+ hmp_print_client_info(mon, ci->peer);
}
}
}
diff --git a/net/net.c b/net/net.c
index 2892f1730d1..2bcd229cd9d 100644
--- a/net/net.c
+++ b/net/net.c
@@ -1523,14 +1523,14 @@ void qmp_netdev_del(const char *id, Error **errp)
}
}
-static void netfilter_print_info(Monitor *mon, NetFilterState *nf)
+static char *netfilter_get_info_str(NetFilterState *nf)
{
char *str;
ObjectProperty *prop;
ObjectPropertyIterator iter;
Visitor *v;
+ GString *buf = g_string_new(NULL);
- /* generate info str */
object_property_iter_init(&iter, OBJECT(nf));
while ((prop = object_property_iter_next(&iter))) {
if (!strcmp(prop->name, "type")) {
@@ -1540,29 +1540,69 @@ static void netfilter_print_info(Monitor *mon, NetFilterState *nf)
object_property_get(OBJECT(nf), prop->name, v, NULL);
visit_complete(v, &str);
visit_free(v);
- monitor_printf(mon, ",%s=%s", prop->name, str);
+ if (buf->len > 0) {
+ g_string_append_c(buf, ',');
+ }
+ g_string_append_printf(buf, "%s=%s", prop->name, str);
g_free(str);
}
- monitor_printf(mon, "\n");
+
+ return g_string_free(buf, false);
}
-void print_net_client(Monitor *mon, NetClientState *nc)
+static NetworkClientInfo *net_client_info_no_peer(NetClientState *nc)
{
+ NetworkClientInfo *info = g_new0(NetworkClientInfo, 1);
NetFilterState *nf;
- monitor_printf(mon, "%s: index=%d,type=%s,%s\n", nc->name,
- nc->queue_index,
- NetClientDriver_str(nc->info->type),
- nc->info_str);
+ info->name = g_strdup(nc->name);
+ info->queue_index = nc->queue_index;
+ info->type = nc->info->type;
+ info->info_str = g_strdup(nc->info_str);
+
if (!QTAILQ_EMPTY(&nc->filters)) {
- monitor_printf(mon, "filters:\n");
+ NetFilterInfoList **ftail = &info->filters;
+ QTAILQ_FOREACH(nf, &nc->filters, next) {
+ NetFilterInfo *fi = g_new0(NetFilterInfo, 1);
+ fi->name = g_strdup(
+ object_get_canonical_path_component(OBJECT(nf)));
+ fi->type = g_strdup(object_get_typename(OBJECT(nf)));
+ fi->info = netfilter_get_info_str(nf);
+ QAPI_LIST_APPEND(ftail, fi);
+ }
}
- QTAILQ_FOREACH(nf, &nc->filters, next) {
- monitor_printf(mon, " - %s: type=%s",
- object_get_canonical_path_component(OBJECT(nf)),
- object_get_typename(OBJECT(nf)));
- netfilter_print_info(mon, nf);
+
+ return info;
+}
+
+NetworkClientInfo *net_client_info(NetClientState *nc)
+{
+ NetworkClientInfo *info = net_client_info_no_peer(nc);
+
+ if (nc->peer) {
+ info->peer = net_client_info_no_peer(nc->peer);
}
+
+ return info;
+}
+
+NetworkInfo *qmp_x_query_network(Error **errp)
+{
+ NetworkInfo *info = g_new0(NetworkInfo, 1);
+ NetworkClientInfoList **tail = &info->clients;
+ NetClientState *nc;
+
+ info->hubs = net_hub_query_info();
+
+ QTAILQ_FOREACH(nc, &net_clients, next) {
+ /* Skip if already gathered in hub info */
+ if (net_hub_id_for_client(nc, NULL) == 0) {
+ continue;
+ }
+ QAPI_LIST_APPEND(tail, net_client_info(nc));
+ }
+
+ return info;
}
RxFilterInfoList *qmp_query_rx_filter(const char *name, Error **errp)
--
2.54.0