net: add x-query-network QMP command
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. Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Message-ID: <20260828-qemu-no-hmp-v5-19-9227de146347@redhat.com>
Marc-André Lureau committed
Aug 28, 2026 at 16:04 UTC
ba78ac4ecd717c1c061f487bf571f130f31328e9
6 files changed
+243
-44
include/net/net.h
+2
-1
@@ -274,7 +274,6 @@ DeviceState *qemu_create_nic_device(const char *typename, bool match_default,
274
void qemu_create_nic_bus_devices(BusState *bus, const char *parent_type,
275
const char *default_model,
276
const char *alias, const char *alias_target);
277
-void print_net_client(Monitor *mon, NetClientState *nc);
277
void net_socket_rs_init(SocketReadState *rs,
278
SocketReadStateFinalize *finalize,
279
bool vnet_hdr);
@@ -325,6 +324,8 @@ void netdev_add(QemuOpts *opts, Error **errp);
324
325
int net_hub_id_for_client(NetClientState *nc, int *id);
326
327
+NetworkClientInfo *net_client_info(NetClientState *nc);
328
+
329
#define DEFAULT_NETWORK_SCRIPT CONFIG_SYSCONFDIR "/qemu-ifup"
330
#define DEFAULT_NETWORK_DOWN_SCRIPT CONFIG_SYSCONFDIR "/qemu-ifdown"
331
#define DEFAULT_BRIDGE_HELPER CONFIG_QEMU_HELPERDIR "/qemu-bridge-helper"
net/hub.c
+17
-12
@@ -14,7 +14,7 @@
14
15
#include "qemu/osdep.h"
16
#include "qapi/error.h"
17
-#include "monitor/monitor.h"
17
+#include "qapi/util.h"
18
#include "net/net.h"
19
#include "clients.h"
20
#include "hub.h"
@@ -199,26 +199,31 @@ NetClientState *net_hub_add_port(int hub_id, const char *name,
199
return &port->nc;
200
}
201
202
-/**
203
- * Print hub configuration
204
- */
205
-void net_hub_info(Monitor *mon)
202
+NetHubInfoList *net_hub_query_info(void)
203
{
204
+ NetHubInfoList *head = NULL, **tail = &head;
205
NetHub *hub;
208
- NetHubPort *port;
206
207
QLIST_FOREACH(hub, &hubs, next) {
211
- monitor_printf(mon, "hub %d\n", hub->id);
208
+ NetHubInfo *hi = g_new0(NetHubInfo, 1);
209
+ NetHubPortInfoList **ptail = &hi->ports;
210
+ NetHubPort *port;
211
+
212
+ hi->id = hub->id;
213
+
214
QLIST_FOREACH(port, &hub->ports, next) {
213
- monitor_printf(mon, " \\ %s", port->nc.name);
215
+ NetHubPortInfo *pi = g_new0(NetHubPortInfo, 1);
216
+ pi->name = g_strdup(port->nc.name);
217
if (port->nc.peer) {
215
- monitor_printf(mon, ": ");
216
- print_net_client(mon, port->nc.peer);
217
- } else {
218
- monitor_printf(mon, "\n");
218
+ pi->peer = net_client_info(port->nc.peer);
219
}
220
+ QAPI_LIST_APPEND(ptail, pi);
221
}
222
+
223
+ QAPI_LIST_APPEND(tail, hi);
224
}
225
+
226
+ return head;
227
}
228
229
/**
net/hub.h
+3
-1
@@ -15,10 +15,12 @@
15
#ifndef NET_HUB_H
16
#define NET_HUB_H
17
18
+#include "qapi/qapi-types-net.h"
19
+
20
NetClientState *net_hub_add_port(int hub_id, const char *name,
21
NetClientState *hubpeer);
20
-void net_hub_info(Monitor *mon);
22
void net_hub_check_clients(void);
23
bool net_hub_flush(NetClientState *nc);
24
+NetHubInfoList *net_hub_query_info(void);
25
26
#endif /* NET_HUB_H */
net/net-hmp-cmds.c
+44
-15
@@ -19,37 +19,66 @@
19
#include "monitor/hmp-completion.h"
20
#include "monitor/monitor.h"
21
#include "net/net.h"
22
-#include "net/hub.h"
22
#include "qapi/clone-visitor.h"
23
#include "qapi/qapi-commands-net.h"
24
#include "qapi/qapi-visit-net.h"
25
+#include "qapi/error.h"
26
#include "qobject/qdict.h"
27
#include "qemu/config-file.h"
28
#include "qemu/help_option.h"
29
#include "qemu/option.h"
30
31
-void hmp_info_network(Monitor *mon, const QDict *qdict)
31
+static void hmp_print_client_info(Monitor *mon, NetworkClientInfo *ci)
32
{
33
- NetClientState *nc, *peer;
34
- NetClientDriver type;
33
+ NetFilterInfoList *f;
34
+
35
+ monitor_printf(mon, "%s: index=%" PRIu32 ",type=%s,%s\n",
36
+ ci->name, ci->queue_index,
37
+ NetClientDriver_str(ci->type), ci->info_str);
38
+ if (ci->filters) {
39
+ monitor_printf(mon, "filters:\n");
40
+ for (f = ci->filters; f; f = f->next) {
41
+ monitor_printf(mon, " - %s: type=%s%s%s\n",
42
+ f->value->name, f->value->type,
43
+ f->value->info[0] ? "," : "", f->value->info);
44
+ }
45
+ }
46
+}
47
36
- net_hub_info(mon);
48
+void hmp_info_network(Monitor *mon, const QDict *qdict)
49
+{
50
+ Error *err = NULL;
51
+ g_autoptr(NetworkInfo) info = qmp_x_query_network(&err);
52
+ NetHubInfoList *h;
53
+ NetworkClientInfoList *entry;
54
38
- QTAILQ_FOREACH(nc, &net_clients, next) {
39
- peer = nc->peer;
40
- type = nc->info->type;
55
+ if (hmp_handle_error(mon, err)) {
56
+ return;
57
+ }
58
42
- /* Skip if already printed in hub info */
43
- if (net_hub_id_for_client(nc, NULL) == 0) {
44
- continue;
59
+ for (h = info->hubs; h; h = h->next) {
60
+ NetHubPortInfoList *p;
61
+
62
+ monitor_printf(mon, "hub %d\n", (int)h->value->id);
63
+ for (p = h->value->ports; p; p = p->next) {
64
+ if (p->value->peer) {
65
+ monitor_printf(mon, " \\ %s: ", p->value->name);
66
+ hmp_print_client_info(mon, p->value->peer);
67
+ } else {
68
+ monitor_printf(mon, " \\ %s\n", p->value->name);
69
+ }
70
}
71
+ }
72
+
73
+ for (entry = info->clients; entry; entry = entry->next) {
74
+ NetworkClientInfo *ci = entry->value;
75
47
- if (!peer || type == NET_CLIENT_DRIVER_NIC) {
48
- print_net_client(mon, nc);
76
+ if (!ci->peer || ci->type == NET_CLIENT_DRIVER_NIC) {
77
+ hmp_print_client_info(mon, ci);
78
} /* else it's a netdev connected to a NIC, printed with the NIC */
50
- if (peer && type == NET_CLIENT_DRIVER_NIC) {
79
+ if (ci->peer && ci->type == NET_CLIENT_DRIVER_NIC) {
80
monitor_printf(mon, " \\ ");
52
- print_net_client(mon, peer);
81
+ hmp_print_client_info(mon, ci->peer);
82
}
83
}
84
}
net/net.c
+55
-15
@@ -1525,14 +1525,14 @@ void qmp_netdev_del(const char *id, Error **errp)
1525
}
1526
}
1527
1528
-static void netfilter_print_info(Monitor *mon, NetFilterState *nf)
1528
+static char *netfilter_get_info_str(NetFilterState *nf)
1529
{
1530
char *str;
1531
ObjectProperty *prop;
1532
ObjectPropertyIterator iter;
1533
Visitor *v;
1534
+ GString *buf = g_string_new(NULL);
1535
1535
- /* generate info str */
1536
object_property_iter_init(&iter, OBJECT(nf));
1537
while ((prop = object_property_iter_next(&iter))) {
1538
if (!strcmp(prop->name, "type")) {
@@ -1542,29 +1542,69 @@ static void netfilter_print_info(Monitor *mon, NetFilterState *nf)
1542
object_property_get(OBJECT(nf), prop->name, v, NULL);
1543
visit_complete(v, &str);
1544
visit_free(v);
1545
- monitor_printf(mon, ",%s=%s", prop->name, str);
1545
+ if (buf->len > 0) {
1546
+ g_string_append_c(buf, ',');
1547
+ }
1548
+ g_string_append_printf(buf, "%s=%s", prop->name, str);
1549
g_free(str);
1550
}
1548
- monitor_printf(mon, "\n");
1551
+
1552
+ return g_string_free(buf, false);
1553
}
1554
1551
-void print_net_client(Monitor *mon, NetClientState *nc)
1555
+static NetworkClientInfo *net_client_info_no_peer(NetClientState *nc)
1556
{
1557
+ NetworkClientInfo *info = g_new0(NetworkClientInfo, 1);
1558
NetFilterState *nf;
1559
1555
- monitor_printf(mon, "%s: index=%d,type=%s,%s\n", nc->name,
1556
- nc->queue_index,
1557
- NetClientDriver_str(nc->info->type),
1558
- nc->info_str);
1560
+ info->name = g_strdup(nc->name);
1561
+ info->queue_index = nc->queue_index;
1562
+ info->type = nc->info->type;
1563
+ info->info_str = g_strdup(nc->info_str);
1564
+
1565
if (!QTAILQ_EMPTY(&nc->filters)) {
1560
- monitor_printf(mon, "filters:\n");
1566
+ NetFilterInfoList **ftail = &info->filters;
1567
+ QTAILQ_FOREACH(nf, &nc->filters, next) {
1568
+ NetFilterInfo *fi = g_new0(NetFilterInfo, 1);
1569
+ fi->name = g_strdup(
1570
+ object_get_canonical_path_component(OBJECT(nf)));
1571
+ fi->type = g_strdup(object_get_typename(OBJECT(nf)));
1572
+ fi->info = netfilter_get_info_str(nf);
1573
+ QAPI_LIST_APPEND(ftail, fi);
1574
+ }
1575
}
1562
- QTAILQ_FOREACH(nf, &nc->filters, next) {
1563
- monitor_printf(mon, " - %s: type=%s",
1564
- object_get_canonical_path_component(OBJECT(nf)),
1565
- object_get_typename(OBJECT(nf)));
1566
- netfilter_print_info(mon, nf);
1576
+
1577
+ return info;
1578
+}
1579
+
1580
+NetworkClientInfo *net_client_info(NetClientState *nc)
1581
+{
1582
+ NetworkClientInfo *info = net_client_info_no_peer(nc);
1583
+
1584
+ if (nc->peer) {
1585
+ info->peer = net_client_info_no_peer(nc->peer);
1586
}
1587
+
1588
+ return info;
1589
+}
1590
+
1591
+NetworkInfo *qmp_x_query_network(Error **errp)
1592
+{
1593
+ NetworkInfo *info = g_new0(NetworkInfo, 1);
1594
+ NetworkClientInfoList **tail = &info->clients;
1595
+ NetClientState *nc;
1596
+
1597
+ info->hubs = net_hub_query_info();
1598
+
1599
+ QTAILQ_FOREACH(nc, &net_clients, next) {
1600
+ /* Skip if already gathered in hub info */
1601
+ if (net_hub_id_for_client(nc, NULL) == 0) {
1602
+ continue;
1603
+ }
1604
+ QAPI_LIST_APPEND(tail, net_client_info(nc));
1605
+ }
1606
+
1607
+ return info;
1608
}
1609
1610
RxFilterInfoList *qmp_query_rx_filter(const char *name, Error **errp)
qapi/net.json
+122
@@ -1305,3 +1305,125 @@
1305
'returns': ['UsernetInfo'],
1306
'if': 'CONFIG_SLIRP',
1307
'features': [ 'unstable' ] }
1308
+
1309
+##
1310
+# @NetFilterInfo:
1311
+#
1312
+# Information about a netfilter attached to a network client.
1313
+#
1314
+# @name: filter object name (QOM path component)
1315
+#
1316
+# @type: QOM type name (e.g. "filter-mirror")
1317
+#
1318
+# @info: filter properties as comma-separated key=value pairs
1319
+# (excluding @type)
1320
+#
1321
+# Since: 11.2
1322
+##
1323
+{ 'struct': 'NetFilterInfo',
1324
+ 'data': {
1325
+ 'name': 'str',
1326
+ 'type': 'str',
1327
+ 'info': 'str' } }
1328
+
1329
+##
1330
+# @NetworkClientInfo:
1331
+#
1332
+# Information about a network client.
1333
+#
1334
+# @name: unique network client identifier
1335
+#
1336
+# @queue-index: index of this queue (0 for single-queue clients)
1337
+#
1338
+# @type: network client driver type
1339
+#
1340
+# @info-str: driver-specific formatted information string (e.g.
1341
+# "model=e1000,macaddr=52:54:00:12:34:56")
1342
+#
1343
+# @peer: the connected peer client (always a leaf; the peer's own
1344
+# peer field is never populated)
1345
+#
1346
+# @filters: attached netfilters
1347
+#
1348
+# Since: 11.2
1349
+##
1350
+{ 'struct': 'NetworkClientInfo',
1351
+ 'data': {
1352
+ 'name': 'str',
1353
+ 'queue-index': 'uint32',
1354
+ 'type': 'NetClientDriver',
1355
+ 'info-str': 'str',
1356
+ '*peer': 'NetworkClientInfo',
1357
+ 'filters': ['NetFilterInfo'] } }
1358
+
1359
+##
1360
+# @NetHubPortInfo:
1361
+#
1362
+# Information about a hub port.
1363
+#
1364
+# @name: hub port identifier
1365
+#
1366
+# @peer: the network client connected through this port
1367
+#
1368
+# Since: 11.2
1369
+##
1370
+{ 'struct': 'NetHubPortInfo',
1371
+ 'data': {
1372
+ 'name': 'str',
1373
+ '*peer': 'NetworkClientInfo' } }
1374
+
1375
+##
1376
+# @NetHubInfo:
1377
+#
1378
+# Information about a network hub.
1379
+#
1380
+# @id: hub identifier
1381
+#
1382
+# @ports: list of ports on this hub
1383
+#
1384
+# Since: 11.2
1385
+##
1386
+{ 'struct': 'NetHubInfo',
1387
+ 'data': {
1388
+ 'id': 'int',
1389
+ 'ports': ['NetHubPortInfo'] } }
1390
+
1391
+##
1392
+# @NetworkInfo:
1393
+#
1394
+# Information about the network configuration.
1395
+#
1396
+# @hubs: network hubs and their ports
1397
+#
1398
+# @clients: network clients not associated with a hub
1399
+#
1400
+# Since: 11.2
1401
+##
1402
+{ 'struct': 'NetworkInfo',
1403
+ 'data': {
1404
+ 'hubs': ['NetHubInfo'],
1405
+ 'clients': ['NetworkClientInfo'] } }
1406
+
1407
+##
1408
+# @x-query-network:
1409
+#
1410
+# Query the network configuration including hubs and clients.
1411
+#
1412
+# Features:
1413
+#
1414
+# @unstable: This command is meant for debugging.
1415
+#
1416
+# Returns: @NetworkInfo describing hubs and clients.
1417
+#
1418
+# Since: 11.2
1419
+#
1420
+# .. qmp-example::
1421
+#
1422
+# -> { "execute": "x-query-network" }
1423
+# <- { "return": { "clients": [ { "name": "st0",
1424
+# "queue-index": 0, "type": "stream",
1425
+# "info-str": "listening" } ] } }
1426
+##
1427
+{ 'command': 'x-query-network',
1428
+ 'returns': 'NetworkInfo',
1429
+ 'features': [ 'unstable' ] }