diff mbox

[4/5] net/dump: Add dump option for netdev devices

Message ID 1435161381-31521-5-git-send-email-thuth@redhat.com
State New
Headers show

Commit Message

Thomas Huth June 24, 2015, 3:56 p.m. UTC
So far it is not possible to dump network traffic with the "-netdev"
option yet - this is only possible with the "-net" option and an
internal "hub".
This patch now fixes this ugliness by adding a proper, generic
dumpfile parameter to the "-netdev" option.

Signed-off-by: Thomas Huth <thuth@redhat.com>
---
 include/net/net.h |  1 +
 net/net.c         | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
 qapi-schema.json  | 12 ++++++++++--
 3 files changed, 59 insertions(+), 2 deletions(-)

Comments

Jason Wang June 26, 2015, 6:50 a.m. UTC | #1
On 06/24/2015 11:56 PM, Thomas Huth wrote:
> So far it is not possible to dump network traffic with the "-netdev"
> option yet - this is only possible with the "-net" option and an
> internal "hub".
> This patch now fixes this ugliness by adding a proper, generic
> dumpfile parameter to the "-netdev" option.
>
> Signed-off-by: Thomas Huth <thuth@redhat.com>
> ---
>  include/net/net.h |  1 +
>  net/net.c         | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
>  qapi-schema.json  | 12 ++++++++++--
>  3 files changed, 59 insertions(+), 2 deletions(-)
>
> diff --git a/include/net/net.h b/include/net/net.h
> index b265047..62abc98 100644
> --- a/include/net/net.h
> +++ b/include/net/net.h
> @@ -98,6 +98,7 @@ struct NetClientState {
>      NetClientDestructor *destructor;
>      unsigned int queue_index;
>      unsigned rxfilter_notify_enabled:1;
> +    unsigned netdev_dump_enabled:1;
>      NetDumpState nds;
>  };
>  
> diff --git a/net/net.c b/net/net.c
> index cc36c7b..8871b77 100644
> --- a/net/net.c
> +++ b/net/net.c
> @@ -568,6 +568,12 @@ ssize_t qemu_deliver_packet(NetClientState *sender,
>          return 0;
>      }
>  
> +    if (nc->netdev_dump_enabled) {
> +        net_dump_receive(nc, data, size);
> +    } else if (sender->netdev_dump_enabled) {
> +        net_dump_receive(sender, data, size);
> +    }
> +
>      if (flags & QEMU_NET_PACKET_FLAG_RAW && nc->info->receive_raw) {
>          ret = nc->info->receive_raw(nc, data, size);
>      } else {
> @@ -684,6 +690,12 @@ ssize_t qemu_deliver_packet_iov(NetClientState *sender,
>          return 0;
>      }
>  
> +    if (nc->netdev_dump_enabled) {
> +        net_dump_receive_iov(nc, iov, iovcnt);
> +    } else if (sender->netdev_dump_enabled) {
> +        net_dump_receive_iov(sender, iov, iovcnt);
> +    }
> +
>      if (nc->info->receive_iov) {
>          ret = nc->info->receive_iov(nc, iov, iovcnt);
>      } else {
> @@ -877,6 +889,36 @@ static int net_init_nic(const NetClientOptions *opts, const char *name,
>      return idx;
>  }
>  
> +static int netdev_init_dumpfile(const Netdev *netdev, const char *name,
> +                                Error **errp)
> +{
> +    NetClientState *nc;
> +    int dumplen = 65536;
> +    int rc;
> +
> +    if (netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_TAP
> +        && netdev->opts->tap->has_vhost && netdev->opts->tap->vhost) {
> +        error_setg(errp, "dumping does not work with vhost enabled");
> +        return -1;

And vhost user is also not suitable here. Looks we can check with
get_vhost_net() here.
> +    }
> +
> +    if (netdev->has_dumplen) {
> +        dumplen = netdev->dumplen;
> +    }
> +
> +    nc = qemu_find_netdev(name);
> +    if (!nc) {
> +        error_setg(errp, "failed to lookup netdev for dump");
> +        return -1;
> +    }

Need consider the case of multiqueue. In this case, more than one
NetClientState was used and needs more thought. Maybe we can start from
only support single queue.

> +
> +    rc = net_dump_state_init(nc, netdev->dumpfile, dumplen, errp);
> +    if (rc == 0) {
> +        nc->netdev_dump_enabled = true;
> +    }
> +
> +    return rc;
> +}
>  
>  static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])(
>      const NetClientOptions *opts,
> @@ -982,6 +1024,12 @@ static int net_client_init1(const void *object, int is_netdev, Error **errp)
>              }
>              return -1;
>          }
> +
> +        if (is_netdev && u.netdev->has_dumpfile) {
> +            if (netdev_init_dumpfile(u.netdev, name, errp)) {
> +                return -1;
> +            }
> +        }

Since dump is not a NetClient, we need close the fd when netdev is deleted.

>      }
>      return 0;
>  }
> diff --git a/qapi-schema.json b/qapi-schema.json
> index 106008c..79c3ed7 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -2528,14 +2528,22 @@
>  #
>  # @id: identifier for monitor commands.
>  #
> +# @dumpfile: #optional name of a file where network traffic should be logged
> +#            (since: 2.4)
> +#
> +# @dumplen: #optional maximum length of the network packets in the dump
> +#           (since: 2.4)
> +#
>  # @opts: device type specific properties
>  #
>  # Since 1.2
>  ##
>  { 'struct': 'Netdev',
>    'data': {
> -    'id':   'str',
> -    'opts': 'NetClientOptions' } }
> +    'id':        'str',
> +    '*dumpfile': 'str',
> +    '*dumplen':  'int32',
> +    'opts':      'NetClientOptions' } }
>  
>  ##
>  # @InetSocketAddress
Stefan Hajnoczi June 26, 2015, 9:44 a.m. UTC | #2
On Wed, Jun 24, 2015 at 05:56:20PM +0200, Thomas Huth wrote:
> diff --git a/net/net.c b/net/net.c
> index cc36c7b..8871b77 100644
> --- a/net/net.c
> +++ b/net/net.c
> @@ -568,6 +568,12 @@ ssize_t qemu_deliver_packet(NetClientState *sender,
>          return 0;
>      }
>  
> +    if (nc->netdev_dump_enabled) {
> +        net_dump_receive(nc, data, size);
> +    } else if (sender->netdev_dump_enabled) {
> +        net_dump_receive(sender, data, size);
> +    }

Why "else if"?  If two interfaces have packet capture enabled then both
should get the full traffic log:

  if (nc->netdev_dump_enabled) {
      net_dump_receive(nc, data, size);
  }
  if (sender->netdev_dump_enabled) {
      net_dump_receive(sender, data, size);
  }

Perhaps dumping should happen after ->receive() has returned size.  If
->receive() returns -1 the packet is discarded, and if it returns 0 the
packet is queued (not delivered yet).

If you dump unconditionally before ->receive() you will see queued
packets dumped multiple times (each time the queue gets flushed).
Thomas Huth June 29, 2015, 9:57 a.m. UTC | #3
On Fri, 26 Jun 2015 10:44:59 +0100
Stefan Hajnoczi <stefanha@gmail.com> wrote:

> On Wed, Jun 24, 2015 at 05:56:20PM +0200, Thomas Huth wrote:
> > diff --git a/net/net.c b/net/net.c
> > index cc36c7b..8871b77 100644
> > --- a/net/net.c
> > +++ b/net/net.c
> > @@ -568,6 +568,12 @@ ssize_t qemu_deliver_packet(NetClientState *sender,
> >          return 0;
> >      }
> >  
> > +    if (nc->netdev_dump_enabled) {
> > +        net_dump_receive(nc, data, size);
> > +    } else if (sender->netdev_dump_enabled) {
> > +        net_dump_receive(sender, data, size);
> > +    }
> 
> Why "else if"?  If two interfaces have packet capture enabled then both
> should get the full traffic log:
> 
>   if (nc->netdev_dump_enabled) {
>       net_dump_receive(nc, data, size);
>   }
>   if (sender->netdev_dump_enabled) {
>       net_dump_receive(sender, data, size);
>   }

I think I assumed that only the interfaces that are created with
"-netdev" can get a dump option. So it's either the receiver or the
sender that dumps. If both interfaces would have the dump flag set,
that would mean that two interfaces created with "-netdev" are talking
to each other - and this can not happen, can it?

> Perhaps dumping should happen after ->receive() has returned size.  If
> ->receive() returns -1 the packet is discarded, and if it returns 0 the
> packet is queued (not delivered yet).
> 
> If you dump unconditionally before ->receive() you will see queued
> packets dumped multiple times (each time the queue gets flushed).

Good point, thanks, I'll move the code accordingly.

 Thomas
Thomas Huth June 30, 2015, 10:37 a.m. UTC | #4
On Fri, 26 Jun 2015 10:44:59 +0100
Stefan Hajnoczi <stefanha@gmail.com> wrote:

> On Wed, Jun 24, 2015 at 05:56:20PM +0200, Thomas Huth wrote:
> > diff --git a/net/net.c b/net/net.c
> > index cc36c7b..8871b77 100644
> > --- a/net/net.c
> > +++ b/net/net.c
> > @@ -568,6 +568,12 @@ ssize_t qemu_deliver_packet(NetClientState *sender,
> >          return 0;
> >      }
> >  
> > +    if (nc->netdev_dump_enabled) {
> > +        net_dump_receive(nc, data, size);
> > +    } else if (sender->netdev_dump_enabled) {
> > +        net_dump_receive(sender, data, size);
> > +    }
...
> Perhaps dumping should happen after ->receive() has returned size.  If
> ->receive() returns -1 the packet is discarded, and if it returns 0 the
> packet is queued (not delivered yet).
> 
> If you dump unconditionally before ->receive() you will see queued
> packets dumped multiple times (each time the queue gets flushed).

I've now tried this, but then I suddenly get the packets in the wrong
order in the dump file (when using slirp networking), e.g.:

  1   0.000000     10.0.2.2 -> 255.255.255.255 DHCP 590 DHCP Offer
  2   0.000012      0.0.0.0 -> 255.255.255.255 DHCP 342 DHCP Discover

instead of:

  1   0.000000      0.0.0.0 -> 255.255.255.255 DHCP 342 DHCP Discover
  2   0.000035     10.0.2.2 -> 255.255.255.255 DHCP 590 DHCP Offer

Looks like with slirp, the answer is already sent before the initial
receive() function returns?

Any idea how to avoid that issue? If not, I think I'll simply keep the
dump hooks before calling the receive functions.

 Thomas
Stefan Hajnoczi June 30, 2015, 3:12 p.m. UTC | #5
On Mon, Jun 29, 2015 at 11:57:15AM +0200, Thomas Huth wrote:
> On Fri, 26 Jun 2015 10:44:59 +0100
> Stefan Hajnoczi <stefanha@gmail.com> wrote:
> 
> > On Wed, Jun 24, 2015 at 05:56:20PM +0200, Thomas Huth wrote:
> > > diff --git a/net/net.c b/net/net.c
> > > index cc36c7b..8871b77 100644
> > > --- a/net/net.c
> > > +++ b/net/net.c
> > > @@ -568,6 +568,12 @@ ssize_t qemu_deliver_packet(NetClientState *sender,
> > >          return 0;
> > >      }
> > >  
> > > +    if (nc->netdev_dump_enabled) {
> > > +        net_dump_receive(nc, data, size);
> > > +    } else if (sender->netdev_dump_enabled) {
> > > +        net_dump_receive(sender, data, size);
> > > +    }
> > 
> > Why "else if"?  If two interfaces have packet capture enabled then both
> > should get the full traffic log:
> > 
> >   if (nc->netdev_dump_enabled) {
> >       net_dump_receive(nc, data, size);
> >   }
> >   if (sender->netdev_dump_enabled) {
> >       net_dump_receive(sender, data, size);
> >   }
> 
> I think I assumed that only the interfaces that are created with
> "-netdev" can get a dump option. So it's either the receiver or the
> sender that dumps. If both interfaces would have the dump flag set,
> that would mean that two interfaces created with "-netdev" are talking
> to each other - and this can not happen, can it?

qemu_deliver_packet() doesn't know about -netdev vs -device.  It just
knows about NetClientState and peers.

Avoiding the 2nd memory load probably isn't worth baking the assumption
into this code.
Stefan Hajnoczi July 1, 2015, 8:36 a.m. UTC | #6
On Tue, Jun 30, 2015 at 12:37:46PM +0200, Thomas Huth wrote:
> On Fri, 26 Jun 2015 10:44:59 +0100
> Stefan Hajnoczi <stefanha@gmail.com> wrote:
> 
> > On Wed, Jun 24, 2015 at 05:56:20PM +0200, Thomas Huth wrote:
> > > diff --git a/net/net.c b/net/net.c
> > > index cc36c7b..8871b77 100644
> > > --- a/net/net.c
> > > +++ b/net/net.c
> > > @@ -568,6 +568,12 @@ ssize_t qemu_deliver_packet(NetClientState *sender,
> > >          return 0;
> > >      }
> > >  
> > > +    if (nc->netdev_dump_enabled) {
> > > +        net_dump_receive(nc, data, size);
> > > +    } else if (sender->netdev_dump_enabled) {
> > > +        net_dump_receive(sender, data, size);
> > > +    }
> ...
> > Perhaps dumping should happen after ->receive() has returned size.  If
> > ->receive() returns -1 the packet is discarded, and if it returns 0 the
> > packet is queued (not delivered yet).
> > 
> > If you dump unconditionally before ->receive() you will see queued
> > packets dumped multiple times (each time the queue gets flushed).
> 
> I've now tried this, but then I suddenly get the packets in the wrong
> order in the dump file (when using slirp networking), e.g.:
> 
>   1   0.000000     10.0.2.2 -> 255.255.255.255 DHCP 590 DHCP Offer
>   2   0.000012      0.0.0.0 -> 255.255.255.255 DHCP 342 DHCP Discover
> 
> instead of:
> 
>   1   0.000000      0.0.0.0 -> 255.255.255.255 DHCP 342 DHCP Discover
>   2   0.000035     10.0.2.2 -> 255.255.255.255 DHCP 590 DHCP Offer
> 
> Looks like with slirp, the answer is already sent before the initial
> receive() function returns?
> 
> Any idea how to avoid that issue? If not, I think I'll simply keep the
> dump hooks before calling the receive functions.

I see.  So either way the packet capture will be misleading.

Please leave add a comment to the code mentioning that either queued
packets are duplicated or slirp traffic is out-of-order.  That way we
won't forget why the code is the way it is.

Stefan
Markus Armbruster July 3, 2015, 11:28 a.m. UTC | #7
Thomas Huth <thuth@redhat.com> writes:

> So far it is not possible to dump network traffic with the "-netdev"
> option yet - this is only possible with the "-net" option and an
> internal "hub".
> This patch now fixes this ugliness by adding a proper, generic
> dumpfile parameter to the "-netdev" option.
>
> Signed-off-by: Thomas Huth <thuth@redhat.com>
> ---
>  include/net/net.h |  1 +
>  net/net.c         | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
>  qapi-schema.json  | 12 ++++++++++--
>  3 files changed, 59 insertions(+), 2 deletions(-)
>
> diff --git a/include/net/net.h b/include/net/net.h
> index b265047..62abc98 100644
> --- a/include/net/net.h
> +++ b/include/net/net.h
> @@ -98,6 +98,7 @@ struct NetClientState {
>      NetClientDestructor *destructor;
>      unsigned int queue_index;
>      unsigned rxfilter_notify_enabled:1;
> +    unsigned netdev_dump_enabled:1;
>      NetDumpState nds;
>  };
>  
> diff --git a/net/net.c b/net/net.c
> index cc36c7b..8871b77 100644
> --- a/net/net.c
> +++ b/net/net.c
> @@ -568,6 +568,12 @@ ssize_t qemu_deliver_packet(NetClientState *sender,
>          return 0;
>      }
>  
> +    if (nc->netdev_dump_enabled) {
> +        net_dump_receive(nc, data, size);
> +    } else if (sender->netdev_dump_enabled) {
> +        net_dump_receive(sender, data, size);
> +    }
> +
>      if (flags & QEMU_NET_PACKET_FLAG_RAW && nc->info->receive_raw) {
>          ret = nc->info->receive_raw(nc, data, size);
>      } else {
> @@ -684,6 +690,12 @@ ssize_t qemu_deliver_packet_iov(NetClientState *sender,
>          return 0;
>      }
>  
> +    if (nc->netdev_dump_enabled) {
> +        net_dump_receive_iov(nc, iov, iovcnt);
> +    } else if (sender->netdev_dump_enabled) {
> +        net_dump_receive_iov(sender, iov, iovcnt);
> +    }
> +
>      if (nc->info->receive_iov) {
>          ret = nc->info->receive_iov(nc, iov, iovcnt);
>      } else {
> @@ -877,6 +889,36 @@ static int net_init_nic(const NetClientOptions *opts, const char *name,
>      return idx;
>  }
>  
> +static int netdev_init_dumpfile(const Netdev *netdev, const char *name,
> +                                Error **errp)
> +{
> +    NetClientState *nc;
> +    int dumplen = 65536;
> +    int rc;
> +
> +    if (netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_TAP
> +        && netdev->opts->tap->has_vhost && netdev->opts->tap->vhost) {
> +        error_setg(errp, "dumping does not work with vhost enabled");
> +        return -1;
> +    }
> +
> +    if (netdev->has_dumplen) {
> +        dumplen = netdev->dumplen;

I keep reading "dumpling" for some reason... %-)

> +    }
> +
> +    nc = qemu_find_netdev(name);
> +    if (!nc) {
> +        error_setg(errp, "failed to lookup netdev for dump");
> +        return -1;

Can this happen?

Hmm, see below.

> +    }
> +
> +    rc = net_dump_state_init(nc, netdev->dumpfile, dumplen, errp);
> +    if (rc == 0) {
> +        nc->netdev_dump_enabled = true;
> +    }
> +
> +    return rc;
> +}
>  
>  static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])(
>      const NetClientOptions *opts,
> @@ -982,6 +1024,12 @@ static int net_client_init1(const void *object, int is_netdev, Error **errp)

       if (net_client_init_fun[opts->kind](opts, name, peer, errp) < 0) {
           /* FIXME drop when all init functions store an Error */
           if (errp && !*errp) {
               error_setg(errp, QERR_DEVICE_INIT_FAILED,
                          NetClientOptionsKind_lookup[opts->kind]);
>              }
>              return -1;
>          }
> +
> +        if (is_netdev && u.netdev->has_dumpfile) {
> +            if (netdev_init_dumpfile(u.netdev, name, errp)) {
> +                return -1;
> +            }
> +        }
>      }
>      return 0;
>  }

net_client_init_fun() creates a netdev with this name.  Since it doesn't
return it, we have to look it up with qemu_find_netdev().  So the answer
to "can this happen?" appears to be no.  assert(!rc)?

> diff --git a/qapi-schema.json b/qapi-schema.json
> index 106008c..79c3ed7 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -2528,14 +2528,22 @@
>  #
>  # @id: identifier for monitor commands.
>  #
> +# @dumpfile: #optional name of a file where network traffic should be logged
> +#            (since: 2.4)
> +#
> +# @dumplen: #optional maximum length of the network packets in the dump
> +#           (since: 2.4)
> +#
>  # @opts: device type specific properties
>  #
>  # Since 1.2
>  ##
>  { 'struct': 'Netdev',
>    'data': {
> -    'id':   'str',
> -    'opts': 'NetClientOptions' } }
> +    'id':        'str',
> +    '*dumpfile': 'str',
> +    '*dumplen':  'int32',
> +    'opts':      'NetClientOptions' } }
>  
>  ##
>  # @InetSocketAddress
Thomas Huth July 10, 2015, 6:27 p.m. UTC | #8
On Fri, 03 Jul 2015 13:28:45 +0200
Markus Armbruster <armbru@redhat.com> wrote:

> Thomas Huth <thuth@redhat.com> writes:
> 
> > So far it is not possible to dump network traffic with the "-netdev"
> > option yet - this is only possible with the "-net" option and an
> > internal "hub".
> > This patch now fixes this ugliness by adding a proper, generic
> > dumpfile parameter to the "-netdev" option.
> >
> > Signed-off-by: Thomas Huth <thuth@redhat.com>
> > ---
> >  include/net/net.h |  1 +
> >  net/net.c         | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
> >  qapi-schema.json  | 12 ++++++++++--
> >  3 files changed, 59 insertions(+), 2 deletions(-)
> >
> > diff --git a/include/net/net.h b/include/net/net.h
> > index b265047..62abc98 100644
> > --- a/include/net/net.h
> > +++ b/include/net/net.h
...
> > @@ -877,6 +889,36 @@ static int net_init_nic(const NetClientOptions *opts, const char *name,
> >      return idx;
> >  }
> >  
> > +static int netdev_init_dumpfile(const Netdev *netdev, const char *name,
> > +                                Error **errp)
> > +{
> > +    NetClientState *nc;
> > +    int dumplen = 65536;
> > +    int rc;
> > +
> > +    if (netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_TAP
> > +        && netdev->opts->tap->has_vhost && netdev->opts->tap->vhost) {
> > +        error_setg(errp, "dumping does not work with vhost enabled");
> > +        return -1;
> > +    }
> > +
> > +    if (netdev->has_dumplen) {
> > +        dumplen = netdev->dumplen;
> 
> I keep reading "dumpling" for some reason... %-)

Were you hungry while reading the patch? :-)

> > +    }
> > +
> > +    nc = qemu_find_netdev(name);
> > +    if (!nc) {
> > +        error_setg(errp, "failed to lookup netdev for dump");
> > +        return -1;
> 
> Can this happen?
> 
> Hmm, see below.
...
> net_client_init_fun() creates a netdev with this name.  Since it doesn't
> return it, we have to look it up with qemu_find_netdev().  So the answer
> to "can this happen?" appears to be no.  assert(!rc)?

I agree, an assert() should be enough here!

 Thomas
diff mbox

Patch

diff --git a/include/net/net.h b/include/net/net.h
index b265047..62abc98 100644
--- a/include/net/net.h
+++ b/include/net/net.h
@@ -98,6 +98,7 @@  struct NetClientState {
     NetClientDestructor *destructor;
     unsigned int queue_index;
     unsigned rxfilter_notify_enabled:1;
+    unsigned netdev_dump_enabled:1;
     NetDumpState nds;
 };
 
diff --git a/net/net.c b/net/net.c
index cc36c7b..8871b77 100644
--- a/net/net.c
+++ b/net/net.c
@@ -568,6 +568,12 @@  ssize_t qemu_deliver_packet(NetClientState *sender,
         return 0;
     }
 
+    if (nc->netdev_dump_enabled) {
+        net_dump_receive(nc, data, size);
+    } else if (sender->netdev_dump_enabled) {
+        net_dump_receive(sender, data, size);
+    }
+
     if (flags & QEMU_NET_PACKET_FLAG_RAW && nc->info->receive_raw) {
         ret = nc->info->receive_raw(nc, data, size);
     } else {
@@ -684,6 +690,12 @@  ssize_t qemu_deliver_packet_iov(NetClientState *sender,
         return 0;
     }
 
+    if (nc->netdev_dump_enabled) {
+        net_dump_receive_iov(nc, iov, iovcnt);
+    } else if (sender->netdev_dump_enabled) {
+        net_dump_receive_iov(sender, iov, iovcnt);
+    }
+
     if (nc->info->receive_iov) {
         ret = nc->info->receive_iov(nc, iov, iovcnt);
     } else {
@@ -877,6 +889,36 @@  static int net_init_nic(const NetClientOptions *opts, const char *name,
     return idx;
 }
 
+static int netdev_init_dumpfile(const Netdev *netdev, const char *name,
+                                Error **errp)
+{
+    NetClientState *nc;
+    int dumplen = 65536;
+    int rc;
+
+    if (netdev->opts->kind == NET_CLIENT_OPTIONS_KIND_TAP
+        && netdev->opts->tap->has_vhost && netdev->opts->tap->vhost) {
+        error_setg(errp, "dumping does not work with vhost enabled");
+        return -1;
+    }
+
+    if (netdev->has_dumplen) {
+        dumplen = netdev->dumplen;
+    }
+
+    nc = qemu_find_netdev(name);
+    if (!nc) {
+        error_setg(errp, "failed to lookup netdev for dump");
+        return -1;
+    }
+
+    rc = net_dump_state_init(nc, netdev->dumpfile, dumplen, errp);
+    if (rc == 0) {
+        nc->netdev_dump_enabled = true;
+    }
+
+    return rc;
+}
 
 static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])(
     const NetClientOptions *opts,
@@ -982,6 +1024,12 @@  static int net_client_init1(const void *object, int is_netdev, Error **errp)
             }
             return -1;
         }
+
+        if (is_netdev && u.netdev->has_dumpfile) {
+            if (netdev_init_dumpfile(u.netdev, name, errp)) {
+                return -1;
+            }
+        }
     }
     return 0;
 }
diff --git a/qapi-schema.json b/qapi-schema.json
index 106008c..79c3ed7 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -2528,14 +2528,22 @@ 
 #
 # @id: identifier for monitor commands.
 #
+# @dumpfile: #optional name of a file where network traffic should be logged
+#            (since: 2.4)
+#
+# @dumplen: #optional maximum length of the network packets in the dump
+#           (since: 2.4)
+#
 # @opts: device type specific properties
 #
 # Since 1.2
 ##
 { 'struct': 'Netdev',
   'data': {
-    'id':   'str',
-    'opts': 'NetClientOptions' } }
+    'id':        'str',
+    '*dumpfile': 'str',
+    '*dumplen':  'int32',
+    'opts':      'NetClientOptions' } }
 
 ##
 # @InetSocketAddress