Merge remote-tracking branch 'stefanha/net' into staging

* stefanha/net:
  remove unused QemuOpts parameter from net init functions
  convert net_init_bridge() to NetClientOptions
  convert net_init_tap() to NetClientOptions
  convert net_init_vde() to NetClientOptions
  convert net_init_socket() to NetClientOptions
  convert net_init_slirp() to NetClientOptions
  convert net_init_dump() to NetClientOptions
  convert net_init_nic() to NetClientOptions
  convert net_client_init() to OptsVisitor
  hw, net: "net_client_type" -> "NetClientOptionsKind" (qapi-generated)
  qapi schema: add Netdev types
  qapi schema: remove trailing whitespace
  qapi: introduce OptsVisitor
  expose QemuOpt and QemuOpts struct definitions to interested parties
  qapi: introduce "size" type
  qapi: generate C types for fixed-width integers
  qapi: add test case for deallocating traversal of incomplete structure
  qapi: fix error propagation
  MAINTAINERS: Replace net maintainer Mark McLoughlin with Stefan Hajnoczi
diff --git a/MAINTAINERS b/MAINTAINERS
index 30ed56d..2d219d2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -560,9 +560,10 @@
 
 Network device layer
 M: Anthony Liguori <aliguori@us.ibm.com>
-M: Mark McLoughlin <markmc@redhat.com>
+M: Stefan Hajnoczi <stefanha@linux.vnet.ibm.com>
 S: Maintained
 F: net/
+T: git git://github.com/stefanha/qemu.git net
 
 Network Block Device (NBD)
 M: Paolo Bonzini <pbonzini@redhat.com>
diff --git a/docs/qapi-code-gen.txt b/docs/qapi-code-gen.txt
index ad11767..cccb11e 100644
--- a/docs/qapi-code-gen.txt
+++ b/docs/qapi-code-gen.txt
@@ -220,6 +220,8 @@
     #endif
     mdroth@illuin:~/w/qemu2.git$
 
+(The actual structure of the visit_type_* functions is a bit more complex
+in order to propagate errors correctly and avoid leaking memory).
 
 === scripts/qapi-commands.py ===
 
diff --git a/error.c b/error.c
index a52b771..58f55a0 100644
--- a/error.c
+++ b/error.c
@@ -32,6 +32,7 @@
     if (errp == NULL) {
         return;
     }
+    assert(*errp == NULL);
 
     err = g_malloc0(sizeof(*err));
 
@@ -132,7 +133,7 @@
 
 void error_propagate(Error **dst_err, Error *local_err)
 {
-    if (dst_err) {
+    if (dst_err && !*dst_err) {
         *dst_err = local_err;
     } else if (local_err) {
         error_free(local_err);
diff --git a/error.h b/error.h
index 45ff6c1..3d9d96d 100644
--- a/error.h
+++ b/error.h
@@ -57,7 +57,7 @@
 /**
  * Propagate an error to an indirect pointer to an error.  This function will
  * always transfer ownership of the error reference and handles the case where
- * dst_err is NULL correctly.
+ * dst_err is NULL correctly.  Errors after the first are discarded.
  */
 void error_propagate(Error **dst_err, Error *local_err);
 
diff --git a/hw/cadence_gem.c b/hw/cadence_gem.c
index 87143ca..a0f51de 100644
--- a/hw/cadence_gem.c
+++ b/hw/cadence_gem.c
@@ -1161,7 +1161,7 @@
 }
 
 static NetClientInfo net_gem_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = gem_can_receive,
     .receive = gem_receive,
diff --git a/hw/dp8393x.c b/hw/dp8393x.c
index 017d074..756d630 100644
--- a/hw/dp8393x.c
+++ b/hw/dp8393x.c
@@ -872,7 +872,7 @@
 }
 
 static NetClientInfo net_dp83932_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = nic_can_receive,
     .receive = nic_receive,
diff --git a/hw/e1000.c b/hw/e1000.c
index 4573f13..ad24298 100644
--- a/hw/e1000.c
+++ b/hw/e1000.c
@@ -1206,7 +1206,7 @@
 }
 
 static NetClientInfo net_e1000_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = e1000_can_receive,
     .receive = e1000_receive,
diff --git a/hw/eepro100.c b/hw/eepro100.c
index 6279ae3..f343685 100644
--- a/hw/eepro100.c
+++ b/hw/eepro100.c
@@ -1845,7 +1845,7 @@
 }
 
 static NetClientInfo net_eepro100_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = nic_can_receive,
     .receive = nic_receive,
diff --git a/hw/etraxfs_eth.c b/hw/etraxfs_eth.c
index 16a0637..45fb40c 100644
--- a/hw/etraxfs_eth.c
+++ b/hw/etraxfs_eth.c
@@ -579,7 +579,7 @@
 }
 
 static NetClientInfo net_etraxfs_info = {
-	.type = NET_CLIENT_TYPE_NIC,
+	.type = NET_CLIENT_OPTIONS_KIND_NIC,
 	.size = sizeof(NICState),
 	.can_receive = eth_can_receive,
 	.receive = eth_receive,
diff --git a/hw/lan9118.c b/hw/lan9118.c
index 7b4fe87..40fb765 100644
--- a/hw/lan9118.c
+++ b/hw/lan9118.c
@@ -1310,7 +1310,7 @@
 }
 
 static NetClientInfo net_lan9118_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = lan9118_can_receive,
     .receive = lan9118_receive,
diff --git a/hw/lance.c b/hw/lance.c
index ce3d46c..91c0e16 100644
--- a/hw/lance.c
+++ b/hw/lance.c
@@ -93,7 +93,7 @@
 }
 
 static NetClientInfo net_lance_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = pcnet_can_receive,
     .receive = pcnet_receive,
diff --git a/hw/mcf_fec.c b/hw/mcf_fec.c
index ae37bef..4ab4ff5 100644
--- a/hw/mcf_fec.c
+++ b/hw/mcf_fec.c
@@ -450,7 +450,7 @@
 }
 
 static NetClientInfo net_mcf_fec_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = mcf_fec_can_receive,
     .receive = mcf_fec_receive,
diff --git a/hw/milkymist-minimac2.c b/hw/milkymist-minimac2.c
index 70bf336..3924b83 100644
--- a/hw/milkymist-minimac2.c
+++ b/hw/milkymist-minimac2.c
@@ -448,7 +448,7 @@
 }
 
 static NetClientInfo net_milkymist_minimac2_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = minimac2_can_rx,
     .receive = minimac2_rx,
diff --git a/hw/mipsnet.c b/hw/mipsnet.c
index 3107246..3385be7 100644
--- a/hw/mipsnet.c
+++ b/hw/mipsnet.c
@@ -217,7 +217,7 @@
 }
 
 static NetClientInfo net_mipsnet_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = mipsnet_can_receive,
     .receive = mipsnet_receive,
diff --git a/hw/musicpal.c b/hw/musicpal.c
index f14f20d..448897f 100644
--- a/hw/musicpal.c
+++ b/hw/musicpal.c
@@ -374,7 +374,7 @@
 }
 
 static NetClientInfo net_mv88w8618_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = eth_can_receive,
     .receive = eth_receive,
diff --git a/hw/ne2000-isa.c b/hw/ne2000-isa.c
index a4a783a..99ed965 100644
--- a/hw/ne2000-isa.c
+++ b/hw/ne2000-isa.c
@@ -44,7 +44,7 @@
 }
 
 static NetClientInfo net_ne2000_isa_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = ne2000_can_receive,
     .receive = ne2000_receive,
diff --git a/hw/ne2000.c b/hw/ne2000.c
index d02e60c..760ed29 100644
--- a/hw/ne2000.c
+++ b/hw/ne2000.c
@@ -711,7 +711,7 @@
 }
 
 static NetClientInfo net_ne2000_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = ne2000_can_receive,
     .receive = ne2000_receive,
diff --git a/hw/opencores_eth.c b/hw/opencores_eth.c
index 350f731..f4498d4 100644
--- a/hw/opencores_eth.c
+++ b/hw/opencores_eth.c
@@ -467,7 +467,7 @@
 }
 
 static NetClientInfo net_open_eth_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = open_eth_can_receive,
     .receive = open_eth_receive,
diff --git a/hw/pcnet-pci.c b/hw/pcnet-pci.c
index 34d73aa..931fedd 100644
--- a/hw/pcnet-pci.c
+++ b/hw/pcnet-pci.c
@@ -284,7 +284,7 @@
 }
 
 static NetClientInfo net_pci_pcnet_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = pcnet_can_receive,
     .receive = pcnet_receive,
diff --git a/hw/rtl8139.c b/hw/rtl8139.c
index 436b015..82fe235 100644
--- a/hw/rtl8139.c
+++ b/hw/rtl8139.c
@@ -3455,7 +3455,7 @@
 }
 
 static NetClientInfo net_rtl8139_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = rtl8139_can_receive,
     .receive = rtl8139_receive,
diff --git a/hw/smc91c111.c b/hw/smc91c111.c
index 1a5213f..451ede0 100644
--- a/hw/smc91c111.c
+++ b/hw/smc91c111.c
@@ -736,7 +736,7 @@
 }
 
 static NetClientInfo net_smc91c111_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = smc91c111_can_receive,
     .receive = smc91c111_receive,
diff --git a/hw/spapr_llan.c b/hw/spapr_llan.c
index d26fe9f..d54f933 100644
--- a/hw/spapr_llan.c
+++ b/hw/spapr_llan.c
@@ -176,7 +176,7 @@
 }
 
 static NetClientInfo net_spapr_vlan_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = spapr_vlan_can_receive,
     .receive = spapr_vlan_receive,
diff --git a/hw/stellaris_enet.c b/hw/stellaris_enet.c
index fbe99cb..b593cd0 100644
--- a/hw/stellaris_enet.c
+++ b/hw/stellaris_enet.c
@@ -393,7 +393,7 @@
 }
 
 static NetClientInfo net_stellaris_enet_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = stellaris_enet_can_receive,
     .receive = stellaris_enet_receive,
diff --git a/hw/usb/dev-network.c b/hw/usb/dev-network.c
index 5d2f098..f40c349 100644
--- a/hw/usb/dev-network.c
+++ b/hw/usb/dev-network.c
@@ -1313,7 +1313,7 @@
 }
 
 static NetClientInfo net_usbnet_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = usbnet_can_receive,
     .receive = usbnet_receive,
diff --git a/hw/vhost_net.c b/hw/vhost_net.c
index f672e9d..75f8211 100644
--- a/hw/vhost_net.c
+++ b/hw/vhost_net.c
@@ -83,7 +83,7 @@
 static int vhost_net_get_fd(VLANClientState *backend)
 {
     switch (backend->info->type) {
-    case NET_CLIENT_TYPE_TAP:
+    case NET_CLIENT_OPTIONS_KIND_TAP:
         return tap_get_fd(backend);
     default:
         fprintf(stderr, "vhost-net requires tap backend\n");
diff --git a/hw/virtio-net.c b/hw/virtio-net.c
index 533aa3d..df20499 100644
--- a/hw/virtio-net.c
+++ b/hw/virtio-net.c
@@ -108,7 +108,7 @@
     if (!n->nic->nc.peer) {
         return;
     }
-    if (n->nic->nc.peer->info->type != NET_CLIENT_TYPE_TAP) {
+    if (n->nic->nc.peer->info->type != NET_CLIENT_OPTIONS_KIND_TAP) {
         return;
     }
 
@@ -205,7 +205,7 @@
     if (!n->nic->nc.peer)
         return 0;
 
-    if (n->nic->nc.peer->info->type != NET_CLIENT_TYPE_TAP)
+    if (n->nic->nc.peer->info->type != NET_CLIENT_OPTIONS_KIND_TAP)
         return 0;
 
     n->has_vnet_hdr = tap_has_vnet_hdr(n->nic->nc.peer);
@@ -249,7 +249,7 @@
     }
 
     if (!n->nic->nc.peer ||
-        n->nic->nc.peer->info->type != NET_CLIENT_TYPE_TAP) {
+        n->nic->nc.peer->info->type != NET_CLIENT_OPTIONS_KIND_TAP) {
         return features;
     }
     if (!tap_get_vhost_net(n->nic->nc.peer)) {
@@ -288,7 +288,7 @@
                         (features >> VIRTIO_NET_F_GUEST_UFO)  & 1);
     }
     if (!n->nic->nc.peer ||
-        n->nic->nc.peer->info->type != NET_CLIENT_TYPE_TAP) {
+        n->nic->nc.peer->info->type != NET_CLIENT_OPTIONS_KIND_TAP) {
         return;
     }
     if (!tap_get_vhost_net(n->nic->nc.peer)) {
@@ -988,7 +988,7 @@
 }
 
 static NetClientInfo net_virtio_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = virtio_net_can_receive,
     .receive = virtio_net_receive,
diff --git a/hw/xen_nic.c b/hw/xen_nic.c
index 98db9bb..593a572 100644
--- a/hw/xen_nic.c
+++ b/hw/xen_nic.c
@@ -301,7 +301,7 @@
 /* ------------------------------------------------------------- */
 
 static NetClientInfo net_xen_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = net_rx_ok,
     .receive = net_rx_packet,
diff --git a/hw/xgmac.c b/hw/xgmac.c
index dd4bdc4..e539681 100644
--- a/hw/xgmac.c
+++ b/hw/xgmac.c
@@ -371,7 +371,7 @@
 }
 
 static NetClientInfo net_xgmac_enet_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = eth_can_rx,
     .receive = eth_rx,
diff --git a/hw/xilinx_axienet.c b/hw/xilinx_axienet.c
index 2e8d8a5..e948505 100644
--- a/hw/xilinx_axienet.c
+++ b/hw/xilinx_axienet.c
@@ -832,7 +832,7 @@
 }
 
 static NetClientInfo net_xilinx_enet_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = eth_can_rx,
     .receive = eth_rx,
diff --git a/hw/xilinx_ethlite.c b/hw/xilinx_ethlite.c
index affbb8b..9006322 100644
--- a/hw/xilinx_ethlite.c
+++ b/hw/xilinx_ethlite.c
@@ -202,7 +202,7 @@
 }
 
 static NetClientInfo net_xilinx_ethlite_info = {
-    .type = NET_CLIENT_TYPE_NIC,
+    .type = NET_CLIENT_OPTIONS_KIND_NIC,
     .size = sizeof(NICState),
     .can_receive = eth_can_rx,
     .receive = eth_rx,
diff --git a/net.c b/net.c
index abf0fd0..dbca77b 100644
--- a/net.c
+++ b/net.c
@@ -37,6 +37,9 @@
 #include "qmp-commands.h"
 #include "hw/qdev.h"
 #include "iov.h"
+#include "qapi-visit.h"
+#include "qapi/opts-visitor.h"
+#include "qapi/qapi-dealloc-visitor.h"
 
 /* Net bridge is currently not supported for W32. */
 #if !defined(_WIN32)
@@ -239,7 +242,7 @@
     VLANClientState *nc;
     NICState *nic;
 
-    assert(info->type == NET_CLIENT_TYPE_NIC);
+    assert(info->type == NET_CLIENT_OPTIONS_KIND_NIC);
     assert(info->size >= sizeof(NICState));
 
     nc = qemu_new_net_client(info, conf->vlan, conf->peer, model, name);
@@ -282,7 +285,7 @@
 void qemu_del_vlan_client(VLANClientState *vc)
 {
     /* If there is a peer NIC, delete and cleanup client, but do not free. */
-    if (!vc->vlan && vc->peer && vc->peer->info->type == NET_CLIENT_TYPE_NIC) {
+    if (!vc->vlan && vc->peer && vc->peer->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
         NICState *nic = DO_UPCAST(NICState, nc, vc->peer);
         if (nic->peer_deleted) {
             return;
@@ -298,7 +301,7 @@
     }
 
     /* If this is a peer NIC and peer has already been deleted, free it now. */
-    if (!vc->vlan && vc->peer && vc->info->type == NET_CLIENT_TYPE_NIC) {
+    if (!vc->vlan && vc->peer && vc->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
         NICState *nic = DO_UPCAST(NICState, nc, vc);
         if (nic->peer_deleted) {
             qemu_free_vlan_client(vc->peer);
@@ -341,14 +344,14 @@
     VLANState *vlan;
 
     QTAILQ_FOREACH(nc, &non_vlan_clients, next) {
-        if (nc->info->type == NET_CLIENT_TYPE_NIC) {
+        if (nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
             func(DO_UPCAST(NICState, nc, nc), opaque);
         }
     }
 
     QTAILQ_FOREACH(vlan, &vlans, next) {
         QTAILQ_FOREACH(nc, &vlan->clients, next) {
-            if (nc->info->type == NET_CLIENT_TYPE_NIC) {
+            if (nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
                 func(DO_UPCAST(NICState, nc, nc), opaque);
             }
         }
@@ -664,7 +667,7 @@
     VLANClientState *vc;
 
     QTAILQ_FOREACH(vc, &non_vlan_clients, next) {
-        if (vc->info->type == NET_CLIENT_TYPE_NIC)
+        if (vc->info->type == NET_CLIENT_OPTIONS_KIND_NIC)
             continue;
         if (!strcmp(vc->name, id)) {
             return vc;
@@ -745,11 +748,15 @@
     return fd;
 }
 
-static int net_init_nic(QemuOpts *opts, const char *name, VLANState *vlan)
+static int net_init_nic(const NetClientOptions *opts, const char *name,
+                        VLANState *vlan)
 {
     int idx;
     NICInfo *nd;
-    const char *netdev;
+    const NetLegacyNicOptions *nic;
+
+    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_NIC);
+    nic = opts->nic;
 
     idx = nic_get_free_idx();
     if (idx == -1 || nb_nics >= MAX_NICS) {
@@ -761,10 +768,10 @@
 
     memset(nd, 0, sizeof(*nd));
 
-    if ((netdev = qemu_opt_get(opts, "netdev"))) {
-        nd->netdev = qemu_find_netdev(netdev);
+    if (nic->has_netdev) {
+        nd->netdev = qemu_find_netdev(nic->netdev);
         if (!nd->netdev) {
-            error_report("netdev '%s' not found", netdev);
+            error_report("netdev '%s' not found", nic->netdev);
             return -1;
         }
     } else {
@@ -774,26 +781,28 @@
     if (name) {
         nd->name = g_strdup(name);
     }
-    if (qemu_opt_get(opts, "model")) {
-        nd->model = g_strdup(qemu_opt_get(opts, "model"));
+    if (nic->has_model) {
+        nd->model = g_strdup(nic->model);
     }
-    if (qemu_opt_get(opts, "addr")) {
-        nd->devaddr = g_strdup(qemu_opt_get(opts, "addr"));
+    if (nic->has_addr) {
+        nd->devaddr = g_strdup(nic->addr);
     }
 
-    if (qemu_opt_get(opts, "macaddr") &&
-        net_parse_macaddr(nd->macaddr.a, qemu_opt_get(opts, "macaddr")) < 0) {
+    if (nic->has_macaddr &&
+        net_parse_macaddr(nd->macaddr.a, nic->macaddr) < 0) {
         error_report("invalid syntax for ethernet address");
         return -1;
     }
     qemu_macaddr_default_if_unset(&nd->macaddr);
 
-    nd->nvectors = qemu_opt_get_number(opts, "vectors",
-                                       DEV_NVECTORS_UNSPECIFIED);
-    if (nd->nvectors != DEV_NVECTORS_UNSPECIFIED &&
-        (nd->nvectors < 0 || nd->nvectors > 0x7ffffff)) {
-        error_report("invalid # of vectors: %d", nd->nvectors);
-        return -1;
+    if (nic->has_vectors) {
+        if (nic->vectors > 0x7ffffff) {
+            error_report("invalid # of vectors: %"PRIu32, nic->vectors);
+            return -1;
+        }
+        nd->nvectors = nic->vectors;
+    } else {
+        nd->nvectors = DEV_NVECTORS_UNSPECIFIED;
     }
 
     nd->used = 1;
@@ -802,371 +811,128 @@
     return idx;
 }
 
-#define NET_COMMON_PARAMS_DESC                     \
-    {                                              \
-        .name = "type",                            \
-        .type = QEMU_OPT_STRING,                   \
-        .help = "net client type (nic, tap etc.)", \
-     }, {                                          \
-        .name = "vlan",                            \
-        .type = QEMU_OPT_NUMBER,                   \
-        .help = "vlan number",                     \
-     }, {                                          \
-        .name = "name",                            \
-        .type = QEMU_OPT_STRING,                   \
-        .help = "identifier for monitor commands", \
-     }
 
-typedef int (*net_client_init_func)(QemuOpts *opts,
-                                    const char *name,
-                                    VLANState *vlan);
-
-/* magic number, but compiler will warn if too small */
-#define NET_MAX_DESC 20
-
-static const struct {
-    const char *type;
-    net_client_init_func init;
-    QemuOptDesc desc[NET_MAX_DESC];
-} net_client_types[NET_CLIENT_TYPE_MAX] = {
-    [NET_CLIENT_TYPE_NONE] = {
-        .type = "none",
-        .desc = {
-            NET_COMMON_PARAMS_DESC,
-            { /* end of list */ }
-        },
-    },
-    [NET_CLIENT_TYPE_NIC] = {
-        .type = "nic",
-        .init = net_init_nic,
-        .desc = {
-            NET_COMMON_PARAMS_DESC,
-            {
-                .name = "netdev",
-                .type = QEMU_OPT_STRING,
-                .help = "id of -netdev to connect to",
-            },
-            {
-                .name = "macaddr",
-                .type = QEMU_OPT_STRING,
-                .help = "MAC address",
-            }, {
-                .name = "model",
-                .type = QEMU_OPT_STRING,
-                .help = "device model (e1000, rtl8139, virtio etc.)",
-            }, {
-                .name = "addr",
-                .type = QEMU_OPT_STRING,
-                .help = "PCI device address",
-            }, {
-                .name = "vectors",
-                .type = QEMU_OPT_NUMBER,
-                .help = "number of MSI-x vectors, 0 to disable MSI-X",
-            },
-            { /* end of list */ }
-        },
-    },
+static int (* const net_client_init_fun[NET_CLIENT_OPTIONS_KIND_MAX])(
+    const NetClientOptions *opts,
+    const char *name,
+    VLANState *vlan) = {
+        [NET_CLIENT_OPTIONS_KIND_NIC]    = net_init_nic,
 #ifdef CONFIG_SLIRP
-    [NET_CLIENT_TYPE_USER] = {
-        .type = "user",
-        .init = net_init_slirp,
-        .desc = {
-            NET_COMMON_PARAMS_DESC,
-            {
-                .name = "hostname",
-                .type = QEMU_OPT_STRING,
-                .help = "client hostname reported by the builtin DHCP server",
-            }, {
-                .name = "restrict",
-                .type = QEMU_OPT_STRING,
-                .help = "isolate the guest from the host (y|yes|n|no)",
-            }, {
-                .name = "ip",
-                .type = QEMU_OPT_STRING,
-                .help = "legacy parameter, use net= instead",
-            }, {
-                .name = "net",
-                .type = QEMU_OPT_STRING,
-                .help = "IP address and optional netmask",
-            }, {
-                .name = "host",
-                .type = QEMU_OPT_STRING,
-                .help = "guest-visible address of the host",
-            }, {
-                .name = "tftp",
-                .type = QEMU_OPT_STRING,
-                .help = "root directory of the built-in TFTP server",
-            }, {
-                .name = "bootfile",
-                .type = QEMU_OPT_STRING,
-                .help = "BOOTP filename, for use with tftp=",
-            }, {
-                .name = "dhcpstart",
-                .type = QEMU_OPT_STRING,
-                .help = "the first of the 16 IPs the built-in DHCP server can assign",
-            }, {
-                .name = "dns",
-                .type = QEMU_OPT_STRING,
-                .help = "guest-visible address of the virtual nameserver",
-            }, {
-                .name = "smb",
-                .type = QEMU_OPT_STRING,
-                .help = "root directory of the built-in SMB server",
-            }, {
-                .name = "smbserver",
-                .type = QEMU_OPT_STRING,
-                .help = "IP address of the built-in SMB server",
-            }, {
-                .name = "hostfwd",
-                .type = QEMU_OPT_STRING,
-                .help = "guest port number to forward incoming TCP or UDP connections",
-            }, {
-                .name = "guestfwd",
-                .type = QEMU_OPT_STRING,
-                .help = "IP address and port to forward guest TCP connections",
-            },
-            { /* end of list */ }
-        },
-    },
+        [NET_CLIENT_OPTIONS_KIND_USER]   = net_init_slirp,
 #endif
-    [NET_CLIENT_TYPE_TAP] = {
-        .type = "tap",
-        .init = net_init_tap,
-        .desc = {
-            NET_COMMON_PARAMS_DESC,
-            {
-                .name = "ifname",
-                .type = QEMU_OPT_STRING,
-                .help = "interface name",
-            },
-#ifndef _WIN32
-            {
-                .name = "fd",
-                .type = QEMU_OPT_STRING,
-                .help = "file descriptor of an already opened tap",
-            }, {
-                .name = "script",
-                .type = QEMU_OPT_STRING,
-                .help = "script to initialize the interface",
-            }, {
-                .name = "downscript",
-                .type = QEMU_OPT_STRING,
-                .help = "script to shut down the interface",
-            }, {
-#ifdef CONFIG_NET_BRIDGE
-                .name = "helper",
-                .type = QEMU_OPT_STRING,
-                .help = "command to execute to configure bridge",
-            }, {
-#endif
-                .name = "sndbuf",
-                .type = QEMU_OPT_SIZE,
-                .help = "send buffer limit"
-            }, {
-                .name = "vnet_hdr",
-                .type = QEMU_OPT_BOOL,
-                .help = "enable the IFF_VNET_HDR flag on the tap interface"
-            }, {
-                .name = "vhost",
-                .type = QEMU_OPT_BOOL,
-                .help = "enable vhost-net network accelerator",
-            }, {
-                .name = "vhostfd",
-                .type = QEMU_OPT_STRING,
-                .help = "file descriptor of an already opened vhost net device",
-            }, {
-                .name = "vhostforce",
-                .type = QEMU_OPT_BOOL,
-                .help = "force vhost on for non-MSIX virtio guests",
-        },
-#endif /* _WIN32 */
-            { /* end of list */ }
-        },
-    },
-    [NET_CLIENT_TYPE_SOCKET] = {
-        .type = "socket",
-        .init = net_init_socket,
-        .desc = {
-            NET_COMMON_PARAMS_DESC,
-            {
-                .name = "fd",
-                .type = QEMU_OPT_STRING,
-                .help = "file descriptor of an already opened socket",
-            }, {
-                .name = "listen",
-                .type = QEMU_OPT_STRING,
-                .help = "port number, and optional hostname, to listen on",
-            }, {
-                .name = "connect",
-                .type = QEMU_OPT_STRING,
-                .help = "port number, and optional hostname, to connect to",
-            }, {
-                .name = "mcast",
-                .type = QEMU_OPT_STRING,
-                .help = "UDP multicast address and port number",
-            }, {
-                .name = "localaddr",
-                .type = QEMU_OPT_STRING,
-                .help = "source address and port for multicast and udp packets",
-            }, {
-                .name = "udp",
-                .type = QEMU_OPT_STRING,
-                .help = "UDP unicast address and port number",
-            },
-            { /* end of list */ }
-        },
-    },
+        [NET_CLIENT_OPTIONS_KIND_TAP]    = net_init_tap,
+        [NET_CLIENT_OPTIONS_KIND_SOCKET] = net_init_socket,
 #ifdef CONFIG_VDE
-    [NET_CLIENT_TYPE_VDE] = {
-        .type = "vde",
-        .init = net_init_vde,
-        .desc = {
-            NET_COMMON_PARAMS_DESC,
-            {
-                .name = "sock",
-                .type = QEMU_OPT_STRING,
-                .help = "socket path",
-            }, {
-                .name = "port",
-                .type = QEMU_OPT_NUMBER,
-                .help = "port number",
-            }, {
-                .name = "group",
-                .type = QEMU_OPT_STRING,
-                .help = "group owner of socket",
-            }, {
-                .name = "mode",
-                .type = QEMU_OPT_NUMBER,
-                .help = "permissions for socket",
-            },
-            { /* end of list */ }
-        },
-    },
+        [NET_CLIENT_OPTIONS_KIND_VDE]    = net_init_vde,
 #endif
-    [NET_CLIENT_TYPE_DUMP] = {
-        .type = "dump",
-        .init = net_init_dump,
-        .desc = {
-            NET_COMMON_PARAMS_DESC,
-            {
-                .name = "len",
-                .type = QEMU_OPT_SIZE,
-                .help = "per-packet size limit (64k default)",
-            }, {
-                .name = "file",
-                .type = QEMU_OPT_STRING,
-                .help = "dump file path (default is qemu-vlan0.pcap)",
-            },
-            { /* end of list */ }
-        },
-    },
+        [NET_CLIENT_OPTIONS_KIND_DUMP]   = net_init_dump,
 #ifdef CONFIG_NET_BRIDGE
-    [NET_CLIENT_TYPE_BRIDGE] = {
-        .type = "bridge",
-        .init = net_init_bridge,
-        .desc = {
-            NET_COMMON_PARAMS_DESC,
-            {
-                .name = "br",
-                .type = QEMU_OPT_STRING,
-                .help = "bridge name",
-            }, {
-                .name = "helper",
-                .type = QEMU_OPT_STRING,
-                .help = "command to execute to configure bridge",
-            },
-            { /* end of list */ }
-        },
-    },
-#endif /* CONFIG_NET_BRIDGE */
+        [NET_CLIENT_OPTIONS_KIND_BRIDGE] = net_init_bridge,
+#endif
 };
 
-int net_client_init(QemuOpts *opts, int is_netdev, Error **errp)
-{
-    const char *name;
-    const char *type;
-    int i;
 
-    type = qemu_opt_get(opts, "type");
-    if (!type) {
-        error_set(errp, QERR_MISSING_PARAMETER, "type");
-        return -1;
-    }
+static int net_client_init1(const void *object, int is_netdev, Error **errp)
+{
+    union {
+        const Netdev    *netdev;
+        const NetLegacy *net;
+    } u;
+    const NetClientOptions *opts;
+    const char *name;
 
     if (is_netdev) {
-        if (strcmp(type, "tap") != 0 &&
-#ifdef CONFIG_NET_BRIDGE
-            strcmp(type, "bridge") != 0 &&
-#endif
+        u.netdev = object;
+        opts = u.netdev->opts;
+        name = u.netdev->id;
+
+        switch (opts->kind) {
 #ifdef CONFIG_SLIRP
-            strcmp(type, "user") != 0 &&
+        case NET_CLIENT_OPTIONS_KIND_USER:
 #endif
+        case NET_CLIENT_OPTIONS_KIND_TAP:
+        case NET_CLIENT_OPTIONS_KIND_SOCKET:
 #ifdef CONFIG_VDE
-            strcmp(type, "vde") != 0 &&
+        case NET_CLIENT_OPTIONS_KIND_VDE:
 #endif
-            strcmp(type, "socket") != 0) {
+#ifdef CONFIG_NET_BRIDGE
+        case NET_CLIENT_OPTIONS_KIND_BRIDGE:
+#endif
+            break;
+
+        default:
             error_set(errp, QERR_INVALID_PARAMETER_VALUE, "type",
                       "a netdev backend type");
             return -1;
         }
+    } else {
+        u.net = object;
+        opts = u.net->opts;
+        /* missing optional values have been initialized to "all bits zero" */
+        name = u.net->has_id ? u.net->id : u.net->name;
+    }
 
-        if (qemu_opt_get(opts, "vlan")) {
-            error_set(errp, QERR_INVALID_PARAMETER, "vlan");
-            return -1;
+    if (net_client_init_fun[opts->kind]) {
+        VLANState *vlan = NULL;
+
+        /* Do not add to a vlan if it's a -netdev or a nic with a netdev=
+         * parameter. */
+        if (!is_netdev &&
+            (opts->kind != NET_CLIENT_OPTIONS_KIND_NIC ||
+             !opts->nic->has_netdev)) {
+            vlan = qemu_find_vlan(u.net->has_vlan ? u.net->vlan : 0, true);
         }
-        if (qemu_opt_get(opts, "name")) {
-            error_set(errp, QERR_INVALID_PARAMETER, "name");
-            return -1;
-        }
-        if (!qemu_opts_id(opts)) {
-            error_set(errp, QERR_MISSING_PARAMETER, "id");
+
+        if (net_client_init_fun[opts->kind](opts, name, vlan) < 0) {
+            /* TODO push error reporting into init() methods */
+            error_set(errp, QERR_DEVICE_INIT_FAILED,
+                      NetClientOptionsKind_lookup[opts->kind]);
             return -1;
         }
     }
-
-    name = qemu_opts_id(opts);
-    if (!name) {
-        name = qemu_opt_get(opts, "name");
-    }
-
-    for (i = 0; i < NET_CLIENT_TYPE_MAX; i++) {
-        if (net_client_types[i].type != NULL &&
-            !strcmp(net_client_types[i].type, type)) {
-            Error *local_err = NULL;
-            VLANState *vlan = NULL;
-            int ret;
-
-            qemu_opts_validate(opts, &net_client_types[i].desc[0], &local_err);
-            if (error_is_set(&local_err)) {
-                error_propagate(errp, local_err);
-                return -1;
-            }
-
-            /* Do not add to a vlan if it's a -netdev or a nic with a
-             * netdev= parameter. */
-            if (!(is_netdev ||
-                  (strcmp(type, "nic") == 0 && qemu_opt_get(opts, "netdev")))) {
-                vlan = qemu_find_vlan(qemu_opt_get_number(opts, "vlan", 0), 1);
-            }
-
-            ret = 0;
-            if (net_client_types[i].init) {
-                ret = net_client_types[i].init(opts, name, vlan);
-                if (ret < 0) {
-                    /* TODO push error reporting into init() methods */
-                    error_set(errp, QERR_DEVICE_INIT_FAILED, type);
-                    return -1;
-                }
-            }
-            return ret;
-        }
-    }
-
-    error_set(errp, QERR_INVALID_PARAMETER_VALUE, "type",
-              "a network client type");
-    return -1;
+    return 0;
 }
 
+
+static void net_visit(Visitor *v, int is_netdev, void **object, Error **errp)
+{
+    if (is_netdev) {
+        visit_type_Netdev(v, (Netdev **)object, NULL, errp);
+    } else {
+        visit_type_NetLegacy(v, (NetLegacy **)object, NULL, errp);
+    }
+}
+
+
+int net_client_init(QemuOpts *opts, int is_netdev, Error **errp)
+{
+    void *object = NULL;
+    Error *err = NULL;
+    int ret = -1;
+
+    {
+        OptsVisitor *ov = opts_visitor_new(opts);
+
+        net_visit(opts_get_visitor(ov), is_netdev, &object, &err);
+        opts_visitor_cleanup(ov);
+    }
+
+    if (!err) {
+        ret = net_client_init1(object, is_netdev, &err);
+    }
+
+    if (object) {
+        QapiDeallocVisitor *dv = qapi_dealloc_visitor_new();
+
+        net_visit(qapi_dealloc_get_visitor(dv), is_netdev, &object, NULL);
+        qapi_dealloc_visitor_cleanup(dv);
+    }
+
+    error_propagate(errp, err);
+    return ret;
+}
+
+
 static int net_host_check_device(const char *device)
 {
     int i;
@@ -1286,14 +1052,14 @@
 static void print_net_client(Monitor *mon, VLANClientState *vc)
 {
     monitor_printf(mon, "%s: type=%s,%s\n", vc->name,
-                   net_client_types[vc->info->type].type, vc->info_str);
+                   NetClientOptionsKind_lookup[vc->info->type], vc->info_str);
 }
 
 void do_info_network(Monitor *mon)
 {
     VLANState *vlan;
     VLANClientState *vc, *peer;
-    net_client_type type;
+    NetClientOptionsKind type;
 
     QTAILQ_FOREACH(vlan, &vlans, next) {
         monitor_printf(mon, "VLAN %d devices:\n", vlan->id);
@@ -1307,11 +1073,11 @@
     QTAILQ_FOREACH(vc, &non_vlan_clients, next) {
         peer = vc->peer;
         type = vc->info->type;
-        if (!peer || type == NET_CLIENT_TYPE_NIC) {
+        if (!peer || type == NET_CLIENT_OPTIONS_KIND_NIC) {
             monitor_printf(mon, "  ");
             print_net_client(mon, vc);
         } /* else it's a netdev connected to a NIC, printed with the NIC */
-        if (peer && type == NET_CLIENT_TYPE_NIC) {
+        if (peer && type == NET_CLIENT_OPTIONS_KIND_NIC) {
             monitor_printf(mon, "   \\ ");
             print_net_client(mon, peer);
         }
@@ -1399,13 +1165,13 @@
 
         QTAILQ_FOREACH(vc, &vlan->clients, next) {
             switch (vc->info->type) {
-            case NET_CLIENT_TYPE_NIC:
+            case NET_CLIENT_OPTIONS_KIND_NIC:
                 has_nic = 1;
                 break;
-            case NET_CLIENT_TYPE_USER:
-            case NET_CLIENT_TYPE_TAP:
-            case NET_CLIENT_TYPE_SOCKET:
-            case NET_CLIENT_TYPE_VDE:
+            case NET_CLIENT_OPTIONS_KIND_USER:
+            case NET_CLIENT_OPTIONS_KIND_TAP:
+            case NET_CLIENT_OPTIONS_KIND_SOCKET:
+            case NET_CLIENT_OPTIONS_KIND_VDE:
                 has_host_dev = 1;
                 break;
             default: ;
@@ -1421,7 +1187,7 @@
     QTAILQ_FOREACH(vc, &non_vlan_clients, next) {
         if (!vc->peer) {
             fprintf(stderr, "Warning: %s %s has no peer\n",
-                    vc->info->type == NET_CLIENT_TYPE_NIC ? "nic" : "netdev",
+                    vc->info->type == NET_CLIENT_OPTIONS_KIND_NIC ? "nic" : "netdev",
                     vc->name);
         }
     }
diff --git a/net.h b/net.h
index bdc2a06..b0b8c7a 100644
--- a/net.h
+++ b/net.h
@@ -7,6 +7,7 @@
 #include "qemu-option.h"
 #include "net/queue.h"
 #include "vmstate.h"
+#include "qapi-types.h"
 
 struct MACAddr {
     uint8_t a[6];
@@ -29,19 +30,6 @@
 
 /* VLANs support */
 
-typedef enum {
-    NET_CLIENT_TYPE_NONE,
-    NET_CLIENT_TYPE_NIC,
-    NET_CLIENT_TYPE_USER,
-    NET_CLIENT_TYPE_TAP,
-    NET_CLIENT_TYPE_SOCKET,
-    NET_CLIENT_TYPE_VDE,
-    NET_CLIENT_TYPE_DUMP,
-    NET_CLIENT_TYPE_BRIDGE,
-
-    NET_CLIENT_TYPE_MAX
-} net_client_type;
-
 typedef void (NetPoll)(VLANClientState *, bool enable);
 typedef int (NetCanReceive)(VLANClientState *);
 typedef ssize_t (NetReceive)(VLANClientState *, const uint8_t *, size_t);
@@ -50,7 +38,7 @@
 typedef void (LinkStatusChanged)(VLANClientState *);
 
 typedef struct NetClientInfo {
-    net_client_type type;
+    NetClientOptionsKind type;
     size_t size;
     NetReceive *receive;
     NetReceive *receive_raw;
diff --git a/net/dump.c b/net/dump.c
index f835c51..b575430 100644
--- a/net/dump.c
+++ b/net/dump.c
@@ -93,7 +93,7 @@
 }
 
 static NetClientInfo net_dump_info = {
-    .type = NET_CLIENT_TYPE_DUMP,
+    .type = NET_CLIENT_OPTIONS_KIND_DUMP,
     .size = sizeof(DumpState),
     .receive = dump_receive,
     .cleanup = dump_cleanup,
@@ -144,21 +144,35 @@
     return 0;
 }
 
-int net_init_dump(QemuOpts *opts, const char *name, VLANState *vlan)
+int net_init_dump(const NetClientOptions *opts, const char *name,
+                  VLANState *vlan)
 {
     int len;
     const char *file;
     char def_file[128];
+    const NetdevDumpOptions *dump;
+
+    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_DUMP);
+    dump = opts->dump;
 
     assert(vlan);
 
-    file = qemu_opt_get(opts, "file");
-    if (!file) {
+    if (dump->has_file) {
+        file = dump->file;
+    } else {
         snprintf(def_file, sizeof(def_file), "qemu-vlan%d.pcap", vlan->id);
         file = def_file;
     }
 
-    len = qemu_opt_get_size(opts, "len", 65536);
+    if (dump->has_len) {
+        if (dump->len > INT_MAX) {
+            error_report("invalid length: %"PRIu64, dump->len);
+            return -1;
+        }
+        len = dump->len;
+    } else {
+        len = 65536;
+    }
 
     return net_dump_init(vlan, "dump", name, file, len);
 }
diff --git a/net/dump.h b/net/dump.h
index 2b5d9ba..0fa2dd7 100644
--- a/net/dump.h
+++ b/net/dump.h
@@ -25,8 +25,9 @@
 #define QEMU_NET_DUMP_H
 
 #include "net.h"
-#include "qemu-common.h"
+#include "qapi-types.h"
 
-int net_init_dump(QemuOpts *opts, const char *name, VLANState *vlan);
+int net_init_dump(const NetClientOptions *opts, const char *name,
+                  VLANState *vlan);
 
 #endif /* QEMU_NET_DUMP_H */
diff --git a/net/slirp.c b/net/slirp.c
index b82eab0..5c2e6b2 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -129,7 +129,7 @@
 }
 
 static NetClientInfo net_slirp_info = {
-    .type = NET_CLIENT_TYPE_USER,
+    .type = NET_CLIENT_OPTIONS_KIND_USER,
     .size = sizeof(SlirpState),
     .receive = net_slirp_receive,
     .cleanup = net_slirp_cleanup,
@@ -686,88 +686,46 @@
     }
 }
 
-static int net_init_slirp_configs(const char *name, const char *value, void *opaque)
+static void
+net_init_slirp_configs(const StringList *fwd, int flags)
 {
-    struct slirp_config_str *config;
+    while (fwd) {
+        struct slirp_config_str *config;
 
-    if (strcmp(name, "hostfwd") != 0 && strcmp(name, "guestfwd") != 0) {
-        return 0;
+        config = g_malloc0(sizeof(*config));
+        pstrcpy(config->str, sizeof(config->str), fwd->value->str);
+        config->flags = flags;
+        config->next = slirp_configs;
+        slirp_configs = config;
+
+        fwd = fwd->next;
     }
-
-    config = g_malloc0(sizeof(*config));
-
-    pstrcpy(config->str, sizeof(config->str), value);
-
-    if (!strcmp(name, "hostfwd")) {
-        config->flags = SLIRP_CFG_HOSTFWD;
-    }
-
-    config->next = slirp_configs;
-    slirp_configs = config;
-
-    return 0;
 }
 
-int net_init_slirp(QemuOpts *opts, const char *name, VLANState *vlan)
+int net_init_slirp(const NetClientOptions *opts, const char *name,
+                   VLANState *vlan)
 {
     struct slirp_config_str *config;
-    const char *vhost;
-    const char *vhostname;
-    const char *vdhcp_start;
-    const char *vnamesrv;
-    const char *tftp_export;
-    const char *bootfile;
-    const char *smb_export;
-    const char *vsmbsrv;
-    const char *restrict_opt;
-    char *vnet = NULL;
-    int restricted = 0;
+    char *vnet;
     int ret;
+    const NetdevUserOptions *user;
 
-    vhost       = qemu_opt_get(opts, "host");
-    vhostname   = qemu_opt_get(opts, "hostname");
-    vdhcp_start = qemu_opt_get(opts, "dhcpstart");
-    vnamesrv    = qemu_opt_get(opts, "dns");
-    tftp_export = qemu_opt_get(opts, "tftp");
-    bootfile    = qemu_opt_get(opts, "bootfile");
-    smb_export  = qemu_opt_get(opts, "smb");
-    vsmbsrv     = qemu_opt_get(opts, "smbserver");
+    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_USER);
+    user = opts->user;
 
-    restrict_opt = qemu_opt_get(opts, "restrict");
-    if (restrict_opt) {
-        if (!strcmp(restrict_opt, "on") ||
-            !strcmp(restrict_opt, "yes") || !strcmp(restrict_opt, "y")) {
-            restricted = 1;
-        } else if (strcmp(restrict_opt, "off") &&
-            strcmp(restrict_opt, "no") && strcmp(restrict_opt, "n")) {
-            error_report("invalid option: 'restrict=%s'", restrict_opt);
-            return -1;
-        }
-    }
+    vnet = user->has_net ? g_strdup(user->net) :
+           user->has_ip  ? g_strdup_printf("%s/24", user->ip) :
+           NULL;
 
-    if (qemu_opt_get(opts, "ip")) {
-        const char *ip = qemu_opt_get(opts, "ip");
-        int l = strlen(ip) + strlen("/24") + 1;
+    /* all optional fields are initialized to "all bits zero" */
 
-        vnet = g_malloc(l);
+    net_init_slirp_configs(user->hostfwd, SLIRP_CFG_HOSTFWD);
+    net_init_slirp_configs(user->guestfwd, 0);
 
-        /* emulate legacy ip= parameter */
-        pstrcpy(vnet, l, ip);
-        pstrcat(vnet, l, "/24");
-    }
-
-    if (qemu_opt_get(opts, "net")) {
-        if (vnet) {
-            g_free(vnet);
-        }
-        vnet = g_strdup(qemu_opt_get(opts, "net"));
-    }
-
-    qemu_opt_foreach(opts, net_init_slirp_configs, NULL, 0);
-
-    ret = net_slirp_init(vlan, "user", name, restricted, vnet, vhost,
-                         vhostname, tftp_export, bootfile, vdhcp_start,
-                         vnamesrv, smb_export, vsmbsrv);
+    ret = net_slirp_init(vlan, "user", name, user->restrict, vnet, user->host,
+                         user->hostname, user->tftp, user->bootfile,
+                         user->dhcpstart, user->dns, user->smb,
+                         user->smbserver);
 
     while (slirp_configs) {
         config = slirp_configs;
diff --git a/net/slirp.h b/net/slirp.h
index 53fe95d..e2c71ee 100644
--- a/net/slirp.h
+++ b/net/slirp.h
@@ -27,10 +27,12 @@
 #include "qemu-common.h"
 #include "qdict.h"
 #include "qemu-option.h"
+#include "qapi-types.h"
 
 #ifdef CONFIG_SLIRP
 
-int net_init_slirp(QemuOpts *opts, const char *name, VLANState *vlan);
+int net_init_slirp(const NetClientOptions *opts, const char *name,
+                   VLANState *vlan);
 
 void net_slirp_hostfwd_add(Monitor *mon, const QDict *qdict);
 void net_slirp_hostfwd_remove(Monitor *mon, const QDict *qdict);
diff --git a/net/socket.c b/net/socket.c
index fcd0a3c..600c287 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -239,7 +239,7 @@
 }
 
 static NetClientInfo net_dgram_socket_info = {
-    .type = NET_CLIENT_TYPE_SOCKET,
+    .type = NET_CLIENT_OPTIONS_KIND_SOCKET,
     .size = sizeof(NetSocketState),
     .receive = net_socket_receive_dgram,
     .cleanup = net_socket_cleanup,
@@ -317,7 +317,7 @@
 }
 
 static NetClientInfo net_socket_info = {
-    .type = NET_CLIENT_TYPE_SOCKET,
+    .type = NET_CLIENT_OPTIONS_KIND_SOCKET,
     .size = sizeof(NetSocketState),
     .receive = net_socket_receive,
     .cleanup = net_socket_cleanup,
@@ -586,100 +586,68 @@
     return 0;
 }
 
-int net_init_socket(QemuOpts *opts, const char *name, VLANState *vlan)
+int net_init_socket(const NetClientOptions *opts, const char *name,
+                    VLANState *vlan)
 {
-    if (qemu_opt_get(opts, "fd")) {
+    const NetdevSocketOptions *sock;
+
+    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_SOCKET);
+    sock = opts->socket;
+
+    if (sock->has_fd + sock->has_listen + sock->has_connect + sock->has_mcast +
+        sock->has_udp != 1) {
+        error_report("exactly one of fd=, listen=, connect=, mcast= or udp="
+                     " is required");
+        return -1;
+    }
+
+    if (sock->has_localaddr && !sock->has_mcast && !sock->has_udp) {
+        error_report("localaddr= is only valid with mcast= or udp=");
+        return -1;
+    }
+
+    if (sock->has_fd) {
         int fd;
 
-        if (qemu_opt_get(opts, "listen") ||
-            qemu_opt_get(opts, "connect") ||
-            qemu_opt_get(opts, "mcast") ||
-            qemu_opt_get(opts, "localaddr")) {
-            error_report("listen=, connect=, mcast= and localaddr= is invalid with fd=");
+        fd = net_handle_fd_param(cur_mon, sock->fd);
+        if (fd == -1 || !net_socket_fd_init(vlan, "socket", name, fd, 1)) {
             return -1;
         }
+        return 0;
+    }
 
-        fd = net_handle_fd_param(cur_mon, qemu_opt_get(opts, "fd"));
-        if (fd == -1) {
+    if (sock->has_listen) {
+        if (net_socket_listen_init(vlan, "socket", name, sock->listen) == -1) {
             return -1;
         }
+        return 0;
+    }
 
-        if (!net_socket_fd_init(vlan, "socket", name, fd, 1)) {
+    if (sock->has_connect) {
+        if (net_socket_connect_init(vlan, "socket", name, sock->connect) ==
+            -1) {
             return -1;
         }
-    } else if (qemu_opt_get(opts, "listen")) {
-        const char *listen;
+        return 0;
+    }
 
-        if (qemu_opt_get(opts, "fd") ||
-            qemu_opt_get(opts, "connect") ||
-            qemu_opt_get(opts, "mcast") ||
-            qemu_opt_get(opts, "localaddr")) {
-            error_report("fd=, connect=, mcast= and localaddr= is invalid with listen=");
+    if (sock->has_mcast) {
+        /* if sock->localaddr is missing, it has been initialized to "all bits
+         * zero" */
+        if (net_socket_mcast_init(vlan, "socket", name, sock->mcast,
+            sock->localaddr) == -1) {
             return -1;
         }
+        return 0;
+    }
 
-        listen = qemu_opt_get(opts, "listen");
-
-        if (net_socket_listen_init(vlan, "socket", name, listen) == -1) {
-            return -1;
-        }
-    } else if (qemu_opt_get(opts, "connect")) {
-        const char *connect;
-
-        if (qemu_opt_get(opts, "fd") ||
-            qemu_opt_get(opts, "listen") ||
-            qemu_opt_get(opts, "mcast") ||
-            qemu_opt_get(opts, "localaddr")) {
-            error_report("fd=, listen=, mcast= and localaddr= is invalid with connect=");
-            return -1;
-        }
-
-        connect = qemu_opt_get(opts, "connect");
-
-        if (net_socket_connect_init(vlan, "socket", name, connect) == -1) {
-            return -1;
-        }
-    } else if (qemu_opt_get(opts, "mcast")) {
-        const char *mcast, *localaddr;
-
-        if (qemu_opt_get(opts, "fd") ||
-            qemu_opt_get(opts, "connect") ||
-            qemu_opt_get(opts, "listen")) {
-            error_report("fd=, connect= and listen= is invalid with mcast=");
-            return -1;
-        }
-
-        mcast = qemu_opt_get(opts, "mcast");
-        localaddr = qemu_opt_get(opts, "localaddr");
-
-        if (net_socket_mcast_init(vlan, "socket", name, mcast, localaddr) == -1) {
-            return -1;
-        }
-    } else if (qemu_opt_get(opts, "udp")) {
-        const char *udp, *localaddr;
-
-        if (qemu_opt_get(opts, "fd") ||
-            qemu_opt_get(opts, "connect") ||
-            qemu_opt_get(opts, "listen") ||
-            qemu_opt_get(opts, "mcast")) {
-            error_report("fd=, connect=, listen="
-                         " and mcast= is invalid with udp=");
-            return -1;
-        }
-
-        udp = qemu_opt_get(opts, "udp");
-        localaddr = qemu_opt_get(opts, "localaddr");
-        if (localaddr == NULL) {
-                error_report("localaddr= is mandatory with udp=");
-                return -1;
-        }
-
-        if (net_socket_udp_init(vlan, "udp", name, udp, localaddr) == -1) {
-            return -1;
-        }
-    } else {
-        error_report("-socket requires fd=, listen=,"
-                     " connect=, mcast= or udp=");
+    assert(sock->has_udp);
+    if (!sock->has_localaddr) {
+        error_report("localaddr= is mandatory with udp=");
+        return -1;
+    }
+    if (net_socket_udp_init(vlan, "udp", name, sock->udp, sock->localaddr) ==
+        -1) {
         return -1;
     }
     return 0;
diff --git a/net/socket.h b/net/socket.h
index e1fe959..c4809ad 100644
--- a/net/socket.h
+++ b/net/socket.h
@@ -25,8 +25,9 @@
 #define QEMU_NET_SOCKET_H
 
 #include "net.h"
-#include "qemu-common.h"
+#include "qapi-types.h"
 
-int net_init_socket(QemuOpts *opts, const char *name, VLANState *vlan);
+int net_init_socket(const NetClientOptions *opts, const char *name,
+                    VLANState *vlan);
 
 #endif /* QEMU_NET_SOCKET_H */
diff --git a/net/tap-aix.c b/net/tap-aix.c
index e19aaba..f27c177 100644
--- a/net/tap-aix.c
+++ b/net/tap-aix.c
@@ -31,7 +31,7 @@
     return -1;
 }
 
-int tap_set_sndbuf(int fd, QemuOpts *opts)
+int tap_set_sndbuf(int fd, const NetdevTapOptions *tap)
 {
     return 0;
 }
diff --git a/net/tap-bsd.c b/net/tap-bsd.c
index 937a94b..a3b717d 100644
--- a/net/tap-bsd.c
+++ b/net/tap-bsd.c
@@ -117,7 +117,7 @@
     return fd;
 }
 
-int tap_set_sndbuf(int fd, QemuOpts *opts)
+int tap_set_sndbuf(int fd, const NetdevTapOptions *tap)
 {
     return 0;
 }
diff --git a/net/tap-haiku.c b/net/tap-haiku.c
index 91dda8e..34739d1 100644
--- a/net/tap-haiku.c
+++ b/net/tap-haiku.c
@@ -31,7 +31,7 @@
     return -1;
 }
 
-int tap_set_sndbuf(int fd, QemuOpts *opts)
+int tap_set_sndbuf(int fd, const NetdevTapOptions *tap)
 {
     return 0;
 }
diff --git a/net/tap-linux.c b/net/tap-linux.c
index 41d581b..c6521be 100644
--- a/net/tap-linux.c
+++ b/net/tap-linux.c
@@ -98,16 +98,19 @@
  */
 #define TAP_DEFAULT_SNDBUF 0
 
-int tap_set_sndbuf(int fd, QemuOpts *opts)
+int tap_set_sndbuf(int fd, const NetdevTapOptions *tap)
 {
     int sndbuf;
 
-    sndbuf = qemu_opt_get_size(opts, "sndbuf", TAP_DEFAULT_SNDBUF);
+    sndbuf = !tap->has_sndbuf       ? TAP_DEFAULT_SNDBUF :
+             tap->sndbuf > INT_MAX  ? INT_MAX :
+             tap->sndbuf;
+
     if (!sndbuf) {
         sndbuf = INT_MAX;
     }
 
-    if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1 && qemu_opt_get(opts, "sndbuf")) {
+    if (ioctl(fd, TUNSETSNDBUF, &sndbuf) == -1 && tap->has_sndbuf) {
         error_report("TUNSETSNDBUF ioctl failed: %s", strerror(errno));
         return -1;
     }
diff --git a/net/tap-solaris.c b/net/tap-solaris.c
index cf76463..5d6ac42 100644
--- a/net/tap-solaris.c
+++ b/net/tap-solaris.c
@@ -197,7 +197,7 @@
     return fd;
 }
 
-int tap_set_sndbuf(int fd, QemuOpts *opts)
+int tap_set_sndbuf(int fd, const NetdevTapOptions *tap)
 {
     return 0;
 }
diff --git a/net/tap-win32.c b/net/tap-win32.c
index a801a55..2328072 100644
--- a/net/tap-win32.c
+++ b/net/tap-win32.c
@@ -667,7 +667,7 @@
 }
 
 static NetClientInfo net_tap_win32_info = {
-    .type = NET_CLIENT_TYPE_TAP,
+    .type = NET_CLIENT_OPTIONS_KIND_TAP,
     .size = sizeof(TAPState),
     .receive = tap_receive,
     .cleanup = tap_cleanup,
@@ -699,18 +699,20 @@
     return 0;
 }
 
-int net_init_tap(QemuOpts *opts, const char *name, VLANState *vlan)
+int net_init_tap(const NetClientOptions *opts, const char *name,
+                 VLANState *vlan)
 {
-    const char *ifname;
+    const NetdevTapOptions *tap;
 
-    ifname = qemu_opt_get(opts, "ifname");
+    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_TAP);
+    tap = opts->tap;
 
-    if (!ifname) {
+    if (!tap->has_ifname) {
         error_report("tap: no interface name");
         return -1;
     }
 
-    if (tap_win32_init(vlan, "tap", name, ifname) == -1) {
+    if (tap_win32_init(vlan, "tap", name, tap->ifname) == -1) {
         return -1;
     }
 
diff --git a/net/tap.c b/net/tap.c
index 17e9135..7206227 100644
--- a/net/tap.c
+++ b/net/tap.c
@@ -218,7 +218,7 @@
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);
 
-    assert(nc->info->type == NET_CLIENT_TYPE_TAP);
+    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
 
     return s->has_ufo;
 }
@@ -227,7 +227,7 @@
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);
 
-    assert(nc->info->type == NET_CLIENT_TYPE_TAP);
+    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
 
     return !!s->host_vnet_hdr_len;
 }
@@ -236,7 +236,7 @@
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);
 
-    assert(nc->info->type == NET_CLIENT_TYPE_TAP);
+    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
 
     return tap_probe_vnet_hdr_len(s->fd, len);
 }
@@ -245,7 +245,7 @@
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);
 
-    assert(nc->info->type == NET_CLIENT_TYPE_TAP);
+    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
     assert(len == sizeof(struct virtio_net_hdr_mrg_rxbuf) ||
            len == sizeof(struct virtio_net_hdr));
 
@@ -259,7 +259,7 @@
 
     using_vnet_hdr = using_vnet_hdr != 0;
 
-    assert(nc->info->type == NET_CLIENT_TYPE_TAP);
+    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
     assert(!!s->host_vnet_hdr_len == using_vnet_hdr);
 
     s->using_vnet_hdr = using_vnet_hdr;
@@ -306,14 +306,14 @@
 int tap_get_fd(VLANClientState *nc)
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);
-    assert(nc->info->type == NET_CLIENT_TYPE_TAP);
+    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
     return s->fd;
 }
 
 /* fd support */
 
 static NetClientInfo net_tap_info = {
-    .type = NET_CLIENT_TYPE_TAP,
+    .type = NET_CLIENT_OPTIONS_KIND_TAP,
     .size = sizeof(TAPState),
     .receive = tap_receive,
     .receive_raw = tap_receive_raw,
@@ -513,20 +513,22 @@
     return -1;
 }
 
-int net_init_bridge(QemuOpts *opts, const char *name, VLANState *vlan)
+int net_init_bridge(const NetClientOptions *opts, const char *name,
+                    VLANState *vlan)
 {
+    const NetdevBridgeOptions *bridge;
+    const char *helper, *br;
+
     TAPState *s;
     int fd, vnet_hdr;
 
-    if (!qemu_opt_get(opts, "br")) {
-        qemu_opt_set(opts, "br", DEFAULT_BRIDGE_INTERFACE);
-    }
-    if (!qemu_opt_get(opts, "helper")) {
-        qemu_opt_set(opts, "helper", DEFAULT_BRIDGE_HELPER);
-    }
+    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_BRIDGE);
+    bridge = opts->bridge;
 
-    fd = net_bridge_run_helper(qemu_opt_get(opts, "helper"),
-                               qemu_opt_get(opts, "br"));
+    helper = bridge->has_helper ? bridge->helper : DEFAULT_BRIDGE_HELPER;
+    br     = bridge->has_br     ? bridge->br     : DEFAULT_BRIDGE_INTERFACE;
+
+    fd = net_bridge_run_helper(helper, br);
     if (fd == -1) {
         return -1;
     }
@@ -541,35 +543,38 @@
         return -1;
     }
 
-    snprintf(s->nc.info_str, sizeof(s->nc.info_str), "helper=%s,br=%s",
-             qemu_opt_get(opts, "helper"), qemu_opt_get(opts, "br"));
+    snprintf(s->nc.info_str, sizeof(s->nc.info_str), "helper=%s,br=%s", helper,
+             br);
 
     return 0;
 }
 
-static int net_tap_init(QemuOpts *opts, int *vnet_hdr)
+static int net_tap_init(const NetdevTapOptions *tap, int *vnet_hdr,
+                        const char *setup_script, char *ifname,
+                        size_t ifname_sz)
 {
     int fd, vnet_hdr_required;
-    char ifname[128] = {0,};
-    const char *setup_script;
 
-    if (qemu_opt_get(opts, "ifname")) {
-        pstrcpy(ifname, sizeof(ifname), qemu_opt_get(opts, "ifname"));
+    if (tap->has_ifname) {
+        pstrcpy(ifname, ifname_sz, tap->ifname);
+    } else {
+        assert(ifname_sz > 0);
+        ifname[0] = '\0';
     }
 
-    *vnet_hdr = qemu_opt_get_bool(opts, "vnet_hdr", 1);
-    if (qemu_opt_get(opts, "vnet_hdr")) {
+    if (tap->has_vnet_hdr) {
+        *vnet_hdr = tap->vnet_hdr;
         vnet_hdr_required = *vnet_hdr;
     } else {
+        *vnet_hdr = 1;
         vnet_hdr_required = 0;
     }
 
-    TFR(fd = tap_open(ifname, sizeof(ifname), vnet_hdr, vnet_hdr_required));
+    TFR(fd = tap_open(ifname, ifname_sz, vnet_hdr, vnet_hdr_required));
     if (fd < 0) {
         return -1;
     }
 
-    setup_script = qemu_opt_get(opts, "script");
     if (setup_script &&
         setup_script[0] != '\0' &&
         strcmp(setup_script, "no") != 0 &&
@@ -578,29 +583,34 @@
         return -1;
     }
 
-    qemu_opt_set(opts, "ifname", ifname);
-
     return fd;
 }
 
-int net_init_tap(QemuOpts *opts, const char *name, VLANState *vlan)
+int net_init_tap(const NetClientOptions *opts, const char *name,
+                 VLANState *vlan)
 {
-    TAPState *s;
+    const NetdevTapOptions *tap;
+
     int fd, vnet_hdr = 0;
     const char *model;
+    TAPState *s;
 
-    if (qemu_opt_get(opts, "fd")) {
-        if (qemu_opt_get(opts, "ifname") ||
-            qemu_opt_get(opts, "script") ||
-            qemu_opt_get(opts, "downscript") ||
-            qemu_opt_get(opts, "vnet_hdr") ||
-            qemu_opt_get(opts, "helper")) {
+    /* for the no-fd, no-helper case */
+    const char *script = NULL; /* suppress wrong "uninit'd use" gcc warning */
+    char ifname[128];
+
+    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_TAP);
+    tap = opts->tap;
+
+    if (tap->has_fd) {
+        if (tap->has_ifname || tap->has_script || tap->has_downscript ||
+            tap->has_vnet_hdr || tap->has_helper) {
             error_report("ifname=, script=, downscript=, vnet_hdr=, "
                          "and helper= are invalid with fd=");
             return -1;
         }
 
-        fd = net_handle_fd_param(cur_mon, qemu_opt_get(opts, "fd"));
+        fd = net_handle_fd_param(cur_mon, tap->fd);
         if (fd == -1) {
             return -1;
         }
@@ -611,18 +621,15 @@
 
         model = "tap";
 
-    } else if (qemu_opt_get(opts, "helper")) {
-        if (qemu_opt_get(opts, "ifname") ||
-            qemu_opt_get(opts, "script") ||
-            qemu_opt_get(opts, "downscript") ||
-            qemu_opt_get(opts, "vnet_hdr")) {
+    } else if (tap->has_helper) {
+        if (tap->has_ifname || tap->has_script || tap->has_downscript ||
+            tap->has_vnet_hdr) {
             error_report("ifname=, script=, downscript=, and vnet_hdr= "
                          "are invalid with helper=");
             return -1;
         }
 
-        fd = net_bridge_run_helper(qemu_opt_get(opts, "helper"),
-                                   DEFAULT_BRIDGE_INTERFACE);
+        fd = net_bridge_run_helper(tap->helper, DEFAULT_BRIDGE_INTERFACE);
         if (fd == -1) {
             return -1;
         }
@@ -634,15 +641,8 @@
         model = "bridge";
 
     } else {
-        if (!qemu_opt_get(opts, "script")) {
-            qemu_opt_set(opts, "script", DEFAULT_NETWORK_SCRIPT);
-        }
-
-        if (!qemu_opt_get(opts, "downscript")) {
-            qemu_opt_set(opts, "downscript", DEFAULT_NETWORK_DOWN_SCRIPT);
-        }
-
-        fd = net_tap_init(opts, &vnet_hdr);
+        script = tap->has_script ? tap->script : DEFAULT_NETWORK_SCRIPT;
+        fd = net_tap_init(tap, &vnet_hdr, script, ifname, sizeof ifname);
         if (fd == -1) {
             return -1;
         }
@@ -656,25 +656,24 @@
         return -1;
     }
 
-    if (tap_set_sndbuf(s->fd, opts) < 0) {
+    if (tap_set_sndbuf(s->fd, tap) < 0) {
         return -1;
     }
 
-    if (qemu_opt_get(opts, "fd")) {
+    if (tap->has_fd) {
         snprintf(s->nc.info_str, sizeof(s->nc.info_str), "fd=%d", fd);
-    } else if (qemu_opt_get(opts, "helper")) {
-        snprintf(s->nc.info_str, sizeof(s->nc.info_str),
-                 "helper=%s", qemu_opt_get(opts, "helper"));
+    } else if (tap->has_helper) {
+        snprintf(s->nc.info_str, sizeof(s->nc.info_str), "helper=%s",
+                 tap->helper);
     } else {
-        const char *ifname, *script, *downscript;
+        const char *downscript;
 
-        ifname     = qemu_opt_get(opts, "ifname");
-        script     = qemu_opt_get(opts, "script");
-        downscript = qemu_opt_get(opts, "downscript");
+        downscript = tap->has_downscript ? tap->downscript :
+                                           DEFAULT_NETWORK_DOWN_SCRIPT;
 
         snprintf(s->nc.info_str, sizeof(s->nc.info_str),
-                 "ifname=%s,script=%s,downscript=%s",
-                 ifname, script, downscript);
+                 "ifname=%s,script=%s,downscript=%s", ifname, script,
+                 downscript);
 
         if (strcmp(downscript, "no") != 0) {
             snprintf(s->down_script, sizeof(s->down_script), "%s", downscript);
@@ -682,25 +681,26 @@
         }
     }
 
-    if (qemu_opt_get_bool(opts, "vhost", !!qemu_opt_get(opts, "vhostfd") ||
-                          qemu_opt_get_bool(opts, "vhostforce", false))) {
-        int vhostfd, r;
-        bool force = qemu_opt_get_bool(opts, "vhostforce", false);
-        if (qemu_opt_get(opts, "vhostfd")) {
-            r = net_handle_fd_param(cur_mon, qemu_opt_get(opts, "vhostfd"));
-            if (r == -1) {
+    if (tap->has_vhost ? tap->vhost :
+        tap->has_vhostfd || (tap->has_vhostforce && tap->vhostforce)) {
+        int vhostfd;
+
+        if (tap->has_vhostfd) {
+            vhostfd = net_handle_fd_param(cur_mon, tap->vhostfd);
+            if (vhostfd == -1) {
                 return -1;
             }
-            vhostfd = r;
         } else {
             vhostfd = -1;
         }
-        s->vhost_net = vhost_net_init(&s->nc, vhostfd, force);
+
+        s->vhost_net = vhost_net_init(&s->nc, vhostfd,
+                                      tap->has_vhostforce && tap->vhostforce);
         if (!s->vhost_net) {
             error_report("vhost-net requested but could not be initialized");
             return -1;
         }
-    } else if (qemu_opt_get(opts, "vhostfd")) {
+    } else if (tap->has_vhostfd) {
         error_report("vhostfd= is not valid without vhost");
         return -1;
     }
@@ -711,6 +711,6 @@
 VHostNetState *tap_get_vhost_net(VLANClientState *nc)
 {
     TAPState *s = DO_UPCAST(TAPState, nc, nc);
-    assert(nc->info->type == NET_CLIENT_TYPE_TAP);
+    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
     return s->vhost_net;
 }
diff --git a/net/tap.h b/net/tap.h
index b2a9450..19dea58 100644
--- a/net/tap.h
+++ b/net/tap.h
@@ -27,12 +27,13 @@
 #define QEMU_NET_TAP_H
 
 #include "qemu-common.h"
-#include "qemu-option.h"
+#include "qapi-types.h"
 
 #define DEFAULT_NETWORK_SCRIPT "/etc/qemu-ifup"
 #define DEFAULT_NETWORK_DOWN_SCRIPT "/etc/qemu-ifdown"
 
-int net_init_tap(QemuOpts *opts, const char *name, VLANState *vlan);
+int net_init_tap(const NetClientOptions *opts, const char *name,
+                 VLANState *vlan);
 
 int tap_open(char *ifname, int ifname_size, int *vnet_hdr, int vnet_hdr_required);
 
@@ -45,7 +46,7 @@
 void tap_set_offload(VLANClientState *vc, int csum, int tso4, int tso6, int ecn, int ufo);
 void tap_set_vnet_hdr_len(VLANClientState *vc, int len);
 
-int tap_set_sndbuf(int fd, QemuOpts *opts);
+int tap_set_sndbuf(int fd, const NetdevTapOptions *tap);
 int tap_probe_vnet_hdr(int fd);
 int tap_probe_vnet_hdr_len(int fd, int len);
 int tap_probe_has_ufo(int fd);
@@ -57,6 +58,7 @@
 struct vhost_net;
 struct vhost_net *tap_get_vhost_net(VLANClientState *vc);
 
-int net_init_bridge(QemuOpts *opts, const char *name, VLANState *vlan);
+int net_init_bridge(const NetClientOptions *opts, const char *name,
+                    VLANState *vlan);
 
 #endif /* QEMU_NET_TAP_H */
diff --git a/net/vde.c b/net/vde.c
index 6b9d452..ee19f5c 100644
--- a/net/vde.c
+++ b/net/vde.c
@@ -69,7 +69,7 @@
 }
 
 static NetClientInfo net_vde_info = {
-    .type = NET_CLIENT_TYPE_VDE,
+    .type = NET_CLIENT_OPTIONS_KIND_VDE,
     .size = sizeof(VDEState),
     .receive = vde_receive,
     .cleanup = vde_cleanup,
@@ -110,19 +110,17 @@
     return 0;
 }
 
-int net_init_vde(QemuOpts *opts, const char *name, VLANState *vlan)
+int net_init_vde(const NetClientOptions *opts, const char *name,
+                 VLANState *vlan)
 {
-    const char *sock;
-    const char *group;
-    int port, mode;
+    const NetdevVdeOptions *vde;
 
-    sock  = qemu_opt_get(opts, "sock");
-    group = qemu_opt_get(opts, "group");
+    assert(opts->kind == NET_CLIENT_OPTIONS_KIND_VDE);
+    vde = opts->vde;
 
-    port = qemu_opt_get_number(opts, "port", 0);
-    mode = qemu_opt_get_number(opts, "mode", 0700);
-
-    if (net_vde_init(vlan, "vde", name, sock, port, group, mode) == -1) {
+    /* missing optional values have been initialized to "all bits zero" */
+    if (net_vde_init(vlan, "vde", name, vde->sock, vde->port, vde->group,
+                     vde->has_mode ? vde->mode : 0700) == -1) {
         return -1;
     }
 
diff --git a/net/vde.h b/net/vde.h
index 732e575..ad502ef 100644
--- a/net/vde.h
+++ b/net/vde.h
@@ -25,11 +25,12 @@
 #define QEMU_NET_VDE_H
 
 #include "qemu-common.h"
-#include "qemu-option.h"
+#include "qapi-types.h"
 
 #ifdef CONFIG_VDE
 
-int net_init_vde(QemuOpts *opts, const char *name, VLANState *vlan);
+int net_init_vde(const NetClientOptions *opts, const char *name,
+                 VLANState *vlan);
 
 #endif /* CONFIG_VDE */
 
diff --git a/qapi-schema.json b/qapi-schema.json
index a92adb1..bc55ed2 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -343,7 +343,7 @@
 # @CPU: the index of the virtual CPU
 #
 # @current: this only exists for backwards compatible and should be ignored
-# 
+#
 # @halted: true if the virtual CPU is in the halt state.  Halt usually refers
 #          to a processor specific low power mode.
 #
@@ -686,7 +686,7 @@
 # @SpiceInfo
 #
 # Information about the SPICE session.
-# 
+#
 # @enabled: true if the SPICE server is enabled, false otherwise
 #
 # @host: #optional The hostname the SPICE server is bound to.  This depends on
@@ -1297,7 +1297,7 @@
 ##
 { 'command': 'human-monitor-command',
   'data': {'command-line': 'str', '*cpu-index': 'int'},
-  'returns': 'str' } 
+  'returns': 'str' }
 
 ##
 # @migrate_cancel
@@ -1458,7 +1458,7 @@
 # @password: the new password
 #
 # @connected: #optional how to handle existing clients when changing the
-#                       password.  If nothing is specified, defaults to `keep' 
+#                       password.  If nothing is specified, defaults to `keep'
 #                       `fail' to fail the command if clients are connected
 #                       `disconnect' to disconnect existing clients
 #                       `keep' to maintain existing clients
@@ -1598,7 +1598,7 @@
 #          If the argument combination is invalid, InvalidParameterCombination
 #
 # Since: 1.1
-## 
+##
 { 'command': 'block_set_io_throttle',
   'data': { 'device': 'str', 'bps': 'int', 'bps_rd': 'int', 'bps_wr': 'int',
             'iops': 'int', 'iops_rd': 'int', 'iops_wr': 'int' } }
@@ -1872,6 +1872,284 @@
 { 'command': 'netdev_del', 'data': {'id': 'str'} }
 
 ##
+# @NetdevNoneOptions
+#
+# Use it alone to have zero network devices.
+#
+# Since 1.2
+##
+{ 'type': 'NetdevNoneOptions',
+  'data': { } }
+
+##
+# @NetLegacyNicOptions
+#
+# Create a new Network Interface Card.
+#
+# @netdev: #optional id of -netdev to connect to
+#
+# @macaddr: #optional MAC address
+#
+# @model: #optional device model (e1000, rtl8139, virtio etc.)
+#
+# @addr: #optional PCI device address
+#
+# @vectors: #optional number of MSI-x vectors, 0 to disable MSI-X
+#
+# Since 1.2
+##
+{ 'type': 'NetLegacyNicOptions',
+  'data': {
+    '*netdev':  'str',
+    '*macaddr': 'str',
+    '*model':   'str',
+    '*addr':    'str',
+    '*vectors': 'uint32' } }
+
+##
+# @String
+#
+# A fat type wrapping 'str', to be embedded in lists.
+#
+# Since 1.2
+##
+{ 'type': 'String',
+  'data': {
+    'str': 'str' } }
+
+##
+# @NetdevUserOptions
+#
+# Use the user mode network stack which requires no administrator privilege to
+# run.
+#
+# @hostname: #optional client hostname reported by the builtin DHCP server
+#
+# @restrict: #optional isolate the guest from the host
+#
+# @ip: #optional legacy parameter, use net= instead
+#
+# @net: #optional IP address and optional netmask
+#
+# @host: #optional guest-visible address of the host
+#
+# @tftp: #optional root directory of the built-in TFTP server
+#
+# @bootfile: #optional BOOTP filename, for use with tftp=
+#
+# @dhcpstart: #optional the first of the 16 IPs the built-in DHCP server can
+#             assign
+#
+# @dns: #optional guest-visible address of the virtual nameserver
+#
+# @smb: #optional root directory of the built-in SMB server
+#
+# @smbserver: #optional IP address of the built-in SMB server
+#
+# @hostfwd: #optional redirect incoming TCP or UDP host connections to guest
+#           endpoints
+#
+# @guestfwd: #optional forward guest TCP connections
+#
+# Since 1.2
+##
+{ 'type': 'NetdevUserOptions',
+  'data': {
+    '*hostname':  'str',
+    '*restrict':  'bool',
+    '*ip':        'str',
+    '*net':       'str',
+    '*host':      'str',
+    '*tftp':      'str',
+    '*bootfile':  'str',
+    '*dhcpstart': 'str',
+    '*dns':       'str',
+    '*smb':       'str',
+    '*smbserver': 'str',
+    '*hostfwd':   ['String'],
+    '*guestfwd':  ['String'] } }
+
+##
+# @NetdevTapOptions
+#
+# Connect the host TAP network interface name to the VLAN.
+#
+# @ifname: #optional interface name
+#
+# @fd: #optional file descriptor of an already opened tap
+#
+# @script: #optional script to initialize the interface
+#
+# @downscript: #optional script to shut down the interface
+#
+# @helper: #optional command to execute to configure bridge
+#
+# @sndbuf: #optional send buffer limit. Understands [TGMKkb] suffixes.
+#
+# @vnet_hdr: #optional enable the IFF_VNET_HDR flag on the tap interface
+#
+# @vhost: #optional enable vhost-net network accelerator
+#
+# @vhostfd: #optional file descriptor of an already opened vhost net device
+#
+# @vhostforce: #optional vhost on for non-MSIX virtio guests
+#
+# Since 1.2
+##
+{ 'type': 'NetdevTapOptions',
+  'data': {
+    '*ifname':     'str',
+    '*fd':         'str',
+    '*script':     'str',
+    '*downscript': 'str',
+    '*helper':     'str',
+    '*sndbuf':     'size',
+    '*vnet_hdr':   'bool',
+    '*vhost':      'bool',
+    '*vhostfd':    'str',
+    '*vhostforce': 'bool' } }
+
+##
+# @NetdevSocketOptions
+#
+# Connect the VLAN to a remote VLAN in another QEMU virtual machine using a TCP
+# socket connection.
+#
+# @fd: #optional file descriptor of an already opened socket
+#
+# @listen: #optional port number, and optional hostname, to listen on
+#
+# @connect: #optional port number, and optional hostname, to connect to
+#
+# @mcast: #optional UDP multicast address and port number
+#
+# @localaddr: #optional source address and port for multicast and udp packets
+#
+# @udp: #optional UDP unicast address and port number
+#
+# Since 1.2
+##
+{ 'type': 'NetdevSocketOptions',
+  'data': {
+    '*fd':        'str',
+    '*listen':    'str',
+    '*connect':   'str',
+    '*mcast':     'str',
+    '*localaddr': 'str',
+    '*udp':       'str' } }
+
+##
+# @NetdevVdeOptions
+#
+# Connect the VLAN to a vde switch running on the host.
+#
+# @sock: #optional socket path
+#
+# @port: #optional port number
+#
+# @group: #optional group owner of socket
+#
+# @mode: #optional permissions for socket
+#
+# Since 1.2
+##
+{ 'type': 'NetdevVdeOptions',
+  'data': {
+    '*sock':  'str',
+    '*port':  'uint16',
+    '*group': 'str',
+    '*mode':  'uint16' } }
+
+##
+# @NetdevDumpOptions
+#
+# Dump VLAN network traffic to a file.
+#
+# @len: #optional per-packet size limit (64k default). Understands [TGMKkb]
+# suffixes.
+#
+# @file: #optional dump file path (default is qemu-vlan0.pcap)
+#
+# Since 1.2
+##
+{ 'type': 'NetdevDumpOptions',
+  'data': {
+    '*len':  'size',
+    '*file': 'str' } }
+
+##
+# @NetdevBridgeOptions
+#
+# Connect a host TAP network interface to a host bridge device.
+#
+# @br: #optional bridge name
+#
+# @helper: #optional command to execute to configure bridge
+#
+# Since 1.2
+##
+{ 'type': 'NetdevBridgeOptions',
+  'data': {
+    '*br':     'str',
+    '*helper': 'str' } }
+
+##
+# @NetClientOptions
+#
+# A discriminated record of network device traits.
+#
+# Since 1.2
+##
+{ 'union': 'NetClientOptions',
+  'data': {
+    'none':   'NetdevNoneOptions',
+    'nic':    'NetLegacyNicOptions',
+    'user':   'NetdevUserOptions',
+    'tap':    'NetdevTapOptions',
+    'socket': 'NetdevSocketOptions',
+    'vde':    'NetdevVdeOptions',
+    'dump':   'NetdevDumpOptions',
+    'bridge': 'NetdevBridgeOptions' } }
+
+##
+# @NetLegacy
+#
+# Captures the configuration of a network device; legacy.
+#
+# @vlan: #optional vlan number
+#
+# @id: #optional identifier for monitor commands
+#
+# @name: #optional identifier for monitor commands, ignored if @id is present
+#
+# @opts: device type specific properties (legacy)
+#
+# Since 1.2
+##
+{ 'type': 'NetLegacy',
+  'data': {
+    '*vlan': 'int32',
+    '*id':   'str',
+    '*name': 'str',
+    'opts':  'NetClientOptions' } }
+
+##
+# @Netdev
+#
+# Captures the configuration of a network device.
+#
+# @id: identifier for monitor commands.
+#
+# @opts: device type specific properties
+#
+# Since 1.2
+##
+{ 'type': 'Netdev',
+  'data': {
+    'id':   'str',
+    'opts': 'NetClientOptions' } }
+
+##
 # @getfd:
 #
 # Receive a file descriptor via SCM rights and assign it a name
diff --git a/qapi/Makefile.objs b/qapi/Makefile.objs
index d0b0c16..5f5846e 100644
--- a/qapi/Makefile.objs
+++ b/qapi/Makefile.objs
@@ -1,3 +1,3 @@
 qapi-obj-y = qapi-visit-core.o qapi-dealloc-visitor.o qmp-input-visitor.o
 qapi-obj-y += qmp-output-visitor.o qmp-registry.o qmp-dispatch.o
-qapi-obj-y += string-input-visitor.o string-output-visitor.o
+qapi-obj-y += string-input-visitor.o string-output-visitor.o opts-visitor.o
diff --git a/qapi/opts-visitor.c b/qapi/opts-visitor.c
new file mode 100644
index 0000000..a59d306
--- /dev/null
+++ b/qapi/opts-visitor.c
@@ -0,0 +1,427 @@
+/*
+ * Options Visitor
+ *
+ * Copyright Red Hat, Inc. 2012
+ *
+ * Author: Laszlo Ersek <lersek@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+
+#include "opts-visitor.h"
+#include "qemu-queue.h"
+#include "qemu-option-internal.h"
+#include "qapi-visit-impl.h"
+
+
+struct OptsVisitor
+{
+    Visitor visitor;
+
+    /* Ownership remains with opts_visitor_new()'s caller. */
+    const QemuOpts *opts_root;
+
+    unsigned depth;
+
+    /* Non-null iff depth is positive. Each key is a QemuOpt name. Each value
+     * is a non-empty GQueue, enumerating all QemuOpt occurrences with that
+     * name. */
+    GHashTable *unprocessed_opts;
+
+    /* The list currently being traversed with opts_start_list() /
+     * opts_next_list(). The list must have a struct element type in the
+     * schema, with a single mandatory scalar member. */
+    GQueue *repeated_opts;
+    bool repeated_opts_first;
+
+    /* If "opts_root->id" is set, reinstantiate it as a fake QemuOpt for
+     * uniformity. Only its "name" and "str" fields are set. "fake_id_opt" does
+     * not survive or escape the OptsVisitor object.
+     */
+    QemuOpt *fake_id_opt;
+};
+
+
+static void
+destroy_list(gpointer list)
+{
+  g_queue_free(list);
+}
+
+
+static void
+opts_visitor_insert(GHashTable *unprocessed_opts, const QemuOpt *opt)
+{
+    GQueue *list;
+
+    list = g_hash_table_lookup(unprocessed_opts, opt->name);
+    if (list == NULL) {
+        list = g_queue_new();
+
+        /* GHashTable will never try to free the keys -- we supply NULL as
+         * "key_destroy_func" in opts_start_struct(). Thus cast away key
+         * const-ness in order to suppress gcc's warning.
+         */
+        g_hash_table_insert(unprocessed_opts, (gpointer)opt->name, list);
+    }
+
+    /* Similarly, destroy_list() doesn't call g_queue_free_full(). */
+    g_queue_push_tail(list, (gpointer)opt);
+}
+
+
+static void
+opts_start_struct(Visitor *v, void **obj, const char *kind,
+                  const char *name, size_t size, Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+    const QemuOpt *opt;
+
+    *obj = g_malloc0(size > 0 ? size : 1);
+    if (ov->depth++ > 0) {
+        return;
+    }
+
+    ov->unprocessed_opts = g_hash_table_new_full(&g_str_hash, &g_str_equal,
+                                                 NULL, &destroy_list);
+    QTAILQ_FOREACH(opt, &ov->opts_root->head, next) {
+        /* ensured by qemu-option.c::opts_do_parse() */
+        assert(strcmp(opt->name, "id") != 0);
+
+        opts_visitor_insert(ov->unprocessed_opts, opt);
+    }
+
+    if (ov->opts_root->id != NULL) {
+        ov->fake_id_opt = g_malloc0(sizeof *ov->fake_id_opt);
+
+        ov->fake_id_opt->name = "id";
+        ov->fake_id_opt->str = ov->opts_root->id;
+        opts_visitor_insert(ov->unprocessed_opts, ov->fake_id_opt);
+    }
+}
+
+
+static gboolean
+ghr_true(gpointer ign_key, gpointer ign_value, gpointer ign_user_data)
+{
+    return TRUE;
+}
+
+
+static void
+opts_end_struct(Visitor *v, Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+    GQueue *any;
+
+    if (--ov->depth > 0) {
+        return;
+    }
+
+    /* we should have processed all (distinct) QemuOpt instances */
+    any = g_hash_table_find(ov->unprocessed_opts, &ghr_true, NULL);
+    if (any) {
+        const QemuOpt *first;
+
+        first = g_queue_peek_head(any);
+        error_set(errp, QERR_INVALID_PARAMETER, first->name);
+    }
+    g_hash_table_destroy(ov->unprocessed_opts);
+    ov->unprocessed_opts = NULL;
+    g_free(ov->fake_id_opt);
+    ov->fake_id_opt = NULL;
+}
+
+
+static GQueue *
+lookup_distinct(const OptsVisitor *ov, const char *name, Error **errp)
+{
+    GQueue *list;
+
+    list = g_hash_table_lookup(ov->unprocessed_opts, name);
+    if (!list) {
+        error_set(errp, QERR_MISSING_PARAMETER, name);
+    }
+    return list;
+}
+
+
+static void
+opts_start_list(Visitor *v, const char *name, Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+
+    /* we can't traverse a list in a list */
+    assert(ov->repeated_opts == NULL);
+    ov->repeated_opts = lookup_distinct(ov, name, errp);
+    ov->repeated_opts_first = (ov->repeated_opts != NULL);
+}
+
+
+static GenericList *
+opts_next_list(Visitor *v, GenericList **list, Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+    GenericList **link;
+
+    if (ov->repeated_opts_first) {
+        ov->repeated_opts_first = false;
+        link = list;
+    } else {
+        const QemuOpt *opt;
+
+        opt = g_queue_pop_head(ov->repeated_opts);
+        if (g_queue_is_empty(ov->repeated_opts)) {
+            g_hash_table_remove(ov->unprocessed_opts, opt->name);
+            return NULL;
+        }
+        link = &(*list)->next;
+    }
+
+    *link = g_malloc0(sizeof **link);
+    return *link;
+}
+
+
+static void
+opts_end_list(Visitor *v, Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+
+    ov->repeated_opts = NULL;
+}
+
+
+static const QemuOpt *
+lookup_scalar(const OptsVisitor *ov, const char *name, Error **errp)
+{
+    if (ov->repeated_opts == NULL) {
+        GQueue *list;
+
+        /* the last occurrence of any QemuOpt takes effect when queried by name
+         */
+        list = lookup_distinct(ov, name, errp);
+        return list ? g_queue_peek_tail(list) : NULL;
+    }
+    return g_queue_peek_head(ov->repeated_opts);
+}
+
+
+static void
+processed(OptsVisitor *ov, const char *name)
+{
+    if (ov->repeated_opts == NULL) {
+        g_hash_table_remove(ov->unprocessed_opts, name);
+    }
+}
+
+
+static void
+opts_type_str(Visitor *v, char **obj, const char *name, Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+    const QemuOpt *opt;
+
+    opt = lookup_scalar(ov, name, errp);
+    if (!opt) {
+        return;
+    }
+    *obj = g_strdup(opt->str ? opt->str : "");
+    processed(ov, name);
+}
+
+
+/* mimics qemu-option.c::parse_option_bool() */
+static void
+opts_type_bool(Visitor *v, bool *obj, const char *name, Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+    const QemuOpt *opt;
+
+    opt = lookup_scalar(ov, name, errp);
+    if (!opt) {
+        return;
+    }
+
+    if (opt->str) {
+        if (strcmp(opt->str, "on") == 0 ||
+            strcmp(opt->str, "yes") == 0 ||
+            strcmp(opt->str, "y") == 0) {
+            *obj = true;
+        } else if (strcmp(opt->str, "off") == 0 ||
+            strcmp(opt->str, "no") == 0 ||
+            strcmp(opt->str, "n") == 0) {
+            *obj = false;
+        } else {
+            error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
+                "on|yes|y|off|no|n");
+            return;
+        }
+    } else {
+        *obj = true;
+    }
+
+    processed(ov, name);
+}
+
+
+static void
+opts_type_int(Visitor *v, int64_t *obj, const char *name, Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+    const QemuOpt *opt;
+    const char *str;
+    long long val;
+    char *endptr;
+
+    opt = lookup_scalar(ov, name, errp);
+    if (!opt) {
+        return;
+    }
+    str = opt->str ? opt->str : "";
+
+    errno = 0;
+    val = strtoll(str, &endptr, 0);
+    if (*str != '\0' && *endptr == '\0' && errno == 0 && INT64_MIN <= val &&
+        val <= INT64_MAX) {
+        *obj = val;
+        processed(ov, name);
+        return;
+    }
+    error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name, "an int64 value");
+}
+
+
+static void
+opts_type_uint64(Visitor *v, uint64_t *obj, const char *name, Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+    const QemuOpt *opt;
+    const char *str;
+
+    opt = lookup_scalar(ov, name, errp);
+    if (!opt) {
+        return;
+    }
+
+    str = opt->str;
+    if (str != NULL) {
+        while (isspace((unsigned char)*str)) {
+            ++str;
+        }
+
+        if (*str != '-' && *str != '\0') {
+            unsigned long long val;
+            char *endptr;
+
+            /* non-empty, non-negative subject sequence */
+            errno = 0;
+            val = strtoull(str, &endptr, 0);
+            if (*endptr == '\0' && errno == 0 && val <= UINT64_MAX) {
+                *obj = val;
+                processed(ov, name);
+                return;
+            }
+        }
+    }
+    error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
+              "an uint64 value");
+}
+
+
+static void
+opts_type_size(Visitor *v, uint64_t *obj, const char *name, Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+    const QemuOpt *opt;
+    int64_t val;
+    char *endptr;
+
+    opt = lookup_scalar(ov, name, errp);
+    if (!opt) {
+        return;
+    }
+
+    val = strtosz_suffix(opt->str ? opt->str : "", &endptr,
+                         STRTOSZ_DEFSUFFIX_B);
+    if (val != -1 && *endptr == '\0') {
+        *obj = val;
+        processed(ov, name);
+        return;
+    }
+    error_set(errp, QERR_INVALID_PARAMETER_VALUE, opt->name,
+              "a size value representible as a non-negative int64");
+}
+
+
+static void
+opts_start_optional(Visitor *v, bool *present, const char *name,
+                       Error **errp)
+{
+    OptsVisitor *ov = DO_UPCAST(OptsVisitor, visitor, v);
+
+    /* we only support a single mandatory scalar field in a list node */
+    assert(ov->repeated_opts == NULL);
+    *present = (lookup_distinct(ov, name, NULL) != NULL);
+}
+
+
+OptsVisitor *
+opts_visitor_new(const QemuOpts *opts)
+{
+    OptsVisitor *ov;
+
+    ov = g_malloc0(sizeof *ov);
+
+    ov->visitor.start_struct = &opts_start_struct;
+    ov->visitor.end_struct   = &opts_end_struct;
+
+    ov->visitor.start_list = &opts_start_list;
+    ov->visitor.next_list  = &opts_next_list;
+    ov->visitor.end_list   = &opts_end_list;
+
+    /* input_type_enum() covers both "normal" enums and union discriminators.
+     * The union discriminator field is always generated as "type"; it should
+     * match the "type" QemuOpt child of any QemuOpts.
+     *
+     * input_type_enum() will remove the looked-up key from the
+     * "unprocessed_opts" hash even if the lookup fails, because the removal is
+     * done earlier in opts_type_str(). This should be harmless.
+     */
+    ov->visitor.type_enum = &input_type_enum;
+
+    ov->visitor.type_int    = &opts_type_int;
+    ov->visitor.type_uint64 = &opts_type_uint64;
+    ov->visitor.type_size   = &opts_type_size;
+    ov->visitor.type_bool   = &opts_type_bool;
+    ov->visitor.type_str    = &opts_type_str;
+
+    /* type_number() is not filled in, but this is not the first visitor to
+     * skip some mandatory methods... */
+
+    ov->visitor.start_optional = &opts_start_optional;
+
+    ov->opts_root = opts;
+
+    return ov;
+}
+
+
+void
+opts_visitor_cleanup(OptsVisitor *ov)
+{
+    if (ov->unprocessed_opts != NULL) {
+        g_hash_table_destroy(ov->unprocessed_opts);
+    }
+    g_free(ov->fake_id_opt);
+    memset(ov, '\0', sizeof *ov);
+}
+
+
+Visitor *
+opts_get_visitor(OptsVisitor *ov)
+{
+    return &ov->visitor;
+}
diff --git a/qapi/opts-visitor.h b/qapi/opts-visitor.h
new file mode 100644
index 0000000..ea1a395
--- /dev/null
+++ b/qapi/opts-visitor.h
@@ -0,0 +1,31 @@
+/*
+ * Options Visitor
+ *
+ * Copyright Red Hat, Inc. 2012
+ *
+ * Author: Laszlo Ersek <lersek@redhat.com>
+ *
+ * This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
+ * See the COPYING.LIB file in the top-level directory.
+ *
+ */
+
+#ifndef OPTS_VISITOR_H
+#define OPTS_VISITOR_H
+
+#include "qapi-visit-core.h"
+#include "qemu-option.h"
+
+typedef struct OptsVisitor OptsVisitor;
+
+/* Contrarily to qemu-option.c::parse_option_number(), OptsVisitor's "int"
+ * parser relies on strtoll() instead of strtoull(). Consequences:
+ * - string representations of negative numbers yield negative values,
+ * - values below INT64_MIN or LLONG_MIN are rejected,
+ * - values above INT64_MAX or LLONG_MAX are rejected.
+ */
+OptsVisitor *opts_visitor_new(const QemuOpts *opts);
+void opts_visitor_cleanup(OptsVisitor *nv);
+Visitor *opts_get_visitor(OptsVisitor *nv);
+
+#endif
diff --git a/qapi/qapi-visit-core.c b/qapi/qapi-visit-core.c
index 705eca9..7a82b63 100644
--- a/qapi/qapi-visit-core.c
+++ b/qapi/qapi-visit-core.c
@@ -39,9 +39,8 @@
 
 void visit_end_struct(Visitor *v, Error **errp)
 {
-    if (!error_is_set(errp)) {
-        v->end_struct(v, errp);
-    }
+    assert(!error_is_set(errp));
+    v->end_struct(v, errp);
 }
 
 void visit_start_list(Visitor *v, const char *name, Error **errp)
@@ -62,9 +61,8 @@
 
 void visit_end_list(Visitor *v, Error **errp)
 {
-    if (!error_is_set(errp)) {
-        v->end_list(v, errp);
-    }
+    assert(!error_is_set(errp));
+    v->end_list(v, errp);
 }
 
 void visit_start_optional(Visitor *v, bool *present, const char *name,
@@ -236,6 +234,13 @@
     }
 }
 
+void visit_type_size(Visitor *v, uint64_t *obj, const char *name, Error **errp)
+{
+    if (!error_is_set(errp)) {
+        (v->type_size ? v->type_size : v->type_uint64)(v, obj, name, errp);
+    }
+}
+
 void visit_type_bool(Visitor *v, bool *obj, const char *name, Error **errp)
 {
     if (!error_is_set(errp)) {
diff --git a/qapi/qapi-visit-core.h b/qapi/qapi-visit-core.h
index a19d70c..60aceda 100644
--- a/qapi/qapi-visit-core.h
+++ b/qapi/qapi-visit-core.h
@@ -60,6 +60,8 @@
     void (*type_int16)(Visitor *v, int16_t *obj, const char *name, Error **errp);
     void (*type_int32)(Visitor *v, int32_t *obj, const char *name, Error **errp);
     void (*type_int64)(Visitor *v, int64_t *obj, const char *name, Error **errp);
+    /* visit_type_size() falls back to (*type_uint64)() if type_size is unset */
+    void (*type_size)(Visitor *v, uint64_t *obj, const char *name, Error **errp);
 };
 
 void visit_start_handle(Visitor *v, void **obj, const char *kind,
@@ -85,6 +87,7 @@
 void visit_type_int16(Visitor *v, int16_t *obj, const char *name, Error **errp);
 void visit_type_int32(Visitor *v, int32_t *obj, const char *name, Error **errp);
 void visit_type_int64(Visitor *v, int64_t *obj, const char *name, Error **errp);
+void visit_type_size(Visitor *v, uint64_t *obj, const char *name, Error **errp);
 void visit_type_bool(Visitor *v, bool *obj, const char *name, Error **errp);
 void visit_type_str(Visitor *v, char **obj, const char *name, Error **errp);
 void visit_type_number(Visitor *v, double *obj, const char *name, Error **errp);
diff --git a/qemu-option-internal.h b/qemu-option-internal.h
new file mode 100644
index 0000000..19fdc1c
--- /dev/null
+++ b/qemu-option-internal.h
@@ -0,0 +1,53 @@
+/*
+ * Commandline option parsing functions
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ * Copyright (c) 2009 Kevin Wolf <kwolf@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef QEMU_OPTIONS_INTERNAL_H
+#define QEMU_OPTIONS_INTERNAL_H
+
+#include "qemu-option.h"
+
+struct QemuOpt {
+    const char   *name;
+    const char   *str;
+
+    const QemuOptDesc *desc;
+    union {
+        bool boolean;
+        uint64_t uint;
+    } value;
+
+    QemuOpts     *opts;
+    QTAILQ_ENTRY(QemuOpt) next;
+};
+
+struct QemuOpts {
+    char *id;
+    QemuOptsList *list;
+    Location loc;
+    QTAILQ_HEAD(QemuOptHead, QemuOpt) head;
+    QTAILQ_ENTRY(QemuOpts) next;
+};
+
+#endif
diff --git a/qemu-option.c b/qemu-option.c
index bb3886c..8334190 100644
--- a/qemu-option.c
+++ b/qemu-option.c
@@ -29,9 +29,9 @@
 #include "qemu-common.h"
 #include "qemu-error.h"
 #include "qemu-objects.h"
-#include "qemu-option.h"
 #include "error.h"
 #include "qerror.h"
+#include "qemu-option-internal.h"
 
 /*
  * Extracts the name of an option from the parameter string (p points at the
@@ -511,28 +511,6 @@
 
 /* ------------------------------------------------------------------ */
 
-struct QemuOpt {
-    const char   *name;
-    const char   *str;
-
-    const QemuOptDesc *desc;
-    union {
-        bool boolean;
-        uint64_t uint;
-    } value;
-
-    QemuOpts     *opts;
-    QTAILQ_ENTRY(QemuOpt) next;
-};
-
-struct QemuOpts {
-    char *id;
-    QemuOptsList *list;
-    Location loc;
-    QTAILQ_HEAD(QemuOptHead, QemuOpt) head;
-    QTAILQ_ENTRY(QemuOpts) next;
-};
-
 static QemuOpt *qemu_opt_find(QemuOpts *opts, const char *name)
 {
     QemuOpt *opt;
diff --git a/scripts/qapi-visit.py b/scripts/qapi-visit.py
index 8d4e94a..04ef7c4 100644
--- a/scripts/qapi-visit.py
+++ b/scripts/qapi-visit.py
@@ -17,32 +17,49 @@
 import getopt
 import errno
 
-def generate_visit_struct_body(field_prefix, members):
-    ret = ""
+def generate_visit_struct_body(field_prefix, name, members):
+    ret = mcgen('''
+if (!error_is_set(errp)) {
+''')
+    push_indent()
+
     if len(field_prefix):
         field_prefix = field_prefix + "."
+        ret += mcgen('''
+Error **errp = &err; /* from outer scope */
+Error *err = NULL;
+visit_start_struct(m, NULL, "", "%(name)s", 0, &err);
+''',
+                name=name)
+    else:
+        ret += mcgen('''
+Error *err = NULL;
+visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
+''',
+                name=name)
+
+    ret += mcgen('''
+if (!err) {
+    if (!obj || *obj) {
+''')
+
+    push_indent()
+    push_indent()
     for argname, argentry, optional, structured in parse_args(members):
         if optional:
             ret += mcgen('''
-visit_start_optional(m, (obj && *obj) ? &(*obj)->%(c_prefix)shas_%(c_name)s : NULL, "%(name)s", errp);
-if ((*obj)->%(prefix)shas_%(c_name)s) {
+visit_start_optional(m, obj ? &(*obj)->%(c_prefix)shas_%(c_name)s : NULL, "%(name)s", &err);
+if (obj && (*obj)->%(prefix)shas_%(c_name)s) {
 ''',
                          c_prefix=c_var(field_prefix), prefix=field_prefix,
                          c_name=c_var(argname), name=argname)
             push_indent()
 
         if structured:
-            ret += mcgen('''
-visit_start_struct(m, NULL, "", "%(name)s", 0, errp);
-''',
-                         name=argname)
-            ret += generate_visit_struct_body(field_prefix + argname, argentry)
-            ret += mcgen('''
-visit_end_struct(m, errp);
-''')
+            ret += generate_visit_struct_body(field_prefix + argname, argname, argentry)
         else:
             ret += mcgen('''
-visit_type_%(type)s(m, (obj && *obj) ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s", errp);
+visit_type_%(type)s(m, obj ? &(*obj)->%(c_prefix)s%(c_name)s : NULL, "%(name)s", &err);
 ''',
                          c_prefix=c_var(field_prefix), prefix=field_prefix,
                          type=type_name(argentry), c_name=c_var(argname),
@@ -52,7 +69,25 @@
             pop_indent()
             ret += mcgen('''
 }
-visit_end_optional(m, errp);
+visit_end_optional(m, &err);
+''')
+
+    pop_indent()
+    ret += mcgen('''
+
+    error_propagate(errp, err);
+    err = NULL;
+}
+''')
+
+    pop_indent()
+    pop_indent()
+    ret += mcgen('''
+        /* Always call end_struct if start_struct succeeded.  */
+        visit_end_struct(m, &err);
+    }
+    error_propagate(errp, err);
+}
 ''')
     return ret
 
@@ -61,22 +96,14 @@
 
 void visit_type_%(name)s(Visitor *m, %(name)s ** obj, const char *name, Error **errp)
 {
-    if (error_is_set(errp)) {
-        return;
-    }
-    visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), errp);
-    if (obj && !*obj) {
-        goto end;
-    }
 ''',
                 name=name)
+
     push_indent()
-    ret += generate_visit_struct_body("", members)
+    ret += generate_visit_struct_body("", name, members)
     pop_indent()
 
     ret += mcgen('''
-end:
-    visit_end_struct(m, errp);
 }
 ''')
     return ret
@@ -87,18 +114,23 @@
 void visit_type_%(name)sList(Visitor *m, %(name)sList ** obj, const char *name, Error **errp)
 {
     GenericList *i, **prev = (GenericList **)obj;
+    Error *err = NULL;
 
-    if (error_is_set(errp)) {
-        return;
+    if (!error_is_set(errp)) {
+        visit_start_list(m, name, &err);
+        if (!err) {
+            for (; (i = visit_next_list(m, prev, &err)) != NULL; prev = &i) {
+                %(name)sList *native_i = (%(name)sList *)i;
+                visit_type_%(name)s(m, &native_i->value, NULL, &err);
+            }
+            error_propagate(errp, err);
+            err = NULL;
+
+            /* Always call end_list if start_list succeeded.  */
+            visit_end_list(m, &err);
+        }
+        error_propagate(errp, err);
     }
-    visit_start_list(m, name, errp);
-
-    for (; (i = visit_next_list(m, prev, errp)) != NULL; prev = &i) {
-        %(name)sList *native_i = (%(name)sList *)i;
-        visit_type_%(name)s(m, &native_i->value, NULL, errp);
-    }
-
-    visit_end_list(m, errp);
 }
 ''',
                 name=name)
@@ -122,27 +154,23 @@
 {
     Error *err = NULL;
 
-    if (error_is_set(errp)) {
-        return;
-    }
-    visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
-    if (obj && !*obj) {
-        goto end;
-    }
-    visit_type_%(name)sKind(m, &(*obj)->kind, "type", &err);
-    if (err) {
-        error_propagate(errp, err);
-        goto end;
-    }
-    switch ((*obj)->kind) {
+    if (!error_is_set(errp)) {
+        visit_start_struct(m, (void **)obj, "%(name)s", name, sizeof(%(name)s), &err);
+        if (!err) {
+            if (!obj || *obj) {
+                visit_type_%(name)sKind(m, &(*obj)->kind, "type", &err);
+                if (!err) {
+                    switch ((*obj)->kind) {
 ''',
                  name=name)
 
+    push_indent()
+    push_indent()
     for key in members:
         ret += mcgen('''
-    case %(abbrev)s_KIND_%(enum)s:
-        visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", errp);
-        break;
+            case %(abbrev)s_KIND_%(enum)s:
+                visit_type_%(c_type)s(m, &(*obj)->%(c_name)s, "data", &err);
+                break;
 ''',
                 abbrev = de_camel_case(name).upper(),
                 enum = c_fun(de_camel_case(key)).upper(),
@@ -150,11 +178,25 @@
                 c_name=c_fun(key))
 
     ret += mcgen('''
-    default:
-        abort();
+            default:
+                abort();
+            }
+        }
+        error_propagate(errp, err);
+        err = NULL;
     }
-end:
-    visit_end_struct(m, errp);
+''')
+    pop_indent()
+    ret += mcgen('''
+        /* Always call end_struct if start_struct succeeded.  */
+        visit_end_struct(m, &err);
+    }
+    error_propagate(errp, err);
+}
+''')
+
+    pop_indent();
+    ret += mcgen('''
 }
 ''')
 
diff --git a/scripts/qapi.py b/scripts/qapi.py
index e062336..8082af3 100644
--- a/scripts/qapi.py
+++ b/scripts/qapi.py
@@ -159,6 +159,12 @@
         return 'char *'
     elif name == 'int':
         return 'int64_t'
+    elif (name == 'int8' or name == 'int16' or name == 'int32' or
+          name == 'int64' or name == 'uint8' or name == 'uint16' or
+          name == 'uint32' or name == 'uint64'):
+        return name + '_t'
+    elif name == 'size':
+        return 'uint64_t'
     elif name == 'bool':
         return 'bool'
     elif name == 'number':
diff --git a/tests/test-qmp-commands.c b/tests/test-qmp-commands.c
index 60cbf01..dc3c507 100644
--- a/tests/test-qmp-commands.c
+++ b/tests/test-qmp-commands.c
@@ -3,6 +3,9 @@
 #include "test-qmp-commands.h"
 #include "qapi/qmp-core.h"
 #include "module.h"
+#include "qapi/qmp-input-visitor.h"
+#include "tests/test-qapi-types.h"
+#include "tests/test-qapi-visit.h"
 
 void qmp_user_def_cmd(Error **errp)
 {
@@ -123,6 +126,44 @@
     qapi_free_UserDefOneList(ud1list);
 }
 
+/* test generated deallocation on an object whose construction was prematurely
+ * terminated due to an error */
+static void test_dealloc_partial(void)
+{
+    static const char text[] = "don't leak me";
+
+    UserDefTwo *ud2 = NULL;
+    Error *err = NULL;
+
+    /* create partial object */
+    {
+        QDict *ud2_dict;
+        QmpInputVisitor *qiv;
+
+        ud2_dict = qdict_new();
+        qdict_put_obj(ud2_dict, "string", QOBJECT(qstring_from_str(text)));
+
+        qiv = qmp_input_visitor_new(QOBJECT(ud2_dict));
+        visit_type_UserDefTwo(qmp_input_get_visitor(qiv), &ud2, NULL, &err);
+        qmp_input_visitor_cleanup(qiv);
+        QDECREF(ud2_dict);
+    }
+
+    /* verify partial success */
+    assert(ud2 != NULL);
+    assert(ud2->string != NULL);
+    assert(strcmp(ud2->string, text) == 0);
+    assert(ud2->dict.dict.userdef == NULL);
+
+    /* confirm & release construction error */
+    assert(err != NULL);
+    error_free(err);
+
+    /* tear down partial object */
+    qapi_free_UserDefTwo(ud2);
+}
+
+
 int main(int argc, char **argv)
 {
     g_test_init(&argc, &argv, NULL);
@@ -131,6 +172,7 @@
     g_test_add_func("/0.15/dispatch_cmd_error", test_dispatch_cmd_error);
     g_test_add_func("/0.15/dispatch_cmd_io", test_dispatch_cmd_io);
     g_test_add_func("/0.15/dealloc_types", test_dealloc_types);
+    g_test_add_func("/0.15/dealloc_partial", test_dealloc_partial);
 
     module_call_init(MODULE_INIT_QAPI);
     g_test_run();
diff --git a/tests/test-qmp-input-visitor.c b/tests/test-qmp-input-visitor.c
index c30fdc4..8f5a509 100644
--- a/tests/test-qmp-input-visitor.c
+++ b/tests/test-qmp-input-visitor.c
@@ -151,14 +151,22 @@
 static void visit_type_TestStruct(Visitor *v, TestStruct **obj,
                                   const char *name, Error **errp)
 {
-    visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
-                       errp);
+    Error *err = NULL;
+    if (!error_is_set(errp)) {
+        visit_start_struct(v, (void **)obj, "TestStruct", name, sizeof(TestStruct),
+                           &err);
+        if (!err) {
+            visit_type_int(v, &(*obj)->integer, "integer", &err);
+            visit_type_bool(v, &(*obj)->boolean, "boolean", &err);
+            visit_type_str(v, &(*obj)->string, "string", &err);
 
-    visit_type_int(v, &(*obj)->integer, "integer", errp);
-    visit_type_bool(v, &(*obj)->boolean, "boolean", errp);
-    visit_type_str(v, &(*obj)->string, "string", errp);
-
-    visit_end_struct(v, errp);
+            /* Always call end_struct if start_struct succeeded.  */
+            error_propagate(errp, err);
+            err = NULL;
+            visit_end_struct(v, &err);
+        }
+        error_propagate(errp, err);
+    }
 }
 
 static void test_visitor_in_struct(TestInputVisitorData *data,