diff mbox series

[v5,3/4] hmp: Use QMP query-netdev in hmp_info_network

Message ID 20201108235952.107961-4-lekiravi@yandex-team.ru
State New
Headers show
Series Introducing QMP query-netdev command | expand

Commit Message

Alexey Kirillov Nov. 8, 2020, 11:59 p.m. UTC
Replace usage of legacy field info_str of NetClientState for
backend network devices with result of QMP command query-netdev.
NIC and hubports still use legacy info_str field.

Signed-off-by: Alexey Kirillov <lekiravi@yandex-team.ru>
---
 include/net/net.h |   3 +-
 net/clients.h     |   1 +
 net/hub.c         |   4 +-
 net/hub.h         |   2 +-
 net/net.c         | 181 ++++++++++++++++++++++++++++++++++++++++++++--
 net/vde.c         |  10 +++
 6 files changed, 192 insertions(+), 9 deletions(-)

Comments

Jason Wang Dec. 7, 2020, 5:52 a.m. UTC | #1
On 2020/11/9 上午7:59, Alexey Kirillov wrote:
> +#ifdef CONFIG_SLIRP
> +        case NET_BACKEND_USER: {
> +            size_t len = strchr(ni->u.user.net, '/') - ni->u.user.net;
> +            char *net = g_strndup(ni->u.user.net, len);
> +
> +            info_str = g_strdup_printf("net=%s,restrict=%s",
> +                                       net,
> +                                       ni->u.user.q_restrict ? "on" : "off");
> +            g_free(net);
> +            break;
> +        }
> +#endif /* CONFIG_SLIRP */
> +        case NET_BACKEND_TAP: {
> +#ifndef _WIN32
> +            if (ni->u.tap.has_fds) {
> +                char **fds = g_strsplit(ni->u.tap.fds, ":", -1);
> +
> +                info_str = g_strdup_printf("fd=%s", fds[nc->queue_index]);
> +                g_strfreev(fds);
> +            } else if (ni->u.tap.has_helper) {
> +                info_str = g_strdup_printf("helper=%s", ni->u.tap.helper);
> +            } else {
> +                info_str = g_strdup_printf("ifname=%s,script=%s,downscript=%s",
> +                    ni->u.tap.ifname,
> +                    nc->queue_index == 0 ? ni->u.tap.script : "no",
> +                    nc->queue_index == 0 ? ni->u.tap.downscript : "no");
> +            }
> +#else
> +            info_str = g_strdup_printf("tap: ifname=%s", ni->u.tap.ifname);
> +#endif /* _WIN32 */
> +            break;
> +        }
> +#ifdef CONFIG_L2TPV3
> +        case NET_BACKEND_L2TPV3: {
> +            info_str = g_strdup_printf("l2tpv3: connected");
> +            break;
> +        }
> +#endif /* CONFIG_L2TPV3 */
> +        case NET_BACKEND_SOCKET: {
> +            if (ni->u.socket.has_listen) {
> +                if (ni->u.socket.has_fd) {
> +                    info_str = g_strdup_printf("socket: connection from %s",
> +                                               ni->u.socket.listen);
> +                } else {
> +                    info_str = g_strdup_printf("socket: wait from %s",
> +                                               ni->u.socket.listen);
> +                }
> +            } else if (ni->u.socket.has_connect && ni->u.socket.has_fd) {
> +                info_str = g_strdup_printf("socket: connect to %s",
> +                                           ni->u.socket.connect);
> +            } else if (ni->u.socket.has_mcast && ni->u.socket.has_fd) {
> +                info_str = g_strdup_printf("socket: mcast=%s",
> +                                           ni->u.socket.mcast);
> +            } else if (ni->u.socket.has_udp && ni->u.socket.has_fd) {
> +                info_str = g_strdup_printf("socket: udp=%s", ni->u.socket.udp);
> +            } else {
> +                g_assert(ni->u.socket.has_fd);
> +                int so_type = -1;
> +                int optlen = sizeof(so_type);
> +                int fd = atoi(ni->u.socket.fd);
> +
> +                getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&so_type,
> +                           (socklen_t *)&optlen);
> +                if (so_type == SOCK_STREAM) {
> +                    info_str = g_strdup_printf("socket: fd=%s",
> +                                               ni->u.socket.fd);
> +                } else {
> +                    if (ni->u.socket.has_mcast) {
> +                        /*
> +                         * This branch is unreachable, according to how it is in
> +                         * net/socket.c at this moment
> +                         */
> +                        info_str = g_strdup_printf("socket: fd=%s "
> +                                                   "(cloned mcast=%s)",
> +                                                   ni->u.socket.fd,
> +                                                   ni->u.socket.mcast);
> +                    } else {
> +                        SocketAddress *sa = socket_local_address(fd, NULL);
> +
> +                        info_str = g_strdup_printf("socket: fd=%s %s",
> +                            ni->u.socket.fd,
> +                            SocketAddressType_str(sa->type));
> +                        qapi_free_SocketAddress(sa);
> +                    }
> +                }
> +            }
> +            break;
> +        }
> +#ifdef CONFIG_VDE
> +        case NET_BACKEND_VDE: {
> +            info_str = g_strdup_printf("sock=%s,fd=%d",
> +                                       ni->u.vde.sock,
> +                                       net_vde_get_fd(nc));
> +            break;
> +        }
> +#endif /* CONFIG_VDE */
> +#ifdef CONFIG_NET_BRIDGE
> +        case NET_BACKEND_BRIDGE: {
> +            info_str = g_strdup_printf("helper=%s,br=%s",
> +                                       ni->u.bridge.helper,
> +                                       ni->u.bridge.br);
> +            break;
> +        }
> +#endif /* CONFIG_NET_BRIDGE */
> +#ifdef CONFIG_NETMAP
> +        case NET_BACKEND_NETMAP: {
> +            info_str = g_strdup_printf("netmap: ifname=%s",
> +                                       ni->u.netmap.ifname);
> +            break;
> +        }
> +#endif /* CONFIG_NETMAP */
> +#ifdef CONFIG_VHOST_NET_USER
> +        case NET_BACKEND_VHOST_USER: {
> +            info_str = g_strdup_printf("vhost-user%d to %s",
> +                                       nc->queue_index,
> +                                       ni->u.vhost_user.chardev);
> +            break;
> +        }
> +#endif /* CONFIG_VHOST_NET_USER */
> +#ifdef CONFIG_VHOST_NET_VDPA
> +        case NET_BACKEND_VHOST_VDPA: {
> +            info_str = g_strdup("vhost-vdpa");
> +            break;
> +        }
> +#endif /* CONFIG_VHOST_NET_VDPA */


This will introduce burdens for new netdevs or new attributes since 
people can easily forget to add the routine here.

I think at least we need introduce callbacks for this.

One more stupid question, instead of generating the string via hard 
codes, is there any method (dict?) to iterate all the key/values 
automatically?

Thanks
Alexey Kirillov Dec. 14, 2020, 5:14 p.m. UTC | #2
Hi!

07.12.2020, 08:52, "Jason Wang" <jasowang@redhat.com>:
> On 2020/11/9 上午7:59, Alexey Kirillov wrote:
>>  +#ifdef CONFIG_SLIRP
>>  + case NET_BACKEND_USER: {
>>  + size_t len = strchr(ni->u.user.net, '/') - ni->u.user.net;
>>  + char *net = g_strndup(ni->u.user.net, len);
>>  +
>>  + info_str = g_strdup_printf("net=%s,restrict=%s",
>>  + net,
>>  + ni->u.user.q_restrict ? "on" : "off");
>>  + g_free(net);
>>  + break;
>>  + }
>>  +#endif /* CONFIG_SLIRP */
>>  + case NET_BACKEND_TAP: {
>>  +#ifndef _WIN32
>>  + if (ni->u.tap.has_fds) {
>>  + char **fds = g_strsplit(ni->u.tap.fds, ":", -1);
>>  +
>>  + info_str = g_strdup_printf("fd=%s", fds[nc->queue_index]);
>>  + g_strfreev(fds);
>>  + } else if (ni->u.tap.has_helper) {
>>  + info_str = g_strdup_printf("helper=%s", ni->u.tap.helper);
>>  + } else {
>>  + info_str = g_strdup_printf("ifname=%s,script=%s,downscript=%s",
>>  + ni->u.tap.ifname,
>>  + nc->queue_index == 0 ? ni->u.tap.script : "no",
>>  + nc->queue_index == 0 ? ni->u.tap.downscript : "no");
>>  + }
>>  +#else
>>  + info_str = g_strdup_printf("tap: ifname=%s", ni->u.tap.ifname);
>>  +#endif /* _WIN32 */
>>  + break;
>>  + }
>>  +#ifdef CONFIG_L2TPV3
>>  + case NET_BACKEND_L2TPV3: {
>>  + info_str = g_strdup_printf("l2tpv3: connected");
>>  + break;
>>  + }
>>  +#endif /* CONFIG_L2TPV3 */
>>  + case NET_BACKEND_SOCKET: {
>>  + if (ni->u.socket.has_listen) {
>>  + if (ni->u.socket.has_fd) {
>>  + info_str = g_strdup_printf("socket: connection from %s",
>>  + ni->u.socket.listen);
>>  + } else {
>>  + info_str = g_strdup_printf("socket: wait from %s",
>>  + ni->u.socket.listen);
>>  + }
>>  + } else if (ni->u.socket.has_connect && ni->u.socket.has_fd) {
>>  + info_str = g_strdup_printf("socket: connect to %s",
>>  + ni->u.socket.connect);
>>  + } else if (ni->u.socket.has_mcast && ni->u.socket.has_fd) {
>>  + info_str = g_strdup_printf("socket: mcast=%s",
>>  + ni->u.socket.mcast);
>>  + } else if (ni->u.socket.has_udp && ni->u.socket.has_fd) {
>>  + info_str = g_strdup_printf("socket: udp=%s", ni->u.socket.udp);
>>  + } else {
>>  + g_assert(ni->u.socket.has_fd);
>>  + int so_type = -1;
>>  + int optlen = sizeof(so_type);
>>  + int fd = atoi(ni->u.socket.fd);
>>  +
>>  + getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&so_type,
>>  + (socklen_t *)&optlen);
>>  + if (so_type == SOCK_STREAM) {
>>  + info_str = g_strdup_printf("socket: fd=%s",
>>  + ni->u.socket.fd);
>>  + } else {
>>  + if (ni->u.socket.has_mcast) {
>>  + /*
>>  + * This branch is unreachable, according to how it is in
>>  + * net/socket.c at this moment
>>  + */
>>  + info_str = g_strdup_printf("socket: fd=%s "
>>  + "(cloned mcast=%s)",
>>  + ni->u.socket.fd,
>>  + ni->u.socket.mcast);
>>  + } else {
>>  + SocketAddress *sa = socket_local_address(fd, NULL);
>>  +
>>  + info_str = g_strdup_printf("socket: fd=%s %s",
>>  + ni->u.socket.fd,
>>  + SocketAddressType_str(sa->type));
>>  + qapi_free_SocketAddress(sa);
>>  + }
>>  + }
>>  + }
>>  + break;
>>  + }
>>  +#ifdef CONFIG_VDE
>>  + case NET_BACKEND_VDE: {
>>  + info_str = g_strdup_printf("sock=%s,fd=%d",
>>  + ni->u.vde.sock,
>>  + net_vde_get_fd(nc));
>>  + break;
>>  + }
>>  +#endif /* CONFIG_VDE */
>>  +#ifdef CONFIG_NET_BRIDGE
>>  + case NET_BACKEND_BRIDGE: {
>>  + info_str = g_strdup_printf("helper=%s,br=%s",
>>  + ni->u.bridge.helper,
>>  + ni->u.bridge.br);
>>  + break;
>>  + }
>>  +#endif /* CONFIG_NET_BRIDGE */
>>  +#ifdef CONFIG_NETMAP
>>  + case NET_BACKEND_NETMAP: {
>>  + info_str = g_strdup_printf("netmap: ifname=%s",
>>  + ni->u.netmap.ifname);
>>  + break;
>>  + }
>>  +#endif /* CONFIG_NETMAP */
>>  +#ifdef CONFIG_VHOST_NET_USER
>>  + case NET_BACKEND_VHOST_USER: {
>>  + info_str = g_strdup_printf("vhost-user%d to %s",
>>  + nc->queue_index,
>>  + ni->u.vhost_user.chardev);
>>  + break;
>>  + }
>>  +#endif /* CONFIG_VHOST_NET_USER */
>>  +#ifdef CONFIG_VHOST_NET_VDPA
>>  + case NET_BACKEND_VHOST_VDPA: {
>>  + info_str = g_strdup("vhost-vdpa");
>>  + break;
>>  + }
>>  +#endif /* CONFIG_VHOST_NET_VDPA */
>
> This will introduce burdens for new netdevs or new attributes since
> people can easily forget to add the routine here.
>
> I think at least we need introduce callbacks for this.

Thanks for pointing. I can't remember why exactly I chose to not do it.
So it's definitely better to split this chunk to several callbacks.
I'll do it in the next version of series.

> One more stupid question, instead of generating the string via hard
> codes, is there any method (dict?) to iterate all the key/values
> automatically?
>
> Thanks

Oh yes, that the point.
Now there are no common format for info_str.
This patch is aimed to keep old HMP command mostly untouched.
But if we can drop old format, all this mess can be generalized as JSON
lines replacing old info_str stuff.

What do you think about that?

Originally I wanted to completely drop old info_str and use
QAPI to store and provide information about netdevs (and NICs too).

Thanks!

-- 
Alexey Kirillov
Yandex.Cloud
Jason Wang Dec. 15, 2020, 4:16 a.m. UTC | #3
----- Original Message -----
> Hi!
> 
> 07.12.2020, 08:52, "Jason Wang" <jasowang@redhat.com>:
> > On 2020/11/9 上午7:59, Alexey Kirillov wrote:
> >>  +#ifdef CONFIG_SLIRP
> >>  + case NET_BACKEND_USER: {
> >>  + size_t len = strchr(ni->u.user.net, '/') - ni->u.user.net;
> >>  + char *net = g_strndup(ni->u.user.net, len);
> >>  +
> >>  + info_str = g_strdup_printf("net=%s,restrict=%s",
> >>  + net,
> >>  + ni->u.user.q_restrict ? "on" : "off");
> >>  + g_free(net);
> >>  + break;
> >>  + }
> >>  +#endif /* CONFIG_SLIRP */
> >>  + case NET_BACKEND_TAP: {
> >>  +#ifndef _WIN32
> >>  + if (ni->u.tap.has_fds) {
> >>  + char **fds = g_strsplit(ni->u.tap.fds, ":", -1);
> >>  +
> >>  + info_str = g_strdup_printf("fd=%s", fds[nc->queue_index]);
> >>  + g_strfreev(fds);
> >>  + } else if (ni->u.tap.has_helper) {
> >>  + info_str = g_strdup_printf("helper=%s", ni->u.tap.helper);
> >>  + } else {
> >>  + info_str = g_strdup_printf("ifname=%s,script=%s,downscript=%s",
> >>  + ni->u.tap.ifname,
> >>  + nc->queue_index == 0 ? ni->u.tap.script : "no",
> >>  + nc->queue_index == 0 ? ni->u.tap.downscript : "no");
> >>  + }
> >>  +#else
> >>  + info_str = g_strdup_printf("tap: ifname=%s", ni->u.tap.ifname);
> >>  +#endif /* _WIN32 */
> >>  + break;
> >>  + }
> >>  +#ifdef CONFIG_L2TPV3
> >>  + case NET_BACKEND_L2TPV3: {
> >>  + info_str = g_strdup_printf("l2tpv3: connected");
> >>  + break;
> >>  + }
> >>  +#endif /* CONFIG_L2TPV3 */
> >>  + case NET_BACKEND_SOCKET: {
> >>  + if (ni->u.socket.has_listen) {
> >>  + if (ni->u.socket.has_fd) {
> >>  + info_str = g_strdup_printf("socket: connection from %s",
> >>  + ni->u.socket.listen);
> >>  + } else {
> >>  + info_str = g_strdup_printf("socket: wait from %s",
> >>  + ni->u.socket.listen);
> >>  + }
> >>  + } else if (ni->u.socket.has_connect && ni->u.socket.has_fd) {
> >>  + info_str = g_strdup_printf("socket: connect to %s",
> >>  + ni->u.socket.connect);
> >>  + } else if (ni->u.socket.has_mcast && ni->u.socket.has_fd) {
> >>  + info_str = g_strdup_printf("socket: mcast=%s",
> >>  + ni->u.socket.mcast);
> >>  + } else if (ni->u.socket.has_udp && ni->u.socket.has_fd) {
> >>  + info_str = g_strdup_printf("socket: udp=%s", ni->u.socket.udp);
> >>  + } else {
> >>  + g_assert(ni->u.socket.has_fd);
> >>  + int so_type = -1;
> >>  + int optlen = sizeof(so_type);
> >>  + int fd = atoi(ni->u.socket.fd);
> >>  +
> >>  + getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&so_type,
> >>  + (socklen_t *)&optlen);
> >>  + if (so_type == SOCK_STREAM) {
> >>  + info_str = g_strdup_printf("socket: fd=%s",
> >>  + ni->u.socket.fd);
> >>  + } else {
> >>  + if (ni->u.socket.has_mcast) {
> >>  + /*
> >>  + * This branch is unreachable, according to how it is in
> >>  + * net/socket.c at this moment
> >>  + */
> >>  + info_str = g_strdup_printf("socket: fd=%s "
> >>  + "(cloned mcast=%s)",
> >>  + ni->u.socket.fd,
> >>  + ni->u.socket.mcast);
> >>  + } else {
> >>  + SocketAddress *sa = socket_local_address(fd, NULL);
> >>  +
> >>  + info_str = g_strdup_printf("socket: fd=%s %s",
> >>  + ni->u.socket.fd,
> >>  + SocketAddressType_str(sa->type));
> >>  + qapi_free_SocketAddress(sa);
> >>  + }
> >>  + }
> >>  + }
> >>  + break;
> >>  + }
> >>  +#ifdef CONFIG_VDE
> >>  + case NET_BACKEND_VDE: {
> >>  + info_str = g_strdup_printf("sock=%s,fd=%d",
> >>  + ni->u.vde.sock,
> >>  + net_vde_get_fd(nc));
> >>  + break;
> >>  + }
> >>  +#endif /* CONFIG_VDE */
> >>  +#ifdef CONFIG_NET_BRIDGE
> >>  + case NET_BACKEND_BRIDGE: {
> >>  + info_str = g_strdup_printf("helper=%s,br=%s",
> >>  + ni->u.bridge.helper,
> >>  + ni->u.bridge.br);
> >>  + break;
> >>  + }
> >>  +#endif /* CONFIG_NET_BRIDGE */
> >>  +#ifdef CONFIG_NETMAP
> >>  + case NET_BACKEND_NETMAP: {
> >>  + info_str = g_strdup_printf("netmap: ifname=%s",
> >>  + ni->u.netmap.ifname);
> >>  + break;
> >>  + }
> >>  +#endif /* CONFIG_NETMAP */
> >>  +#ifdef CONFIG_VHOST_NET_USER
> >>  + case NET_BACKEND_VHOST_USER: {
> >>  + info_str = g_strdup_printf("vhost-user%d to %s",
> >>  + nc->queue_index,
> >>  + ni->u.vhost_user.chardev);
> >>  + break;
> >>  + }
> >>  +#endif /* CONFIG_VHOST_NET_USER */
> >>  +#ifdef CONFIG_VHOST_NET_VDPA
> >>  + case NET_BACKEND_VHOST_VDPA: {
> >>  + info_str = g_strdup("vhost-vdpa");
> >>  + break;
> >>  + }
> >>  +#endif /* CONFIG_VHOST_NET_VDPA */
> >
> > This will introduce burdens for new netdevs or new attributes since
> > people can easily forget to add the routine here.
> >
> > I think at least we need introduce callbacks for this.
> 
> Thanks for pointing. I can't remember why exactly I chose to not do it.
> So it's definitely better to split this chunk to several callbacks.
> I'll do it in the next version of series.
> 
> > One more stupid question, instead of generating the string via hard
> > codes, is there any method (dict?) to iterate all the key/values
> > automatically?
> >
> > Thanks
> 
> Oh yes, that the point.
> Now there are no common format for info_str.
> This patch is aimed to keep old HMP command mostly untouched.
> But if we can drop old format, all this mess can be generalized as JSON
> lines replacing old info_str stuff.
> 
> What do you think about that?

I think it works and there's no need for sticking to the old HMP
format since it's not an ABI. And it would be better if you can
generate more human readable format from JSON.

Thanks


> 
> Originally I wanted to completely drop old info_str and use
> QAPI to store and provide information about netdevs (and NICs too).
> 
> Thanks!
> 
> --
> Alexey Kirillov
> Yandex.Cloud
> 
>
Markus Armbruster Dec. 15, 2020, 8:39 a.m. UTC | #4
Jason Wang <jasowang@redhat.com> writes:

[...]
> One more stupid question, instead of generating the string via hard
> codes, is there any method (dict?) to iterate all the key/values 
> automatically?

QAPI visitors.

The lazy way: use the QObject output visitor to convert the QAPI type
(here: NetdevInfo) to QObject, then qobject_to_json() to convert to
JSON text.

If you don't want JSON, replace qobject_to_json().  Perhaps you can
create something that's generally useful for HMP, not just "info
network".  I'd pick keyval_parse() syntax.

The detour through QObject creates and destroys a rather fat temporary
data structure.  Tolerable when the amount of data is small.  An output
visitor that directly creates the string is more efficient.  Takes a bit
more code, though.  I intend to post one for JSON, to reduce QMP's
malloc gluttony.
Jason Wang Dec. 16, 2020, 5:58 a.m. UTC | #5
----- Original Message -----
> Jason Wang <jasowang@redhat.com> writes:
> 
> [...]
> > One more stupid question, instead of generating the string via hard
> > codes, is there any method (dict?) to iterate all the key/values
> > automatically?
> 
> QAPI visitors.
> 
> The lazy way: use the QObject output visitor to convert the QAPI type
> (here: NetdevInfo) to QObject, then qobject_to_json() to convert to
> JSON text.
> 
> If you don't want JSON, replace qobject_to_json().  Perhaps you can
> create something that's generally useful for HMP, not just "info
> network".  I'd pick keyval_parse() syntax.
> 
> The detour through QObject creates and destroys a rather fat temporary
> data structure.  Tolerable when the amount of data is small.  An output
> visitor that directly creates the string is more efficient.  Takes a bit
> more code, though.  I intend to post one for JSON, to reduce QMP's
> malloc gluttony.
> 

Thanks a lot for the answer.

Alexey, let's try what Markus suggested here.
Alexey Kirillov Dec. 16, 2020, 9:16 a.m. UTC | #6
16.12.2020, 08:58, "Jason Wang" <jasowang@redhat.com>:
> ----- Original Message -----
>>  Jason Wang <jasowang@redhat.com> writes:
>>
>>  [...]
>>  > One more stupid question, instead of generating the string via hard
>>  > codes, is there any method (dict?) to iterate all the key/values
>>  > automatically?
>>
>>  QAPI visitors.
>>
>>  The lazy way: use the QObject output visitor to convert the QAPI type
>>  (here: NetdevInfo) to QObject, then qobject_to_json() to convert to
>>  JSON text.
>>
>>  If you don't want JSON, replace qobject_to_json(). Perhaps you can
>>  create something that's generally useful for HMP, not just "info
>>  network". I'd pick keyval_parse() syntax.
>>
>>  The detour through QObject creates and destroys a rather fat temporary
>>  data structure. Tolerable when the amount of data is small. An output
>>  visitor that directly creates the string is more efficient. Takes a bit
>>  more code, though. I intend to post one for JSON, to reduce QMP's
>>  malloc gluttony.
>
> Thanks a lot for the answer.
>
> Alexey, let's try what Markus suggested here.

Thanks for information.
I will do so and send a new version of the patchset.

-- 
Alexey Kirillov
Yandex.Cloud
diff mbox series

Patch

diff --git a/include/net/net.h b/include/net/net.h
index 16700f715e..ed77f444de 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -172,7 +172,8 @@  void qemu_check_nic_model(NICInfo *nd, const char *model);
 int qemu_find_nic_model(NICInfo *nd, const char * const *models,
                         const char *default_model);
 
-void print_net_client(Monitor *mon, NetClientState *nc);
+void print_net_client(Monitor *mon, NetClientState *nc,
+                      NetdevInfoList *ni_list);
 void hmp_info_network(Monitor *mon, const QDict *qdict);
 void net_socket_rs_init(SocketReadState *rs,
                         SocketReadStateFinalize *finalize,
diff --git a/net/clients.h b/net/clients.h
index 92f9b59aed..fdf257f641 100644
--- a/net/clients.h
+++ b/net/clients.h
@@ -51,6 +51,7 @@  int net_init_l2tpv3(const Netdev *netdev, const char *name,
 #ifdef CONFIG_VDE
 int net_init_vde(const Netdev *netdev, const char *name,
                  NetClientState *peer, Error **errp);
+int net_vde_get_fd(const NetClientState *nc);
 #endif
 
 #ifdef CONFIG_NETMAP
diff --git a/net/hub.c b/net/hub.c
index 1375738bf1..7815248650 100644
--- a/net/hub.c
+++ b/net/hub.c
@@ -221,7 +221,7 @@  NetClientState *net_hub_port_find(int hub_id)
 /**
  * Print hub configuration
  */
-void net_hub_info(Monitor *mon)
+void net_hub_info(Monitor *mon, NetdevInfoList *ni_list)
 {
     NetHub *hub;
     NetHubPort *port;
@@ -232,7 +232,7 @@  void net_hub_info(Monitor *mon)
             monitor_printf(mon, " \\ %s", port->nc.name);
             if (port->nc.peer) {
                 monitor_printf(mon, ": ");
-                print_net_client(mon, port->nc.peer);
+                print_net_client(mon, port->nc.peer, ni_list);
             } else {
                 monitor_printf(mon, "\n");
             }
diff --git a/net/hub.h b/net/hub.h
index ce45f7b399..2c9a384077 100644
--- a/net/hub.h
+++ b/net/hub.h
@@ -17,7 +17,7 @@ 
 
 NetClientState *net_hub_add_port(int hub_id, const char *name,
                                  NetClientState *hubpeer);
-void net_hub_info(Monitor *mon);
+void net_hub_info(Monitor *mon, NetdevInfoList *ninfo);
 void net_hub_check_clients(void);
 bool net_hub_flush(NetClientState *nc);
 
diff --git a/net/net.c b/net/net.c
index 551acadabc..4d3719434c 100644
--- a/net/net.c
+++ b/net/net.c
@@ -1178,14 +1178,182 @@  static void netfilter_print_info(Monitor *mon, NetFilterState *nf)
     monitor_printf(mon, "\n");
 }
 
-void print_net_client(Monitor *mon, NetClientState *nc)
+static NetdevInfo *get_netdev_info(NetdevInfoList *ni_list, char *name)
+{
+    NetdevInfo *ni;
+
+    while (ni_list) {
+        ni = ni_list->value;
+        if (g_str_equal(ni->id, name)) {
+            return ni;
+        }
+        ni_list = ni_list->next;
+    }
+
+    return NULL;
+}
+
+static char *generate_info_str(NetdevInfo *ni, NetClientState *nc)
+{
+    char *info_str;
+
+    /* Use legacy field info_str for NIC and hubports */
+    if ((nc->info->type == NET_CLIENT_DRIVER_NIC) ||
+        (nc->info->type == NET_CLIENT_DRIVER_HUBPORT)) {
+        return g_strdup(nc->info_str);
+    }
+
+    if (!ni) {
+        return g_malloc0(1);
+    }
+
+    switch (ni->type) {
+#ifdef CONFIG_SLIRP
+        case NET_BACKEND_USER: {
+            size_t len = strchr(ni->u.user.net, '/') - ni->u.user.net;
+            char *net = g_strndup(ni->u.user.net, len);
+
+            info_str = g_strdup_printf("net=%s,restrict=%s",
+                                       net,
+                                       ni->u.user.q_restrict ? "on" : "off");
+            g_free(net);
+            break;
+        }
+#endif /* CONFIG_SLIRP */
+        case NET_BACKEND_TAP: {
+#ifndef _WIN32
+            if (ni->u.tap.has_fds) {
+                char **fds = g_strsplit(ni->u.tap.fds, ":", -1);
+
+                info_str = g_strdup_printf("fd=%s", fds[nc->queue_index]);
+                g_strfreev(fds);
+            } else if (ni->u.tap.has_helper) {
+                info_str = g_strdup_printf("helper=%s", ni->u.tap.helper);
+            } else {
+                info_str = g_strdup_printf("ifname=%s,script=%s,downscript=%s",
+                    ni->u.tap.ifname,
+                    nc->queue_index == 0 ? ni->u.tap.script : "no",
+                    nc->queue_index == 0 ? ni->u.tap.downscript : "no");
+            }
+#else
+            info_str = g_strdup_printf("tap: ifname=%s", ni->u.tap.ifname);
+#endif /* _WIN32 */
+            break;
+        }
+#ifdef CONFIG_L2TPV3
+        case NET_BACKEND_L2TPV3: {
+            info_str = g_strdup_printf("l2tpv3: connected");
+            break;
+        }
+#endif /* CONFIG_L2TPV3 */
+        case NET_BACKEND_SOCKET: {
+            if (ni->u.socket.has_listen) {
+                if (ni->u.socket.has_fd) {
+                    info_str = g_strdup_printf("socket: connection from %s",
+                                               ni->u.socket.listen);
+                } else {
+                    info_str = g_strdup_printf("socket: wait from %s",
+                                               ni->u.socket.listen);
+                }
+            } else if (ni->u.socket.has_connect && ni->u.socket.has_fd) {
+                info_str = g_strdup_printf("socket: connect to %s",
+                                           ni->u.socket.connect);
+            } else if (ni->u.socket.has_mcast && ni->u.socket.has_fd) {
+                info_str = g_strdup_printf("socket: mcast=%s",
+                                           ni->u.socket.mcast);
+            } else if (ni->u.socket.has_udp && ni->u.socket.has_fd) {
+                info_str = g_strdup_printf("socket: udp=%s", ni->u.socket.udp);
+            } else {
+                g_assert(ni->u.socket.has_fd);
+                int so_type = -1;
+                int optlen = sizeof(so_type);
+                int fd = atoi(ni->u.socket.fd);
+
+                getsockopt(fd, SOL_SOCKET, SO_TYPE, (char *)&so_type,
+                           (socklen_t *)&optlen);
+                if (so_type == SOCK_STREAM) {
+                    info_str = g_strdup_printf("socket: fd=%s",
+                                               ni->u.socket.fd);
+                } else {
+                    if (ni->u.socket.has_mcast) {
+                        /*
+                         * This branch is unreachable, according to how it is in
+                         * net/socket.c at this moment
+                         */
+                        info_str = g_strdup_printf("socket: fd=%s "
+                                                   "(cloned mcast=%s)",
+                                                   ni->u.socket.fd,
+                                                   ni->u.socket.mcast);
+                    } else {
+                        SocketAddress *sa = socket_local_address(fd, NULL);
+
+                        info_str = g_strdup_printf("socket: fd=%s %s",
+                            ni->u.socket.fd,
+                            SocketAddressType_str(sa->type));
+                        qapi_free_SocketAddress(sa);
+                    }
+                }
+            }
+            break;
+        }
+#ifdef CONFIG_VDE
+        case NET_BACKEND_VDE: {
+            info_str = g_strdup_printf("sock=%s,fd=%d",
+                                       ni->u.vde.sock,
+                                       net_vde_get_fd(nc));
+            break;
+        }
+#endif /* CONFIG_VDE */
+#ifdef CONFIG_NET_BRIDGE
+        case NET_BACKEND_BRIDGE: {
+            info_str = g_strdup_printf("helper=%s,br=%s",
+                                       ni->u.bridge.helper,
+                                       ni->u.bridge.br);
+            break;
+        }
+#endif /* CONFIG_NET_BRIDGE */
+#ifdef CONFIG_NETMAP
+        case NET_BACKEND_NETMAP: {
+            info_str = g_strdup_printf("netmap: ifname=%s",
+                                       ni->u.netmap.ifname);
+            break;
+        }
+#endif /* CONFIG_NETMAP */
+#ifdef CONFIG_VHOST_NET_USER
+        case NET_BACKEND_VHOST_USER: {
+            info_str = g_strdup_printf("vhost-user%d to %s",
+                                       nc->queue_index,
+                                       ni->u.vhost_user.chardev);
+            break;
+        }
+#endif /* CONFIG_VHOST_NET_USER */
+#ifdef CONFIG_VHOST_NET_VDPA
+        case NET_BACKEND_VHOST_VDPA: {
+            info_str = g_strdup("vhost-vdpa");
+            break;
+        }
+#endif /* CONFIG_VHOST_NET_VDPA */
+        default: {
+            info_str = g_malloc0(1);
+            break;
+        }
+    }
+
+    return info_str;
+}
+
+void print_net_client(Monitor *mon, NetClientState *nc, NetdevInfoList *ni_list)
 {
     NetFilterState *nf;
+    NetdevInfo *ni = get_netdev_info(ni_list, nc->name);
+    char *info_str = generate_info_str(ni, nc);
 
     monitor_printf(mon, "%s: index=%d,type=%s,%s\n", nc->name,
                    nc->queue_index,
                    NetClientDriver_str(nc->info->type),
-                   nc->info_str);
+                   info_str);
+    g_free(info_str);
+
     if (!QTAILQ_EMPTY(&nc->filters)) {
         monitor_printf(mon, "filters:\n");
     }
@@ -1289,8 +1457,9 @@  void hmp_info_network(Monitor *mon, const QDict *qdict)
 {
     NetClientState *nc, *peer;
     NetClientDriver type;
+    NetdevInfoList *ni_list = qmp_query_netdev(NULL);
 
-    net_hub_info(mon);
+    net_hub_info(mon, ni_list);
 
     QTAILQ_FOREACH(nc, &net_clients, next) {
         peer = nc->peer;
@@ -1302,13 +1471,15 @@  void hmp_info_network(Monitor *mon, const QDict *qdict)
         }
 
         if (!peer || type == NET_CLIENT_DRIVER_NIC) {
-            print_net_client(mon, nc);
+            print_net_client(mon, nc, ni_list);
         } /* else it's a netdev connected to a NIC, printed with the NIC */
         if (peer && type == NET_CLIENT_DRIVER_NIC) {
             monitor_printf(mon, " \\ ");
-            print_net_client(mon, peer);
+            print_net_client(mon, peer, ni_list);
         }
     }
+
+    qapi_free_NetdevInfoList(ni_list);
 }
 
 void colo_notify_filters_event(int event, Error **errp)
diff --git a/net/vde.c b/net/vde.c
index 1af9175060..041eadbcf9 100644
--- a/net/vde.c
+++ b/net/vde.c
@@ -153,3 +153,13 @@  int net_init_vde(const Netdev *netdev, const char *name,
 
     return 0;
 }
+
+int net_vde_get_fd(const NetClientState *nc)
+{
+    VDEState *s;
+    assert(nc->info->type == NET_CLIENT_DRIVER_VDE);
+
+    s = DO_UPCAST(VDEState, nc, nc);
+
+    return vde_datafd(s->vde);
+}