diff --git a/hw/cadence_gem.c b/hw/cadence_gem.c
index 967f625..0b633e8 100644
--- a/hw/cadence_gem.c
+++ b/hw/cadence_gem.c
@@ -1160,13 +1160,23 @@ static void gem_set_link(NetClientState *nc)
     phy_update_link(DO_UPCAST(NICState, nc, nc)->opaque);
 }
 
-static NetClientInfo net_gem_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = gem_can_receive,
-    .receive = gem_receive,
-    .cleanup = gem_cleanup,
-    .link_status_changed = gem_set_link,
+#define TYPE_GEM_NET_CLIENT "gem-net-client"
+
+static void gem_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = gem_can_receive;
+    ncc->receive = gem_receive;
+    ncc->cleanup = gem_cleanup;
+    ncc->link_status_changed = gem_set_link;
+}
+
+static TypeInfo gem_net_client_info = {
+    .name = TYPE_GEM_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = gem_net_client_class_init,
 };
 
 static int gem_init(SysBusDevice *dev)
@@ -1182,7 +1192,7 @@ static int gem_init(SysBusDevice *dev)
     sysbus_init_irq(dev, &s->irq);
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
 
-    s->nic = qemu_new_nic(&net_gem_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_GEM_NET_CLIENT, &s->conf,
             object_get_typename(OBJECT(dev)), dev->qdev.id, s);
 
     return 0;
@@ -1228,6 +1238,7 @@ static TypeInfo gem_info = {
 static void gem_register_types(void)
 {
     type_register_static(&gem_info);
+    type_register_static(&gem_net_client_info);
 }
 
 type_init(gem_register_types)
diff --git a/hw/dp8393x.c b/hw/dp8393x.c
index 4fa6ecc..503af67 100644
--- a/hw/dp8393x.c
+++ b/hw/dp8393x.c
@@ -172,6 +172,10 @@ typedef struct dp8393xState {
     void* mem_opaque;
 } dp8393xState;
 
+static int nic_can_receive(NetClientState *nc);
+static ssize_t nic_receive(NetClientState *nc, const uint8_t * buf,
+                           size_t size);
+
 static void dp8393x_update_irq(dp8393xState *s)
 {
     int level = (s->regs[SONIC_IMR] & s->regs[SONIC_ISR]) ? 1 : 0;
@@ -408,9 +412,9 @@ static void do_transmit_packets(dp8393xState *s)
         if (s->regs[SONIC_RCR] & (SONIC_RCR_LB1 | SONIC_RCR_LB0)) {
             /* Loopback */
             s->regs[SONIC_TCR] |= SONIC_TCR_CRSL;
-            if (s->nic->nc.info->can_receive(&s->nic->nc)) {
+            if (nic_can_receive(&s->nic->nc)) {
                 s->loopback_packet = 1;
-                s->nic->nc.info->receive(&s->nic->nc, s->tx_buffer, tx_len);
+                nic_receive(&s->nic->nc, s->tx_buffer, tx_len);
             }
         } else {
             /* Transmit packet */
@@ -871,12 +875,23 @@ static void nic_cleanup(NetClientState *nc)
     g_free(s);
 }
 
-static NetClientInfo net_dp83932_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = nic_can_receive,
-    .receive = nic_receive,
-    .cleanup = nic_cleanup,
+#define TYPE_DP83932_NET_CLIENT "dp83932-net-client"
+
+static void dp83932_net_client_class_init(ObjectClass *klass,
+                                          void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = nic_can_receive;
+    ncc->receive = nic_receive;
+    ncc->cleanup = nic_cleanup;
+}
+
+static TypeInfo dp83932_net_client_info = {
+    .name = TYPE_DP83932_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = dp83932_net_client_class_init,
 };
 
 void dp83932_init(NICInfo *nd, target_phys_addr_t base, int it_shift,
@@ -901,7 +916,8 @@ void dp83932_init(NICInfo *nd, target_phys_addr_t base, int it_shift,
     s->conf.macaddr = nd->macaddr;
     s->conf.peer = nd->netdev;
 
-    s->nic = qemu_new_nic(&net_dp83932_info, &s->conf, nd->model, nd->name, s);
+    s->nic = qemu_new_nic(TYPE_DP83932_NET_CLIENT, &s->conf, nd->model,
+                          nd->name, s);
 
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
     qemu_register_reset(nic_reset, s);
@@ -911,3 +927,10 @@ void dp83932_init(NICInfo *nd, target_phys_addr_t base, int it_shift,
                           "dp8393x", 0x40 << it_shift);
     memory_region_add_subregion(address_space, base, &s->mmio);
 }
+
+static void dp83932_register_types(void)
+{
+    type_register_static(&dp83932_net_client_info);
+}
+
+type_init(dp83932_register_types)
diff --git a/hw/e1000.c b/hw/e1000.c
index 63fee10..1d5f2f3 100644
--- a/hw/e1000.c
+++ b/hw/e1000.c
@@ -144,6 +144,9 @@ enum {
     defreg(VET),
 };
 
+static ssize_t e1000_receive(NetClientState *nc, const uint8_t *buf,
+                             size_t size);
+
 static void
 e1000_link_down(E1000State *s)
 {
@@ -447,7 +450,7 @@ static void
 e1000_send_packet(E1000State *s, const uint8_t *buf, int size)
 {
     if (s->phy_reg[PHY_CTRL] & MII_CR_LOOPBACK) {
-        s->nic->nc.info->receive(&s->nic->nc, buf, size);
+        e1000_receive(&s->nic->nc, buf, size);
     } else {
         qemu_send_packet(&s->nic->nc, buf, size);
     }
@@ -1220,13 +1223,23 @@ pci_e1000_uninit(PCIDevice *dev)
     qemu_del_net_client(&d->nic->nc);
 }
 
-static NetClientInfo net_e1000_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = e1000_can_receive,
-    .receive = e1000_receive,
-    .cleanup = e1000_cleanup,
-    .link_status_changed = e1000_set_link_status,
+#define TYPE_E1000_NET_CLIENT "e1000-net-client"
+
+static void e1000_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = e1000_can_receive;
+    ncc->receive = e1000_receive;
+    ncc->cleanup = e1000_cleanup;
+    ncc->link_status_changed = e1000_set_link_status;
+}
+
+static TypeInfo e1000_net_client_info = {
+    .name = TYPE_E1000_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = e1000_net_client_class_init,
 };
 
 static int pci_e1000_init(PCIDevice *pci_dev)
@@ -1261,7 +1274,7 @@ static int pci_e1000_init(PCIDevice *pci_dev)
     checksum = (uint16_t) EEPROM_SUM - checksum;
     d->eeprom_data[EEPROM_CHECKSUM_REG] = checksum;
 
-    d->nic = qemu_new_nic(&net_e1000_info, &d->conf,
+    d->nic = qemu_new_nic(TYPE_E1000_NET_CLIENT, &d->conf,
                           object_get_typename(OBJECT(d)), d->dev.qdev.id, d);
 
     qemu_format_nic_info_str(&d->nic->nc, macaddr);
@@ -1312,6 +1325,7 @@ static TypeInfo e1000_info = {
 static void e1000_register_types(void)
 {
     type_register_static(&e1000_info);
+    type_register_static(&e1000_net_client_info);
 }
 
 type_init(e1000_register_types)
diff --git a/hw/eepro100.c b/hw/eepro100.c
index 5b23116..9b82a70 100644
--- a/hw/eepro100.c
+++ b/hw/eepro100.c
@@ -1852,12 +1852,23 @@ static void pci_nic_uninit(PCIDevice *pci_dev)
     qemu_del_net_client(&s->nic->nc);
 }
 
-static NetClientInfo net_eepro100_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = nic_can_receive,
-    .receive = nic_receive,
-    .cleanup = nic_cleanup,
+#define TYPE_EEPRO100_NET_CLIENT "eepro100-net-client"
+
+static void eepro100_net_client_class_init(ObjectClass *klass,
+                                           void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = nic_can_receive;
+    ncc->receive = nic_receive;
+    ncc->cleanup = nic_cleanup;
+}
+
+static TypeInfo eepro100_net_client_info = {
+    .name = TYPE_EEPRO100_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = eepro100_net_client_class_init,
 };
 
 static int e100_nic_init(PCIDevice *pci_dev)
@@ -1892,7 +1903,7 @@ static int e100_nic_init(PCIDevice *pci_dev)
 
     nic_reset(s);
 
-    s->nic = qemu_new_nic(&net_eepro100_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_EEPRO100_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(pci_dev)), pci_dev->qdev.id, s);
 
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
@@ -2110,6 +2121,8 @@ static void eepro100_register_types(void)
         
         type_register(&type_info);
     }
+
+    type_register_static(&eepro100_net_client_info);
 }
 
 type_init(eepro100_register_types)
diff --git a/hw/etraxfs_eth.c b/hw/etraxfs_eth.c
index b124f5b..5470642 100644
--- a/hw/etraxfs_eth.c
+++ b/hw/etraxfs_eth.c
@@ -578,13 +578,23 @@ static void eth_cleanup(NetClientState *nc)
         g_free(eth);
 }
 
-static NetClientInfo net_etraxfs_info = {
-	.type = NET_CLIENT_OPTIONS_KIND_NIC,
-	.size = sizeof(NICState),
-	.can_receive = eth_can_receive,
-	.receive = eth_receive,
-	.cleanup = eth_cleanup,
-	.link_status_changed = eth_set_link,
+#define TYPE_ETRAXFS_NET_CLIENT "etraxfs-net-client"
+
+static void etraxfs_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+	NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+	ncc->can_receive = eth_can_receive;
+	ncc->receive = eth_receive;
+	ncc->cleanup = eth_cleanup;
+	ncc->link_status_changed = eth_set_link;
+}
+
+static TypeInfo etraxfs_net_client_info = {
+	.name = TYPE_ETRAXFS_NET_CLIENT,
+	.parent = TYPE_NIC_NET_CLIENT,
+	.instance_size = sizeof(NICState),
+	.class_init = etraxfs_net_client_class_init,
 };
 
 static int fs_eth_init(SysBusDevice *dev)
@@ -604,7 +614,7 @@ static int fs_eth_init(SysBusDevice *dev)
 	sysbus_init_mmio(dev, &s->mmio);
 
 	qemu_macaddr_default_if_unset(&s->conf.macaddr);
-	s->nic = qemu_new_nic(&net_etraxfs_info, &s->conf,
+	s->nic = qemu_new_nic(TYPE_ETRAXFS_NET_CLIENT, &s->conf,
 			      object_get_typename(OBJECT(s)), dev->qdev.id, s);
 	qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
 
@@ -640,6 +650,7 @@ static TypeInfo etraxfs_eth_info = {
 static void etraxfs_eth_register_types(void)
 {
     type_register_static(&etraxfs_eth_info);
+    type_register_static(&etraxfs_net_client_info);
 }
 
 type_init(etraxfs_eth_register_types)
diff --git a/hw/lan9118.c b/hw/lan9118.c
index ceaf96f..162ad00 100644
--- a/hw/lan9118.c
+++ b/hw/lan9118.c
@@ -1311,13 +1311,23 @@ static void lan9118_cleanup(NetClientState *nc)
     s->nic = NULL;
 }
 
-static NetClientInfo net_lan9118_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = lan9118_can_receive,
-    .receive = lan9118_receive,
-    .cleanup = lan9118_cleanup,
-    .link_status_changed = lan9118_set_link,
+#define TYPE_LAN9118_NET_CLIENT "lan9118-net-client"
+
+static void lan9118_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = lan9118_can_receive;
+    ncc->receive = lan9118_receive;
+    ncc->cleanup = lan9118_cleanup;
+    ncc->link_status_changed = lan9118_set_link;
+}
+
+static TypeInfo lan9118_net_client_info = {
+    .name = TYPE_LAN9118_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = lan9118_net_client_class_init,
 };
 
 static int lan9118_init1(SysBusDevice *dev)
@@ -1333,7 +1343,7 @@ static int lan9118_init1(SysBusDevice *dev)
     sysbus_init_irq(dev, &s->irq);
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
 
-    s->nic = qemu_new_nic(&net_lan9118_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_LAN9118_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
     s->eeprom[0] = 0xa5;
@@ -1378,6 +1388,7 @@ static TypeInfo lan9118_info = {
 static void lan9118_register_types(void)
 {
     type_register_static(&lan9118_info);
+    type_register_static(&lan9118_net_client_info);
 }
 
 /* Legacy helper function.  Should go away when machine config files are
diff --git a/hw/lance.c b/hw/lance.c
index 9b98bb8..25daca7 100644
--- a/hw/lance.c
+++ b/hw/lance.c
@@ -85,22 +85,6 @@ static const MemoryRegionOps lance_mem_ops = {
     },
 };
 
-static void lance_cleanup(NetClientState *nc)
-{
-    PCNetState *d = DO_UPCAST(NICState, nc, nc)->opaque;
-
-    pcnet_common_cleanup(d);
-}
-
-static NetClientInfo net_lance_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = pcnet_can_receive,
-    .receive = pcnet_receive,
-    .link_status_changed = pcnet_set_link_status,
-    .cleanup = lance_cleanup,
-};
-
 static const VMStateDescription vmstate_lance = {
     .name = "pcnet",
     .version_id = 3,
@@ -127,7 +111,7 @@ static int lance_init(SysBusDevice *dev)
 
     s->phys_mem_read = ledma_memory_read;
     s->phys_mem_write = ledma_memory_write;
-    return pcnet_common_init(&dev->qdev, s, &net_lance_info);
+    return pcnet_common_init(&dev->qdev, s);
 }
 
 static void lance_reset(DeviceState *dev)
diff --git a/hw/mcf_fec.c b/hw/mcf_fec.c
index 2fec5bc..2957805 100644
--- a/hw/mcf_fec.c
+++ b/hw/mcf_fec.c
@@ -449,12 +449,22 @@ static void mcf_fec_cleanup(NetClientState *nc)
     g_free(s);
 }
 
-static NetClientInfo net_mcf_fec_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = mcf_fec_can_receive,
-    .receive = mcf_fec_receive,
-    .cleanup = mcf_fec_cleanup,
+#define TYPE_MCF_FEC_NET_CLIENT "mcf-fec-net-client"
+
+static void mcf_fec_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = mcf_fec_can_receive;
+    ncc->receive = mcf_fec_receive;
+    ncc->cleanup = mcf_fec_cleanup;
+}
+
+static TypeInfo mcf_fec_net_client_info = {
+    .name = TYPE_MCF_FEC_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = mcf_fec_net_client_class_init,
 };
 
 void mcf_fec_init(MemoryRegion *sysmem, NICInfo *nd,
@@ -474,7 +484,15 @@ void mcf_fec_init(MemoryRegion *sysmem, NICInfo *nd,
     s->conf.macaddr = nd->macaddr;
     s->conf.peer = nd->netdev;
 
-    s->nic = qemu_new_nic(&net_mcf_fec_info, &s->conf, nd->model, nd->name, s);
+    s->nic = qemu_new_nic(TYPE_MCF_FEC_NET_CLIENT, &s->conf, nd->model,
+                          nd->name, s);
 
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
 }
+
+static void mcf_fec_register_types(void)
+{
+    type_register_static(&mcf_fec_net_client_info);
+}
+
+type_init(mcf_fec_register_types)
diff --git a/hw/milkymist-minimac2.c b/hw/milkymist-minimac2.c
index b483a02..3c80c35 100644
--- a/hw/milkymist-minimac2.c
+++ b/hw/milkymist-minimac2.c
@@ -447,12 +447,23 @@ static void milkymist_minimac2_reset(DeviceState *d)
     s->phy_regs[R_PHY_ID2] = 0x161a;
 }
 
-static NetClientInfo net_milkymist_minimac2_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = minimac2_can_rx,
-    .receive = minimac2_rx,
-    .cleanup = minimac2_cleanup,
+#define TYPE_MILKYMIST_MINIMAC2_NET_CLIENT "milkymist-minimac2-net-client"
+
+static void milkymist_minimac2_net_client_class_init(ObjectClass *klass,
+                                                     void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = minimac2_can_rx;
+    ncc->receive = minimac2_rx;
+    ncc->cleanup = minimac2_cleanup;
+}
+
+static TypeInfo milkymist_minimac2_net_client_info = {
+    .name = TYPE_MILKYMIST_MINIMAC2_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = milkymist_minimac2_net_client_class_init,
 };
 
 static int milkymist_minimac2_init(SysBusDevice *dev)
@@ -478,7 +489,7 @@ static int milkymist_minimac2_init(SysBusDevice *dev)
     sysbus_add_memory(dev, s->buffers_base, &s->buffers);
 
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
-    s->nic = qemu_new_nic(&net_milkymist_minimac2_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_MILKYMIST_MINIMAC2_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
 
@@ -545,6 +556,7 @@ static TypeInfo milkymist_minimac2_info = {
 static void milkymist_minimac2_register_types(void)
 {
     type_register_static(&milkymist_minimac2_info);
+    type_register_static(&milkymist_minimac2_net_client_info);
 }
 
 type_init(milkymist_minimac2_register_types)
diff --git a/hw/mipsnet.c b/hw/mipsnet.c
index 28063b1..df06219 100644
--- a/hw/mipsnet.c
+++ b/hw/mipsnet.c
@@ -216,12 +216,22 @@ static void mipsnet_cleanup(NetClientState *nc)
     s->nic = NULL;
 }
 
-static NetClientInfo net_mipsnet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = mipsnet_can_receive,
-    .receive = mipsnet_receive,
-    .cleanup = mipsnet_cleanup,
+#define TYPE_MIPSNET_NET_CLIENT "mipsnet-net-client"
+
+static void mipsnet_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = mipsnet_can_receive;
+    ncc->receive = mipsnet_receive;
+    ncc->cleanup = mipsnet_cleanup;
+}
+
+static TypeInfo mipsnet_net_client_info = {
+    .name = TYPE_MIPSNET_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = mipsnet_net_client_class_init,
 };
 
 static const MemoryRegionOps mipsnet_ioport_ops = {
@@ -239,7 +249,7 @@ static int mipsnet_sysbus_init(SysBusDevice *dev)
     sysbus_init_mmio(dev, &s->io);
     sysbus_init_irq(dev, &s->irq);
 
-    s->nic = qemu_new_nic(&net_mipsnet_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_MIPSNET_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
 
@@ -279,6 +289,7 @@ static TypeInfo mipsnet_info = {
 static void mipsnet_register_types(void)
 {
     type_register_static(&mipsnet_info);
+    type_register_static(&mipsnet_net_client_info);
 }
 
 type_init(mipsnet_register_types)
diff --git a/hw/musicpal.c b/hw/musicpal.c
index f305e21..8ebb9c8 100644
--- a/hw/musicpal.c
+++ b/hw/musicpal.c
@@ -373,12 +373,23 @@ static void eth_cleanup(NetClientState *nc)
     s->nic = NULL;
 }
 
-static NetClientInfo net_mv88w8618_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = eth_can_receive,
-    .receive = eth_receive,
-    .cleanup = eth_cleanup,
+#define TYPE_MV88W8618_NET_CLIENT "mv88w8618-net-client"
+
+static void mv88w8618_net_client_class_init(ObjectClass *klass,
+                                            void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = eth_can_receive;
+    ncc->receive = eth_receive;
+    ncc->cleanup = eth_cleanup;
+}
+
+static TypeInfo mv88w8618_net_client_info = {
+    .name = TYPE_MV88W8618_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = mv88w8618_net_client_class_init,
 };
 
 static int mv88w8618_eth_init(SysBusDevice *dev)
@@ -386,7 +397,7 @@ static int mv88w8618_eth_init(SysBusDevice *dev)
     mv88w8618_eth_state *s = FROM_SYSBUS(mv88w8618_eth_state, dev);
 
     sysbus_init_irq(dev, &s->irq);
-    s->nic = qemu_new_nic(&net_mv88w8618_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_MV88W8618_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     memory_region_init_io(&s->iomem, &mv88w8618_eth_ops, s, "mv88w8618-eth",
                           MP_ETH_SIZE);
@@ -1691,6 +1702,7 @@ static void musicpal_register_types(void)
     type_register_static(&musicpal_lcd_info);
     type_register_static(&musicpal_gpio_info);
     type_register_static(&musicpal_key_info);
+    type_register_static(&mv88w8618_net_client_info);
 }
 
 type_init(musicpal_register_types)
diff --git a/hw/ne2000-isa.c b/hw/ne2000-isa.c
index 69982a9..2b4cd61 100644
--- a/hw/ne2000-isa.c
+++ b/hw/ne2000-isa.c
@@ -36,21 +36,6 @@ typedef struct ISANE2000State {
     NE2000State ne2000;
 } ISANE2000State;
 
-static void isa_ne2000_cleanup(NetClientState *nc)
-{
-    NE2000State *s = DO_UPCAST(NICState, nc, nc)->opaque;
-
-    s->nic = NULL;
-}
-
-static NetClientInfo net_ne2000_isa_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = ne2000_can_receive,
-    .receive = ne2000_receive,
-    .cleanup = isa_ne2000_cleanup,
-};
-
 static const VMStateDescription vmstate_isa_ne2000 = {
     .name = "ne2000",
     .version_id = 2,
@@ -75,7 +60,7 @@ static int isa_ne2000_initfn(ISADevice *dev)
     qemu_macaddr_default_if_unset(&s->c.macaddr);
     ne2000_reset(s);
 
-    s->nic = qemu_new_nic(&net_ne2000_isa_info, &s->c,
+    s->nic = qemu_new_nic(TYPE_NE2000_NET_CLIENT, &s->c,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->c.macaddr.a);
 
diff --git a/hw/ne2000.c b/hw/ne2000.c
index 15605c4..1b7d1d8 100644
--- a/hw/ne2000.c
+++ b/hw/ne2000.c
@@ -710,12 +710,20 @@ static void ne2000_cleanup(NetClientState *nc)
     s->nic = NULL;
 }
 
-static NetClientInfo net_ne2000_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = ne2000_can_receive,
-    .receive = ne2000_receive,
-    .cleanup = ne2000_cleanup,
+static void ne2000_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = ne2000_can_receive;
+    ncc->receive = ne2000_receive;
+    ncc->cleanup = ne2000_cleanup;
+}
+
+static TypeInfo ne2000_net_client_info = {
+    .name = TYPE_NE2000_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = ne2000_net_client_class_init,
 };
 
 static int pci_ne2000_init(PCIDevice *pci_dev)
@@ -735,7 +743,7 @@ static int pci_ne2000_init(PCIDevice *pci_dev)
     qemu_macaddr_default_if_unset(&s->c.macaddr);
     ne2000_reset(s);
 
-    s->nic = qemu_new_nic(&net_ne2000_info, &s->c,
+    s->nic = qemu_new_nic(TYPE_NE2000_NET_CLIENT, &s->c,
                           object_get_typename(OBJECT(pci_dev)), pci_dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->c.macaddr.a);
 
@@ -783,6 +791,7 @@ static TypeInfo ne2000_info = {
 static void ne2000_register_types(void)
 {
     type_register_static(&ne2000_info);
+    type_register_static(&ne2000_net_client_info);
 }
 
 type_init(ne2000_register_types)
diff --git a/hw/ne2000.h b/hw/ne2000.h
index 1e7ab07..2d790cb 100644
--- a/hw/ne2000.h
+++ b/hw/ne2000.h
@@ -28,6 +28,8 @@ typedef struct NE2000State {
     uint8_t mem[NE2000_MEM_SIZE];
 } NE2000State;
 
+#define TYPE_NE2000_NET_CLIENT "ne2000-net-client"
+
 void ne2000_setup_io(NE2000State *s, unsigned size);
 extern const VMStateDescription vmstate_ne2000;
 void ne2000_reset(NE2000State *s);
diff --git a/hw/opencores_eth.c b/hw/opencores_eth.c
index 8c15969..40b4059 100644
--- a/hw/opencores_eth.c
+++ b/hw/opencores_eth.c
@@ -466,13 +466,24 @@ static void open_eth_cleanup(NetClientState *nc)
 {
 }
 
-static NetClientInfo net_open_eth_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = open_eth_can_receive,
-    .receive = open_eth_receive,
-    .cleanup = open_eth_cleanup,
-    .link_status_changed = open_eth_set_link_status,
+#define TYPE_OPEN_ETH_NET_CLIENT "open-eth-net-client"
+
+static void open_eth_net_client_class_init(ObjectClass *klass,
+                                           void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = open_eth_can_receive;
+    ncc->receive = open_eth_receive;
+    ncc->cleanup = open_eth_cleanup;
+    ncc->link_status_changed = open_eth_set_link_status;
+}
+
+static TypeInfo open_eth_net_client_info = {
+    .name = TYPE_OPEN_ETH_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = open_eth_net_client_class_init,
 };
 
 static void open_eth_start_xmit(OpenEthState *s, desc *tx)
@@ -691,7 +702,7 @@ static int sysbus_open_eth_init(SysBusDevice *dev)
 
     sysbus_init_irq(dev, &s->irq);
 
-    s->nic = qemu_new_nic(&net_open_eth_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_OPEN_ETH_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(s)), s->dev.qdev.id, s);
     return 0;
 }
@@ -728,6 +739,7 @@ static TypeInfo open_eth_info = {
 static void open_eth_register_types(void)
 {
     type_register_static(&open_eth_info);
+    type_register_static(&open_eth_net_client_info);
 }
 
 type_init(open_eth_register_types)
diff --git a/hw/pcnet-pci.c b/hw/pcnet-pci.c
index 48fd447..808a713 100644
--- a/hw/pcnet-pci.c
+++ b/hw/pcnet-pci.c
@@ -264,13 +264,6 @@ static void pci_physical_memory_read(void *dma_opaque, target_phys_addr_t addr,
     pci_dma_read(dma_opaque, addr, buf, len);
 }
 
-static void pci_pcnet_cleanup(NetClientState *nc)
-{
-    PCNetState *d = DO_UPCAST(NICState, nc, nc)->opaque;
-
-    pcnet_common_cleanup(d);
-}
-
 static void pci_pcnet_uninit(PCIDevice *dev)
 {
     PCIPCNetState *d = DO_UPCAST(PCIPCNetState, pci_dev, dev);
@@ -282,15 +275,6 @@ static void pci_pcnet_uninit(PCIDevice *dev)
     qemu_del_net_client(&d->state.nic->nc);
 }
 
-static NetClientInfo net_pci_pcnet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = pcnet_can_receive,
-    .receive = pcnet_receive,
-    .link_status_changed = pcnet_set_link_status,
-    .cleanup = pci_pcnet_cleanup,
-};
-
 static int pci_pcnet_init(PCIDevice *pci_dev)
 {
     PCIPCNetState *d = DO_UPCAST(PCIPCNetState, pci_dev, pci_dev);
@@ -329,7 +313,7 @@ static int pci_pcnet_init(PCIDevice *pci_dev)
     s->phys_mem_write = pci_physical_memory_write;
     s->dma_opaque = pci_dev;
 
-    return pcnet_common_init(&pci_dev->qdev, s, &net_pci_pcnet_info);
+    return pcnet_common_init(&pci_dev->qdev, s);
 }
 
 static void pci_reset(DeviceState *dev)
diff --git a/hw/pcnet.c b/hw/pcnet.c
index 40820b3..9444b97 100644
--- a/hw/pcnet.c
+++ b/hw/pcnet.c
@@ -1716,12 +1716,34 @@ const VMStateDescription vmstate_pcnet = {
     }
 };
 
-void pcnet_common_cleanup(PCNetState *d)
+static void pcnet_common_cleanup(NetClientState *nc)
 {
+    NICState *nic = DO_UPCAST(NICState, nc, nc);
+    PCNetState *d = nic->opaque;
+
     d->nic = NULL;
 }
 
-int pcnet_common_init(DeviceState *dev, PCNetState *s, NetClientInfo *info)
+#define TYPE_PCNET_NET_CLIENT "pcnet-net-client"
+
+static void pcnet_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = pcnet_can_receive;
+    ncc->receive = pcnet_receive;
+    ncc->link_status_changed = pcnet_set_link_status;
+    ncc->cleanup = pcnet_common_cleanup;
+}
+
+static TypeInfo pcnet_net_client_info = {
+    .name = TYPE_PCNET_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = pcnet_net_client_class_init,
+};
+
+int pcnet_common_init(DeviceState *dev, PCNetState *s)
 {
     int i;
     uint16_t checksum;
@@ -1729,7 +1751,8 @@ int pcnet_common_init(DeviceState *dev, PCNetState *s, NetClientInfo *info)
     s->poll_timer = qemu_new_timer_ns(vm_clock, pcnet_poll_timer, s);
 
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
-    s->nic = qemu_new_nic(info, &s->conf, object_get_typename(OBJECT(dev)), dev->id, s);
+    s->nic = qemu_new_nic(TYPE_PCNET_NET_CLIENT, &s->conf,
+                          object_get_typename(OBJECT(dev)), dev->id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
 
     add_boot_device_path(s->conf.bootindex, dev, "/ethernet-phy@0");
@@ -1765,3 +1788,10 @@ int pcnet_common_init(DeviceState *dev, PCNetState *s, NetClientInfo *info)
 
     return 0;
 }
+
+static void pcnet_register_types(void)
+{
+    type_register_static(&pcnet_net_client_info);
+}
+
+type_init(pcnet_register_types)
diff --git a/hw/pcnet.h b/hw/pcnet.h
index d0af54a..c9b408d 100644
--- a/hw/pcnet.h
+++ b/hw/pcnet.h
@@ -60,6 +60,5 @@ uint32_t pcnet_bcr_readw(PCNetState *s, uint32_t rap);
 int pcnet_can_receive(NetClientState *nc);
 ssize_t pcnet_receive(NetClientState *nc, const uint8_t *buf, size_t size_);
 void pcnet_set_link_status(NetClientState *nc);
-void pcnet_common_cleanup(PCNetState *d);
-int pcnet_common_init(DeviceState *dev, PCNetState *s, NetClientInfo *info);
+int pcnet_common_init(DeviceState *dev, PCNetState *s);
 extern const VMStateDescription vmstate_pcnet;
diff --git a/hw/rtl8139.c b/hw/rtl8139.c
index 6b28fea..1fdbf71 100644
--- a/hw/rtl8139.c
+++ b/hw/rtl8139.c
@@ -3468,13 +3468,24 @@ static void rtl8139_set_link_status(NetClientState *nc)
     rtl8139_update_irq(s);
 }
 
-static NetClientInfo net_rtl8139_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = rtl8139_can_receive,
-    .receive = rtl8139_receive,
-    .cleanup = rtl8139_cleanup,
-    .link_status_changed = rtl8139_set_link_status,
+#define TYPE_RTL8139_NET_CLIENT "rtl8139-net-client"
+
+static void rtl8139_net_client_class_init(ObjectClass *klass,
+                                          void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = rtl8139_can_receive;
+    ncc->receive = rtl8139_receive;
+    ncc->cleanup = rtl8139_cleanup;
+    ncc->link_status_changed = rtl8139_set_link_status;
+}
+
+static TypeInfo rtl8139_net_client_info = {
+    .name = TYPE_RTL8139_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = rtl8139_net_client_class_init,
 };
 
 static int pci_rtl8139_init(PCIDevice *dev)
@@ -3506,7 +3517,7 @@ static int pci_rtl8139_init(PCIDevice *dev)
     s->eeprom.contents[8] = s->conf.macaddr.a[2] | s->conf.macaddr.a[3] << 8;
     s->eeprom.contents[9] = s->conf.macaddr.a[4] | s->conf.macaddr.a[5] << 8;
 
-    s->nic = qemu_new_nic(&net_rtl8139_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_RTL8139_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
 
@@ -3555,6 +3566,7 @@ static TypeInfo rtl8139_info = {
 static void rtl8139_register_types(void)
 {
     type_register_static(&rtl8139_info);
+    type_register_static(&rtl8139_net_client_info);
 }
 
 type_init(rtl8139_register_types)
diff --git a/hw/smc91c111.c b/hw/smc91c111.c
index d6ef302..4337462 100644
--- a/hw/smc91c111.c
+++ b/hw/smc91c111.c
@@ -735,12 +735,23 @@ static void smc91c111_cleanup(NetClientState *nc)
     s->nic = NULL;
 }
 
-static NetClientInfo net_smc91c111_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = smc91c111_can_receive,
-    .receive = smc91c111_receive,
-    .cleanup = smc91c111_cleanup,
+#define TYPE_SMC91C111_NET_CLIENT "smc91c111-net-client"
+
+static void smc91c111_net_client_class_init(ObjectClass *klass,
+                                            void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = smc91c111_can_receive;
+    ncc->receive = smc91c111_receive;
+    ncc->cleanup = smc91c111_cleanup;
+}
+
+static TypeInfo smc91c111_net_client_info = {
+    .name = TYPE_SMC91C111_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = smc91c111_net_client_class_init,
 };
 
 static int smc91c111_init1(SysBusDevice *dev)
@@ -751,7 +762,7 @@ static int smc91c111_init1(SysBusDevice *dev)
     sysbus_init_mmio(dev, &s->mmio);
     sysbus_init_irq(dev, &s->irq);
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
-    s->nic = qemu_new_nic(&net_smc91c111_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_SMC91C111_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
     /* ??? Save/restore.  */
@@ -784,6 +795,7 @@ static TypeInfo smc91c111_info = {
 static void smc91c111_register_types(void)
 {
     type_register_static(&smc91c111_info);
+    type_register_static(&smc91c111_net_client_info);
 }
 
 /* Legacy helper function.  Should go away when machine config files are
diff --git a/hw/spapr_llan.c b/hw/spapr_llan.c
index bd3f131..bacec94 100644
--- a/hw/spapr_llan.c
+++ b/hw/spapr_llan.c
@@ -175,11 +175,22 @@ static ssize_t spapr_vlan_receive(NetClientState *nc, const uint8_t *buf,
     return size;
 }
 
-static NetClientInfo net_spapr_vlan_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = spapr_vlan_can_receive,
-    .receive = spapr_vlan_receive,
+#define TYPE_SPAPR_VLAN_NET_CLIENT "spapr-vlan-net-client"
+
+static void spapr_vlan_net_client_class_init(ObjectClass *klass,
+                                             void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = spapr_vlan_can_receive;
+    ncc->receive = spapr_vlan_receive;
+}
+
+static TypeInfo spapr_vlan_net_client_info = {
+    .name = TYPE_SPAPR_VLAN_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = spapr_vlan_net_client_class_init,
 };
 
 static void spapr_vlan_reset(VIOsPAPRDevice *sdev)
@@ -197,7 +208,7 @@ static int spapr_vlan_init(VIOsPAPRDevice *sdev)
 
     qemu_macaddr_default_if_unset(&dev->nicconf.macaddr);
 
-    dev->nic = qemu_new_nic(&net_spapr_vlan_info, &dev->nicconf,
+    dev->nic = qemu_new_nic(TYPE_SPAPR_VLAN_NET_CLIENT, &dev->nicconf,
                             object_get_typename(OBJECT(sdev)), sdev->qdev.id, dev);
     qemu_format_nic_info_str(&dev->nic->nc, dev->nicconf.macaddr.a);
 
@@ -518,6 +529,7 @@ static void spapr_vlan_register_types(void)
                              h_add_logical_lan_buffer);
     spapr_register_hypercall(H_MULTICAST_CTRL, h_multicast_ctrl);
     type_register_static(&spapr_vlan_info);
+    type_register_static(&spapr_vlan_net_client_info);
 }
 
 type_init(spapr_vlan_register_types)
diff --git a/hw/stellaris_enet.c b/hw/stellaris_enet.c
index bc97280..a892712 100644
--- a/hw/stellaris_enet.c
+++ b/hw/stellaris_enet.c
@@ -392,12 +392,23 @@ static void stellaris_enet_cleanup(NetClientState *nc)
     g_free(s);
 }
 
-static NetClientInfo net_stellaris_enet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = stellaris_enet_can_receive,
-    .receive = stellaris_enet_receive,
-    .cleanup = stellaris_enet_cleanup,
+#define TYPE_STELLARIS_ENET_NET_CLIENT "stellaris-enet-net-client"
+
+static void stellaris_enet_net_client_class_init(ObjectClass *klass,
+                                                 void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = stellaris_enet_can_receive;
+    ncc->receive = stellaris_enet_receive;
+    ncc->cleanup = stellaris_enet_cleanup;
+}
+
+static TypeInfo stellaris_enet_net_client_info = {
+    .name = TYPE_STELLARIS_ENET_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = stellaris_enet_net_client_class_init,
 };
 
 static int stellaris_enet_init(SysBusDevice *dev)
@@ -410,7 +421,7 @@ static int stellaris_enet_init(SysBusDevice *dev)
     sysbus_init_irq(dev, &s->irq);
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
 
-    s->nic = qemu_new_nic(&net_stellaris_enet_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_STELLARIS_ENET_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
 
@@ -444,6 +455,7 @@ static TypeInfo stellaris_enet_info = {
 static void stellaris_enet_register_types(void)
 {
     type_register_static(&stellaris_enet_info);
+    type_register_static(&stellaris_enet_net_client_info);
 }
 
 type_init(stellaris_enet_register_types)
diff --git a/hw/usb/dev-network.c b/hw/usb/dev-network.c
index e4a4359..6fa27a9 100644
--- a/hw/usb/dev-network.c
+++ b/hw/usb/dev-network.c
@@ -1329,12 +1329,22 @@ static void usb_net_handle_destroy(USBDevice *dev)
     qemu_del_net_client(&s->nic->nc);
 }
 
-static NetClientInfo net_usbnet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = usbnet_can_receive,
-    .receive = usbnet_receive,
-    .cleanup = usbnet_cleanup,
+#define TYPE_USB_NET_CLIENT "usb-net-client"
+
+static void usb_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = usbnet_can_receive;
+    ncc->receive = usbnet_receive;
+    ncc->cleanup = usbnet_cleanup;
+}
+
+static TypeInfo usb_net_client_info = {
+    .name = TYPE_USB_NET_CLIENT,
+    .parent = TYPE_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = usb_net_client_class_init,
 };
 
 static int usb_net_initfn(USBDevice *dev)
@@ -1354,7 +1364,7 @@ static int usb_net_initfn(USBDevice *dev)
     s->vendorid = 0x1234;
 
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
-    s->nic = qemu_new_nic(&net_usbnet_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_USB_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(s)), s->dev.qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
     snprintf(s->usbstring_mac, sizeof(s->usbstring_mac),
@@ -1439,6 +1449,7 @@ static void usb_net_register_types(void)
 {
     type_register_static(&net_info);
     usb_legacy_register("usb-net", "net", usb_net_init);
+    type_register_static(&usb_net_client_info);
 }
 
 type_init(usb_net_register_types)
diff --git a/hw/virtio-net.c b/hw/virtio-net.c
index 6668ff2..5e108ff 100644
--- a/hw/virtio-net.c
+++ b/hw/virtio-net.c
@@ -989,13 +989,23 @@ static void virtio_net_cleanup(NetClientState *nc)
     n->nic = NULL;
 }
 
-static NetClientInfo net_virtio_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = virtio_net_can_receive,
-    .receive = virtio_net_receive,
-        .cleanup = virtio_net_cleanup,
-    .link_status_changed = virtio_net_set_link_status,
+#define TYPE_VIRTIO_NET_CLIENT "virtio-net-client"
+
+static void virtio_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = virtio_net_can_receive;
+    ncc->receive = virtio_net_receive;
+    ncc->cleanup = virtio_net_cleanup;
+    ncc->link_status_changed = virtio_net_set_link_status;
+}
+
+static TypeInfo virtio_net_client_info = {
+    .name = TYPE_VIRTIO_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = virtio_net_client_class_init,
 };
 
 VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf,
@@ -1036,7 +1046,8 @@ VirtIODevice *virtio_net_init(DeviceState *dev, NICConf *conf,
     memcpy(&n->mac[0], &conf->macaddr, sizeof(n->mac));
     n->status = VIRTIO_NET_S_LINK_UP;
 
-    n->nic = qemu_new_nic(&net_virtio_info, conf, object_get_typename(OBJECT(dev)), dev->id, n);
+    n->nic = qemu_new_nic(TYPE_VIRTIO_NET_CLIENT, conf,
+                          object_get_typename(OBJECT(dev)), dev->id, n);
 
     qemu_format_nic_info_str(&n->nic->nc, conf->macaddr.a);
 
@@ -1082,3 +1093,10 @@ void virtio_net_exit(VirtIODevice *vdev)
     qemu_del_net_client(&n->nic->nc);
     virtio_cleanup(&n->vdev);
 }
+
+static void virtio_net_register_types(void)
+{
+    type_register_static(&virtio_net_client_info);
+}
+
+type_init(virtio_net_register_types)
diff --git a/hw/xen_nic.c b/hw/xen_nic.c
index cf7d559..0512b66 100644
--- a/hw/xen_nic.c
+++ b/hw/xen_nic.c
@@ -300,11 +300,21 @@ static ssize_t net_rx_packet(NetClientState *nc, const uint8_t *buf, size_t size
 
 /* ------------------------------------------------------------- */
 
-static NetClientInfo net_xen_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = net_rx_ok,
-    .receive = net_rx_packet,
+#define TYPE_XEN_NET_CLIENT "xen-net-client"
+
+static void xen_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = net_rx_ok;
+    ncc->receive = net_rx_packet;
+}
+
+static TypeInfo xen_net_client_info = {
+    .name = TYPE_XEN_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = xen_net_client_class_init,
 };
 
 static int net_init(struct XenDevice *xendev)
@@ -327,7 +337,7 @@ static int net_init(struct XenDevice *xendev)
 
     netdev->conf.peer = NULL;
 
-    netdev->nic = qemu_new_nic(&net_xen_info, &netdev->conf,
+    netdev->nic = qemu_new_nic(TYPE_XEN_NET_CLIENT, &netdev->conf,
                                "xen", NULL, netdev);
 
     snprintf(netdev->nic->nc.info_str, sizeof(netdev->nic->nc.info_str),
@@ -437,3 +447,10 @@ struct XenDevOps xen_netdev_ops = {
     .disconnect = net_disconnect,
     .free       = net_free,
 };
+
+static void xen_net_register_types(void)
+{
+    type_register_static(&xen_net_client_info);
+}
+
+type_init(xen_net_register_types)
diff --git a/hw/xgmac.c b/hw/xgmac.c
index a91ef60..41264dc 100644
--- a/hw/xgmac.c
+++ b/hw/xgmac.c
@@ -370,12 +370,23 @@ static void eth_cleanup(NetClientState *nc)
     s->nic = NULL;
 }
 
-static NetClientInfo net_xgmac_enet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = eth_can_rx,
-    .receive = eth_rx,
-    .cleanup = eth_cleanup,
+#define TYPE_XGMAC_ENET_NET_CLIENT "xgmac-enet-net-client"
+
+static void xgmac_enet_net_client_class_init(ObjectClass *klass,
+                                             void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = eth_can_rx;
+    ncc->receive = eth_rx;
+    ncc->cleanup = eth_cleanup;
+}
+
+static TypeInfo xgmac_enet_net_client_info = {
+    .name = TYPE_XGMAC_ENET_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = xgmac_enet_net_client_class_init,
 };
 
 static int xgmac_enet_init(SysBusDevice *dev)
@@ -389,7 +400,7 @@ static int xgmac_enet_init(SysBusDevice *dev)
     sysbus_init_irq(dev, &s->mci_irq);
 
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
-    s->nic = qemu_new_nic(&net_xgmac_enet_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_XGMAC_ENET_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
 
@@ -428,6 +439,7 @@ static TypeInfo xgmac_enet_info = {
 static void xgmac_enet_register_types(void)
 {
     type_register_static(&xgmac_enet_info);
+    type_register_static(&xgmac_enet_net_client_info);
 }
 
 type_init(xgmac_enet_register_types)
diff --git a/hw/xilinx_axienet.c b/hw/xilinx_axienet.c
index eec155d..7cd3f9b 100644
--- a/hw/xilinx_axienet.c
+++ b/hw/xilinx_axienet.c
@@ -830,12 +830,23 @@ axienet_stream_push(StreamSlave *obj, uint8_t *buf, size_t size, uint32_t *hdr)
     enet_update_irq(s);
 }
 
-static NetClientInfo net_xilinx_enet_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = eth_can_rx,
-    .receive = eth_rx,
-    .cleanup = eth_cleanup,
+#define TYPE_XILINX_AXI_NET_CLIENT "xilinx-axi-net-client"
+
+static void xilinx_axi_net_client_class_init(ObjectClass *klass,
+                                             void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = eth_can_rx;
+    ncc->receive = eth_rx;
+    ncc->cleanup = eth_cleanup;
+}
+
+static TypeInfo xilinx_axi_net_client_info = {
+    .name = TYPE_XILINX_AXI_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = xilinx_axi_net_client_class_init,
 };
 
 static int xilinx_enet_init(SysBusDevice *dev)
@@ -848,7 +859,7 @@ static int xilinx_enet_init(SysBusDevice *dev)
     sysbus_init_mmio(dev, &s->iomem);
 
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
-    s->nic = qemu_new_nic(&net_xilinx_enet_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_XILINX_AXI_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
 
@@ -905,6 +916,7 @@ static TypeInfo xilinx_enet_info = {
 static void xilinx_enet_register_types(void)
 {
     type_register_static(&xilinx_enet_info);
+    type_register_static(&xilinx_axi_net_client_info);
 }
 
 type_init(xilinx_enet_register_types)
diff --git a/hw/xilinx_ethlite.c b/hw/xilinx_ethlite.c
index 56ca620..9a88165 100644
--- a/hw/xilinx_ethlite.c
+++ b/hw/xilinx_ethlite.c
@@ -201,12 +201,23 @@ static void eth_cleanup(NetClientState *nc)
     s->nic = NULL;
 }
 
-static NetClientInfo net_xilinx_ethlite_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_NIC,
-    .size = sizeof(NICState),
-    .can_receive = eth_can_rx,
-    .receive = eth_rx,
-    .cleanup = eth_cleanup,
+#define TYPE_XILINX_ETHLITE_NET_CLIENT "xilinx-ethlite-net-client"
+
+static void xilinx_ethlite_net_client_class_init(ObjectClass *klass,
+                                                 void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->can_receive = eth_can_rx;
+    ncc->receive = eth_rx;
+    ncc->cleanup = eth_cleanup;
+}
+
+static TypeInfo xilinx_ethlite_net_client_info = {
+    .name = TYPE_XILINX_ETHLITE_NET_CLIENT,
+    .parent = TYPE_NIC_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = xilinx_ethlite_net_client_class_init,
 };
 
 static int xilinx_ethlite_init(SysBusDevice *dev)
@@ -221,7 +232,7 @@ static int xilinx_ethlite_init(SysBusDevice *dev)
     sysbus_init_mmio(dev, &s->mmio);
 
     qemu_macaddr_default_if_unset(&s->conf.macaddr);
-    s->nic = qemu_new_nic(&net_xilinx_ethlite_info, &s->conf,
+    s->nic = qemu_new_nic(TYPE_XILINX_ETHLITE_NET_CLIENT, &s->conf,
                           object_get_typename(OBJECT(dev)), dev->qdev.id, s);
     qemu_format_nic_info_str(&s->nic->nc, s->conf.macaddr.a);
     return 0;
@@ -253,6 +264,7 @@ static TypeInfo xilinx_ethlite_info = {
 static void xilinx_ethlite_register_types(void)
 {
     type_register_static(&xilinx_ethlite_info);
+    type_register_static(&xilinx_ethlite_net_client_info);
 }
 
 type_init(xilinx_ethlite_register_types)
diff --git a/net.c b/net.c
index 67d2616..edab653 100644
--- a/net.c
+++ b/net.c
@@ -155,8 +155,10 @@ void qemu_macaddr_default_if_unset(MACAddr *macaddr)
 
 static void notify_link_status_changed(NetClientState *nc)
 {
-    if (nc->info->link_status_changed) {
-        nc->info->link_status_changed(nc);
+    NetClientClass *klass = NET_CLIENT_GET_CLASS(nc);
+
+    if (klass->link_status_changed) {
+        klass->link_status_changed(nc);
     }
 }
 
@@ -188,18 +190,15 @@ static char *assign_name(NetClientState *nc1, const char *model)
     return g_strdup(buf);
 }
 
-NetClientState *qemu_new_net_client(NetClientInfo *info,
+NetClientState *qemu_new_net_client(const char *typename,
                                     NetClientState *peer,
                                     const char *model,
                                     const char *name)
 {
     NetClientState *nc;
 
-    assert(info->size >= sizeof(NetClientState));
-
-    nc = g_malloc0(info->size);
+    nc = NET_CLIENT(object_new(typename));
 
-    nc->info = info;
     nc->model = g_strdup(model);
     if (name) {
         nc->name = g_strdup(name);
@@ -219,7 +218,7 @@ NetClientState *qemu_new_net_client(NetClientInfo *info,
     return nc;
 }
 
-NICState *qemu_new_nic(NetClientInfo *info,
+NICState *qemu_new_nic(const char *typename,
                        NICConf *conf,
                        const char *model,
                        const char *name,
@@ -228,12 +227,9 @@ NICState *qemu_new_nic(NetClientInfo *info,
     NetClientState *nc;
     NICState *nic;
 
-    assert(info->type == NET_CLIENT_OPTIONS_KIND_NIC);
-    assert(info->size >= sizeof(NICState));
-
-    nc = qemu_new_net_client(info, conf->peer, model, name);
+    nc = qemu_new_net_client(typename, conf->peer, model, name);
 
-    nic = DO_UPCAST(NICState, nc, nc);
+    nic = NIC_NET_CLIENT(nc);
     nic->conf = conf;
     nic->opaque = opaque;
 
@@ -242,10 +238,12 @@ NICState *qemu_new_nic(NetClientInfo *info,
 
 static void qemu_cleanup_net_client(NetClientState *nc)
 {
+    NetClientClass *klass = NET_CLIENT_GET_CLASS(nc);
+
     QTAILQ_REMOVE(&net_clients, nc, next);
 
-    if (nc->info->cleanup) {
-        nc->info->cleanup(nc);
+    if (klass->cleanup) {
+        klass->cleanup(nc);
     }
 }
 
@@ -259,14 +257,15 @@ static void qemu_free_net_client(NetClientState *nc)
     }
     g_free(nc->name);
     g_free(nc->model);
-    g_free(nc);
+    object_delete(OBJECT(nc));
 }
 
 void qemu_del_net_client(NetClientState *nc)
 {
     /* If there is a peer NIC, delete and cleanup client, but do not free. */
-    if (nc->peer && nc->peer->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
-        NICState *nic = DO_UPCAST(NICState, nc, nc->peer);
+    if (nc->peer && object_dynamic_cast(OBJECT(nc->peer),
+                                        TYPE_NIC_NET_CLIENT)) {
+        NICState *nic = NIC_NET_CLIENT(nc->peer);
         if (nic->peer_deleted) {
             return;
         }
@@ -279,8 +278,8 @@ void qemu_del_net_client(NetClientState *nc)
     }
 
     /* If this is a peer NIC and peer has already been deleted, free it now. */
-    if (nc->peer && nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
-        NICState *nic = DO_UPCAST(NICState, nc, nc);
+    if (nc->peer && object_dynamic_cast(OBJECT(nc), TYPE_NIC_NET_CLIENT)) {
+        NICState *nic = NIC_NET_CLIENT(nc);
         if (nic->peer_deleted) {
             qemu_free_net_client(nc->peer);
         }
@@ -295,22 +294,26 @@ void qemu_foreach_nic(qemu_nic_foreach func, void *opaque)
     NetClientState *nc;
 
     QTAILQ_FOREACH(nc, &net_clients, next) {
-        if (nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC) {
-            func(DO_UPCAST(NICState, nc, nc), opaque);
+        if (object_dynamic_cast(OBJECT(nc), TYPE_NIC_NET_CLIENT)) {
+            func(NIC_NET_CLIENT(nc), opaque);
         }
     }
 }
 
 int qemu_can_send_packet(NetClientState *sender)
 {
+    NetClientClass *klass;
+
     if (!sender->peer) {
         return 1;
     }
 
     if (sender->peer->receive_disabled) {
         return 0;
-    } else if (sender->peer->info->can_receive &&
-               !sender->peer->info->can_receive(sender->peer)) {
+    }
+
+    klass = NET_CLIENT_GET_CLASS(sender->peer);
+    if (klass->can_receive && !klass->can_receive(sender->peer)) {
         return 0;
     }
     return 1;
@@ -323,6 +326,7 @@ ssize_t qemu_deliver_packet(NetClientState *sender,
                             void *opaque)
 {
     NetClientState *nc = opaque;
+    NetClientClass *klass;
     ssize_t ret;
 
     if (nc->link_down) {
@@ -333,10 +337,11 @@ ssize_t qemu_deliver_packet(NetClientState *sender,
         return 0;
     }
 
-    if (flags & QEMU_NET_PACKET_FLAG_RAW && nc->info->receive_raw) {
-        ret = nc->info->receive_raw(nc, data, size);
+    klass = NET_CLIENT_GET_CLASS(nc);
+    if (flags & QEMU_NET_PACKET_FLAG_RAW && klass->receive_raw) {
+        ret = klass->receive_raw(nc, data, size);
     } else {
-        ret = nc->info->receive(nc, data, size);
+        ret = klass->receive(nc, data, size);
     }
 
     if (ret == 0) {
@@ -410,12 +415,13 @@ ssize_t qemu_send_packet_raw(NetClientState *nc, const uint8_t *buf, int size)
 static ssize_t nc_sendv_compat(NetClientState *nc, const struct iovec *iov,
                                int iovcnt)
 {
+    NetClientClass *klass = NET_CLIENT_GET_CLASS(nc);
     uint8_t buffer[4096];
     size_t offset;
 
     offset = iov_to_buf(iov, iovcnt, 0, buffer, sizeof(buffer));
 
-    return nc->info->receive(nc, buffer, offset);
+    return klass->receive(nc, buffer, offset);
 }
 
 ssize_t qemu_deliver_packet_iov(NetClientState *sender,
@@ -425,6 +431,7 @@ ssize_t qemu_deliver_packet_iov(NetClientState *sender,
                                 void *opaque)
 {
     NetClientState *nc = opaque;
+    NetClientClass *klass = NET_CLIENT_GET_CLASS(nc);
     int ret;
 
     if (nc->link_down) {
@@ -435,8 +442,8 @@ ssize_t qemu_deliver_packet_iov(NetClientState *sender,
         return 0;
     }
 
-    if (nc->info->receive_iov) {
-        ret = nc->info->receive_iov(nc, iov, iovcnt);
+    if (klass->receive_iov) {
+        ret = klass->receive_iov(nc, iov, iovcnt);
     } else {
         ret = nc_sendv_compat(nc, iov, iovcnt);
     }
@@ -473,8 +480,10 @@ qemu_sendv_packet(NetClientState *nc, const struct iovec *iov, int iovcnt)
 
 void qemu_net_poll(NetClientState *nc, bool enable)
 {
-    if (nc->info->poll) {
-        nc->info->poll(nc, enable);
+    NetClientClass *klass = NET_CLIENT_GET_CLASS(nc);
+
+    if (klass->poll) {
+        klass->poll(nc, enable);
     }
 }
 
@@ -483,8 +492,9 @@ NetClientState *qemu_find_netdev(const char *id)
     NetClientState *nc;
 
     QTAILQ_FOREACH(nc, &net_clients, next) {
-        if (nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC)
+        if (object_dynamic_cast(OBJECT(nc), TYPE_NIC_NET_CLIENT)) {
             continue;
+        }
         if (!strcmp(nc->name, id)) {
             return nc;
         }
@@ -852,30 +862,30 @@ void qmp_netdev_del(const char *id, Error **errp)
 
 void print_net_client(Monitor *mon, NetClientState *nc)
 {
+    NetClientClass *klass = NET_CLIENT_GET_CLASS(nc);
+
     monitor_printf(mon, "%s: type=%s,%s\n", nc->name,
-                   NetClientOptionsKind_lookup[nc->info->type], nc->info_str);
+                   klass->type_str, nc->info_str);
 }
 
 void do_info_network(Monitor *mon)
 {
     NetClientState *nc, *peer;
-    NetClientOptionsKind type;
 
     net_hub_info(mon);
 
     QTAILQ_FOREACH(nc, &net_clients, next) {
         peer = nc->peer;
-        type = nc->info->type;
 
         /* Skip if already printed in hub info */
         if (net_hub_id_for_client(nc, NULL) == 0) {
             continue;
         }
 
-        if (!peer || type == NET_CLIENT_OPTIONS_KIND_NIC) {
+        if (!peer || object_dynamic_cast(OBJECT(nc), TYPE_NIC_NET_CLIENT)) {
             print_net_client(mon, nc);
         } /* else it's a netdev connected to a NIC, printed with the NIC */
-        if (peer && type == NET_CLIENT_OPTIONS_KIND_NIC) {
+        if (peer && object_dynamic_cast(OBJECT(nc), TYPE_NIC_NET_CLIENT)) {
             monitor_printf(mon, " \\ ");
             print_net_client(mon, peer);
         }
@@ -944,8 +954,9 @@ void net_check_clients(void)
     QTAILQ_FOREACH(nc, &net_clients, next) {
         if (!nc->peer) {
             fprintf(stderr, "Warning: %s %s has no peer\n",
-                    nc->info->type == NET_CLIENT_OPTIONS_KIND_NIC ?
-                    "nic" : "netdev", nc->name);
+                    object_dynamic_cast(OBJECT(nc),
+                        TYPE_NIC_NET_CLIENT) ? "nic" : "netdev",
+                    nc->name);
         }
     }
 
@@ -1056,3 +1067,34 @@ unsigned compute_mcast_idx(const uint8_t *ep)
     }
     return crc >> 26;
 }
+
+static TypeInfo net_client_info = {
+    .name = TYPE_NET_CLIENT,
+    .parent = TYPE_OBJECT,
+    .instance_size = sizeof(NetClientState),
+    .class_size = sizeof(NetClientClass),
+    .abstract = true,
+};
+
+static void nic_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->type_str = "nic";
+}
+
+static TypeInfo nic_net_client_info = {
+    .name = TYPE_NIC_NET_CLIENT,
+    .parent = TYPE_NET_CLIENT,
+    .instance_size = sizeof(NICState),
+    .class_init = nic_net_client_class_init,
+    .abstract = true,
+};
+
+static void net_client_register_types(void)
+{
+    type_register_static(&net_client_info);
+    type_register_static(&nic_net_client_info);
+}
+
+type_init(net_client_register_types)
diff --git a/net.h b/net.h
index 763f926..66cef37 100644
--- a/net.h
+++ b/net.h
@@ -1,6 +1,7 @@
 #ifndef QEMU_NET_H
 #define QEMU_NET_H
 
+#include "qemu/object.h"
 #include "qemu-queue.h"
 #include "qemu-common.h"
 #include "qdict.h"
@@ -29,6 +30,14 @@ typedef struct NICConf {
 
 /* Net clients */
 
+#define TYPE_NET_CLIENT "net-client"
+#define NET_CLIENT_GET_CLASS(obj) \
+   OBJECT_GET_CLASS(NetClientClass, obj, TYPE_NET_CLIENT)
+#define NET_CLIENT_CLASS(klass) \
+   OBJECT_CLASS_CHECK(NetClientClass, klass, TYPE_NET_CLIENT)
+#define NET_CLIENT(obj) \
+   OBJECT_CHECK(NetClientState, obj, TYPE_NET_CLIENT)
+
 typedef void (NetPoll)(NetClientState *, bool enable);
 typedef int (NetCanReceive)(NetClientState *);
 typedef ssize_t (NetReceive)(NetClientState *, const uint8_t *, size_t);
@@ -36,9 +45,10 @@ typedef ssize_t (NetReceiveIOV)(NetClientState *, const struct iovec *, int);
 typedef void (NetCleanup) (NetClientState *);
 typedef void (LinkStatusChanged)(NetClientState *);
 
-typedef struct NetClientInfo {
-    NetClientOptionsKind type;
-    size_t size;
+typedef struct NetClientClass {
+    ObjectClass parent;
+
+    const char *type_str; /* human-readable name for "info network" */
     NetReceive *receive;
     NetReceive *receive_raw;
     NetReceiveIOV *receive_iov;
@@ -46,10 +56,11 @@ typedef struct NetClientInfo {
     NetCleanup *cleanup;
     LinkStatusChanged *link_status_changed;
     NetPoll *poll;
-} NetClientInfo;
+} NetClientClass;
+
+typedef struct NetClientState {
+    Object parent;
 
-struct NetClientState {
-    NetClientInfo *info;
     int link_down;
     QTAILQ_ENTRY(NetClientState) next;
     NetClientState *peer;
@@ -58,7 +69,11 @@ struct NetClientState {
     char *name;
     char info_str[256];
     unsigned receive_disabled : 1;
-};
+} NetClientState;
+
+#define TYPE_NIC_NET_CLIENT "nic-net-client"
+#define NIC_NET_CLIENT(obj) \
+   OBJECT_CHECK(NICState, obj, TYPE_NIC_NET_CLIENT)
 
 typedef struct NICState {
     NetClientState nc;
@@ -68,11 +83,11 @@ typedef struct NICState {
 } NICState;
 
 NetClientState *qemu_find_netdev(const char *id);
-NetClientState *qemu_new_net_client(NetClientInfo *info,
+NetClientState *qemu_new_net_client(const char *typename,
                                     NetClientState *peer,
                                     const char *model,
                                     const char *name);
-NICState *qemu_new_nic(NetClientInfo *info,
+NICState *qemu_new_nic(const char *typename,
                        NICConf *conf,
                        const char *model,
                        const char *name,
diff --git a/net/dump.c b/net/dump.c
index e0a5d74..cd28d02 100644
--- a/net/dump.c
+++ b/net/dump.c
@@ -93,11 +93,22 @@ static void dump_cleanup(NetClientState *nc)
     close(s->fd);
 }
 
-static NetClientInfo net_dump_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_DUMP,
-    .size = sizeof(DumpState),
-    .receive = dump_receive,
-    .cleanup = dump_cleanup,
+#define TYPE_DUMP_NET_CLIENT "dump-net-client"
+
+static void net_dump_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->type_str = "dump",
+    ncc->receive = dump_receive;
+    ncc->cleanup = dump_cleanup;
+}
+
+static TypeInfo net_dump_info = {
+    .name = TYPE_DUMP_NET_CLIENT,
+    .parent = TYPE_NET_CLIENT,
+    .instance_size = sizeof(DumpState),
+    .class_init = net_dump_class_init,
 };
 
 static int net_dump_init(NetClientState *peer, const char *device,
@@ -129,7 +140,7 @@ static int net_dump_init(NetClientState *peer, const char *device,
         return -1;
     }
 
-    nc = qemu_new_net_client(&net_dump_info, peer, device, name);
+    nc = qemu_new_net_client(TYPE_DUMP_NET_CLIENT, peer, device, name);
 
     snprintf(nc->info_str, sizeof(nc->info_str),
              "dump to %s (len=%d)", filename, len);
@@ -183,3 +194,10 @@ int net_init_dump(const NetClientOptions *opts, const char *name,
 
     return net_dump_init(peer, "dump", name, file, len);
 }
+
+static void net_dump_register_types(void)
+{
+    type_register_static(&net_dump_info);
+}
+
+type_init(net_dump_register_types)
diff --git a/net/hub.c b/net/hub.c
index be41301..218d7b0 100644
--- a/net/hub.c
+++ b/net/hub.c
@@ -129,13 +129,27 @@ static void net_hub_port_cleanup(NetClientState *nc)
     QLIST_REMOVE(port, next);
 }
 
-static NetClientInfo net_hub_port_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_HUBPORT,
-    .size = sizeof(NetHubPort),
-    .can_receive = net_hub_port_can_receive,
-    .receive = net_hub_port_receive,
-    .receive_iov = net_hub_port_receive_iov,
-    .cleanup = net_hub_port_cleanup,
+#define TYPE_HUB_PORT_NET_CLIENT "hub-port-net-client"
+#define HUB_PORT(obj) \
+    OBJECT_CHECK(NetHubPort, obj, TYPE_HUB_PORT_NET_CLIENT)
+
+static void net_hub_port_net_client_class_init(ObjectClass *klass,
+                                               void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->type_str = "hubport",
+    ncc->can_receive = net_hub_port_can_receive;
+    ncc->receive = net_hub_port_receive;
+    ncc->receive_iov = net_hub_port_receive_iov;
+    ncc->cleanup = net_hub_port_cleanup;
+}
+
+static TypeInfo hub_port_net_client_info = {
+    .name = TYPE_HUB_PORT_NET_CLIENT,
+    .parent = TYPE_NET_CLIENT,
+    .instance_size = sizeof(NetHubPort),
+    .class_init = net_hub_port_net_client_class_init,
 };
 
 static NetHubPort *net_hub_port_new(NetHub *hub, const char *name)
@@ -151,8 +165,8 @@ static NetHubPort *net_hub_port_new(NetHub *hub, const char *name)
         name = default_name;
     }
 
-    nc = qemu_new_net_client(&net_hub_port_info, NULL, "hub", name);
-    port = DO_UPCAST(NetHubPort, nc, nc);
+    nc = qemu_new_net_client(TYPE_HUB_PORT_NET_CLIENT, NULL, "hub", name);
+    port = HUB_PORT(nc);
     port->id = id;
     port->hub = hub;
 
@@ -262,12 +276,13 @@ int net_hub_id_for_client(NetClientState *nc, int *id)
 {
     NetHubPort *port;
 
-    if (nc->info->type == NET_CLIENT_OPTIONS_KIND_HUBPORT) {
-        port = DO_UPCAST(NetHubPort, nc, nc);
-    } else if (nc->peer != NULL && nc->peer->info->type ==
-            NET_CLIENT_OPTIONS_KIND_HUBPORT) {
-        port = DO_UPCAST(NetHubPort, nc, nc->peer);
-    } else {
+    port = (NetHubPort *)object_dynamic_cast(OBJECT(nc),
+                                             TYPE_HUB_PORT_NET_CLIENT);
+    if (!port && nc->peer) {
+        port = (NetHubPort *)object_dynamic_cast(OBJECT(nc->peer),
+                                                 TYPE_HUB_PORT_NET_CLIENT);
+    }
+    if (!port) {
         return -ENOENT;
     }
 
@@ -314,18 +329,10 @@ void net_hub_check_clients(void)
                 continue;
             }
 
-            switch (peer->info->type) {
-            case NET_CLIENT_OPTIONS_KIND_NIC:
+            if (object_dynamic_cast(OBJECT(peer), TYPE_NIC_NET_CLIENT)) {
                 has_nic = 1;
-                break;
-            case NET_CLIENT_OPTIONS_KIND_USER:
-            case NET_CLIENT_OPTIONS_KIND_TAP:
-            case NET_CLIENT_OPTIONS_KIND_SOCKET:
-            case NET_CLIENT_OPTIONS_KIND_VDE:
+            } else {
                 has_host_dev = 1;
-                break;
-            default:
-                break;
             }
         }
         if (has_host_dev && !has_nic) {
@@ -338,3 +345,10 @@ void net_hub_check_clients(void)
         }
     }
 }
+
+static void net_hub_register_types(void)
+{
+    type_register_static(&hub_port_net_client_info);
+}
+
+type_init(net_hub_register_types)
diff --git a/net/slirp.c b/net/slirp.c
index bf86a44..ec0a6c5 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -123,11 +123,22 @@ static void net_slirp_cleanup(NetClientState *nc)
     QTAILQ_REMOVE(&slirp_stacks, s, entry);
 }
 
-static NetClientInfo net_slirp_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_USER,
-    .size = sizeof(SlirpState),
-    .receive = net_slirp_receive,
-    .cleanup = net_slirp_cleanup,
+#define TYPE_SLIRP_NET_CLIENT "slirp-net-client"
+
+static void net_slirp_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->type_str = "user";
+    ncc->receive = net_slirp_receive;
+    ncc->cleanup = net_slirp_cleanup;
+}
+
+static TypeInfo net_slirp_info = {
+    .name = TYPE_SLIRP_NET_CLIENT,
+    .parent = TYPE_NET_CLIENT,
+    .instance_size = sizeof(SlirpState),
+    .class_init = net_slirp_class_init,
 };
 
 static int net_slirp_init(NetClientState *peer, const char *model,
@@ -233,7 +244,7 @@ static int net_slirp_init(NetClientState *peer, const char *model,
     }
 #endif
 
-    nc = qemu_new_net_client(&net_slirp_info, peer, model, name);
+    nc = qemu_new_net_client(TYPE_SLIRP_NET_CLIENT, peer, model, name);
 
     snprintf(nc->info_str, sizeof(nc->info_str),
              "net=%s,restrict=%s", inet_ntoa(net),
@@ -761,3 +772,9 @@ int net_slirp_parse_legacy(QemuOptsList *opts_list, const char *optarg, int *ret
     return 1;
 }
 
+static void net_slirp_register_types(void)
+{
+    type_register_static(&net_slirp_info);
+}
+
+type_init(net_slirp_register_types)
diff --git a/net/socket.c b/net/socket.c
index b75d567..6d40419 100644
--- a/net/socket.c
+++ b/net/socket.c
@@ -33,6 +33,10 @@
 #include "qemu_socket.h"
 #include "iov.h"
 
+#define TYPE_SOCKET_NET_CLIENT "socket-net-client"
+#define SOCKET_NET_CLIENT(obj) \
+    OBJECT_CHECK(NetSocketState, obj, TYPE_SOCKET_NET_CLIENT)
+
 typedef struct NetSocketState {
     NetClientState nc;
     int listen_fd;
@@ -44,6 +48,8 @@ typedef struct NetSocketState {
     uint8_t buf[4096];
     struct sockaddr_in dgram_dst; /* contains inet host and port destination iff connectionless (SOCK_DGRAM) */
     IOHandler *send_fn;           /* differs between SOCK_STREAM/SOCK_DGRAM */
+    ssize_t (*receive_fn)(struct NetSocketState *s, const uint8_t *buf,
+                          size_t size);
     bool read_poll;               /* waiting to receive data? */
     bool write_poll;              /* waiting to transmit data? */
 } NetSocketState;
@@ -89,9 +95,9 @@ static void net_socket_writable(void *opaque)
     qemu_flush_queued_packets(&s->nc);
 }
 
-static ssize_t net_socket_receive(NetClientState *nc, const uint8_t *buf, size_t size)
+static ssize_t net_socket_receive_stream(NetSocketState *s, const uint8_t *buf,
+                                         size_t size)
 {
-    NetSocketState *s = DO_UPCAST(NetSocketState, nc, nc);
     uint32_t len = htonl(size);
     struct iovec iov[] = {
         {
@@ -124,9 +130,9 @@ static ssize_t net_socket_receive(NetClientState *nc, const uint8_t *buf, size_t
     return size;
 }
 
-static ssize_t net_socket_receive_dgram(NetClientState *nc, const uint8_t *buf, size_t size)
+static ssize_t net_socket_receive_dgram(NetSocketState *s, const uint8_t *buf,
+                                        size_t size)
 {
-    NetSocketState *s = DO_UPCAST(NetSocketState, nc, nc);
     ssize_t ret;
 
     do {
@@ -142,6 +148,13 @@ static ssize_t net_socket_receive_dgram(NetClientState *nc, const uint8_t *buf,
     return ret;
 }
 
+static ssize_t net_socket_receive(NetClientState *nc, const uint8_t *buf,
+                                  size_t size)
+{
+    NetSocketState *s = SOCKET_NET_CLIENT(nc);
+    return s->receive_fn(s, buf, size);
+}
+
 static void net_socket_send(void *opaque)
 {
     NetSocketState *s = opaque;
@@ -320,7 +333,7 @@ fail:
 
 static void net_socket_cleanup(NetClientState *nc)
 {
-    NetSocketState *s = DO_UPCAST(NetSocketState, nc, nc);
+    NetSocketState *s = SOCKET_NET_CLIENT(nc);
     if (s->fd != -1) {
         net_socket_read_poll(s, false);
         net_socket_write_poll(s, false);
@@ -334,13 +347,6 @@ static void net_socket_cleanup(NetClientState *nc)
     }
 }
 
-static NetClientInfo net_dgram_socket_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_SOCKET,
-    .size = sizeof(NetSocketState),
-    .receive = net_socket_receive_dgram,
-    .cleanup = net_socket_cleanup,
-};
-
 static NetSocketState *net_socket_fd_init_dgram(NetClientState *peer,
                                                 const char *model,
                                                 const char *name,
@@ -383,18 +389,19 @@ static NetSocketState *net_socket_fd_init_dgram(NetClientState *peer,
         }
     }
 
-    nc = qemu_new_net_client(&net_dgram_socket_info, peer, model, name);
+    nc = qemu_new_net_client(TYPE_SOCKET_NET_CLIENT, peer, model, name);
 
     snprintf(nc->info_str, sizeof(nc->info_str),
             "socket: fd=%d (%s mcast=%s:%d)",
             fd, is_connected ? "cloned" : "",
             inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port));
 
-    s = DO_UPCAST(NetSocketState, nc, nc);
+    s = SOCKET_NET_CLIENT(nc);
 
     s->fd = fd;
     s->listen_fd = -1;
     s->send_fn = net_socket_send_dgram;
+    s->receive_fn = net_socket_receive_dgram;
     net_socket_read_poll(s, true);
 
     /* mcast: save bound address as dst */
@@ -413,16 +420,10 @@ static void net_socket_connect(void *opaque)
 {
     NetSocketState *s = opaque;
     s->send_fn = net_socket_send;
+    s->receive_fn = net_socket_receive_stream;
     net_socket_read_poll(s, true);
 }
 
-static NetClientInfo net_socket_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_SOCKET,
-    .size = sizeof(NetSocketState),
-    .receive = net_socket_receive,
-    .cleanup = net_socket_cleanup,
-};
-
 static NetSocketState *net_socket_fd_init_stream(NetClientState *peer,
                                                  const char *model,
                                                  const char *name,
@@ -431,11 +432,11 @@ static NetSocketState *net_socket_fd_init_stream(NetClientState *peer,
     NetClientState *nc;
     NetSocketState *s;
 
-    nc = qemu_new_net_client(&net_socket_info, peer, model, name);
+    nc = qemu_new_net_client(TYPE_SOCKET_NET_CLIENT, peer, model, name);
 
     snprintf(nc->info_str, sizeof(nc->info_str), "socket: fd=%d", fd);
 
-    s = DO_UPCAST(NetSocketState, nc, nc);
+    s = SOCKET_NET_CLIENT(nc);
 
     s->fd = fd;
     s->listen_fd = -1;
@@ -537,8 +538,8 @@ static int net_socket_listen_init(NetClientState *peer,
         return -1;
     }
 
-    nc = qemu_new_net_client(&net_socket_info, peer, model, name);
-    s = DO_UPCAST(NetSocketState, nc, nc);
+    nc = qemu_new_net_client(TYPE_SOCKET_NET_CLIENT, peer, model, name);
+    s = SOCKET_NET_CLIENT(nc);
     s->fd = -1;
     s->listen_fd = fd;
     s->nc.link_down = true;
@@ -753,3 +754,26 @@ int net_init_socket(const NetClientOptions *opts, const char *name,
     }
     return 0;
 }
+
+static void net_socket_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->type_str = "socket";
+    ncc->receive = net_socket_receive;
+    ncc->cleanup = net_socket_cleanup;
+}
+
+static TypeInfo net_socket_info = {
+    .name = TYPE_SOCKET_NET_CLIENT,
+    .parent = TYPE_NET_CLIENT,
+    .instance_size = sizeof(NetSocketState),
+    .class_init = net_socket_class_init,
+};
+
+static void net_socket_register_types(void)
+{
+    type_register_static(&net_socket_info);
+}
+
+type_init(net_socket_register_types)
diff --git a/net/tap-win32.c b/net/tap-win32.c
index f1801e2..db2a11a 100644
--- a/net/tap-win32.c
+++ b/net/tap-win32.c
@@ -666,11 +666,23 @@ static void tap_win32_send(void *opaque)
     }
 }
 
-static NetClientInfo net_tap_win32_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_TAP,
-    .size = sizeof(TAPState),
-    .receive = tap_receive,
-    .cleanup = tap_cleanup,
+#define TYPE_TAP_WIN32_NET_CLIENT "tap-win32-net-client"
+
+static void tap_win32_net_client_class_init(ObjectClass *klass,
+                                            void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->type_str = "tap";
+    ncc->receive = tap_receive;
+    ncc->cleanup = tap_cleanup;
+}
+
+static TypeInfo tap_win32_net_client_info = {
+    .name = TYPE_TAP_WIN32_NET_CLIENT,
+    .parent = TYPE_NET_CLIENT,
+    .instance_size = sizeof(TAPState),
+    .class_init = tap_win32_net_client_class_init,
 };
 
 static int tap_win32_init(NetClientState *peer, const char *model,
@@ -751,3 +763,10 @@ struct vhost_net *tap_get_vhost_net(NetClientState *nc)
 {
     return NULL;
 }
+
+static void tap_win32_register_types(void)
+{
+    type_register_static(&tap_win32_net_client_info);
+}
+
+type_init(tap_win32_register_types)
diff --git a/net/tap.c b/net/tap.c
index 22f8de7..0de84e4 100644
--- a/net/tap.c
+++ b/net/tap.c
@@ -50,6 +50,10 @@
  */
 #define TAP_BUFSIZE (4096 + 65536)
 
+#define TYPE_TAP_NET_CLIENT "tap-net-client"
+#define TAP_NET_CLIENT(obj) \
+    OBJECT_CHECK(TAPState, obj, TYPE_TAP_NET_CLIENT)
+
 typedef struct TAPState {
     NetClientState nc;
     int fd;
@@ -119,7 +123,7 @@ static ssize_t tap_write_packet(TAPState *s, const struct iovec *iov, int iovcnt
 static ssize_t tap_receive_iov(NetClientState *nc, const struct iovec *iov,
                                int iovcnt)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
+    TAPState *s = TAP_NET_CLIENT(nc);
     const struct iovec *iovp = iov;
     struct iovec iov_copy[iovcnt + 1];
     struct virtio_net_hdr_mrg_rxbuf hdr = { };
@@ -137,7 +141,7 @@ static ssize_t tap_receive_iov(NetClientState *nc, const struct iovec *iov,
 
 static ssize_t tap_receive_raw(NetClientState *nc, const uint8_t *buf, size_t size)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
+    TAPState *s = TAP_NET_CLIENT(nc);
     struct iovec iov[2];
     int iovcnt = 0;
     struct virtio_net_hdr_mrg_rxbuf hdr = { };
@@ -157,7 +161,7 @@ static ssize_t tap_receive_raw(NetClientState *nc, const uint8_t *buf, size_t si
 
 static ssize_t tap_receive(NetClientState *nc, const uint8_t *buf, size_t size)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
+    TAPState *s = TAP_NET_CLIENT(nc);
     struct iovec iov[1];
 
     if (s->host_vnet_hdr_len && !s->using_vnet_hdr) {
@@ -186,7 +190,7 @@ ssize_t tap_read_packet(int tapfd, uint8_t *buf, int maxlen)
 
 static void tap_send_completed(NetClientState *nc, ssize_t len)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
+    TAPState *s = TAP_NET_CLIENT(nc);
     tap_read_poll(s, 1);
 }
 
@@ -217,36 +221,26 @@ static void tap_send(void *opaque)
 
 int tap_has_ufo(NetClientState *nc)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
-
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
-
+    TAPState *s = TAP_NET_CLIENT(nc);
     return s->has_ufo;
 }
 
 int tap_has_vnet_hdr(NetClientState *nc)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
-
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
-
+    TAPState *s = TAP_NET_CLIENT(nc);
     return !!s->host_vnet_hdr_len;
 }
 
 int tap_has_vnet_hdr_len(NetClientState *nc, int len)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
-
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
-
+    TAPState *s = TAP_NET_CLIENT(nc);
     return tap_probe_vnet_hdr_len(s->fd, len);
 }
 
 void tap_set_vnet_hdr_len(NetClientState *nc, int len)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
+    TAPState *s = TAP_NET_CLIENT(nc);
 
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
     assert(len == sizeof(struct virtio_net_hdr_mrg_rxbuf) ||
            len == sizeof(struct virtio_net_hdr));
 
@@ -256,11 +250,10 @@ void tap_set_vnet_hdr_len(NetClientState *nc, int len)
 
 void tap_using_vnet_hdr(NetClientState *nc, int using_vnet_hdr)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
+    TAPState *s = TAP_NET_CLIENT(nc);
 
     using_vnet_hdr = using_vnet_hdr != 0;
 
-    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;
@@ -269,7 +262,7 @@ void tap_using_vnet_hdr(NetClientState *nc, int using_vnet_hdr)
 void tap_set_offload(NetClientState *nc, int csum, int tso4,
                      int tso6, int ecn, int ufo)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
+    TAPState *s = TAP_NET_CLIENT(nc);
     if (s->fd < 0) {
         return;
     }
@@ -279,7 +272,7 @@ void tap_set_offload(NetClientState *nc, int csum, int tso4,
 
 static void tap_cleanup(NetClientState *nc)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
+    TAPState *s = TAP_NET_CLIENT(nc);
 
     if (s->vhost_net) {
         vhost_net_cleanup(s->vhost_net);
@@ -299,33 +292,41 @@ static void tap_cleanup(NetClientState *nc)
 
 static void tap_poll(NetClientState *nc, bool enable)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
+    TAPState *s = TAP_NET_CLIENT(nc);
     tap_read_poll(s, enable);
     tap_write_poll(s, enable);
 }
 
 bool net_is_tap_client(NetClientState *nc)
 {
-    return nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP;
+    return object_dynamic_cast(OBJECT(nc), TYPE_TAP_NET_CLIENT);
 }
 
 int tap_get_fd(NetClientState *nc)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
+    TAPState *s = TAP_NET_CLIENT(nc);
     return s->fd;
 }
 
 /* fd support */
 
-static NetClientInfo net_tap_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_TAP,
-    .size = sizeof(TAPState),
-    .receive = tap_receive,
-    .receive_raw = tap_receive_raw,
-    .receive_iov = tap_receive_iov,
-    .poll = tap_poll,
-    .cleanup = tap_cleanup,
+static void tap_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->type_str = "tap";
+    ncc->receive = tap_receive;
+    ncc->receive_raw = tap_receive_raw;
+    ncc->receive_iov = tap_receive_iov;
+    ncc->poll = tap_poll;
+    ncc->cleanup = tap_cleanup;
+}
+
+static TypeInfo tap_net_client_info = {
+    .name = TYPE_TAP_NET_CLIENT,
+    .parent = TYPE_NET_CLIENT,
+    .instance_size = sizeof(TAPState),
+    .class_init = tap_net_client_class_init,
 };
 
 static TAPState *net_tap_fd_init(NetClientState *peer,
@@ -337,9 +338,9 @@ static TAPState *net_tap_fd_init(NetClientState *peer,
     NetClientState *nc;
     TAPState *s;
 
-    nc = qemu_new_net_client(&net_tap_info, peer, model, name);
+    nc = qemu_new_net_client(TYPE_TAP_NET_CLIENT, peer, model, name);
 
-    s = DO_UPCAST(TAPState, nc, nc);
+    s = TAP_NET_CLIENT(nc);
 
     s->fd = fd;
     s->host_vnet_hdr_len = vnet_hdr ? sizeof(struct virtio_net_hdr) : 0;
@@ -716,7 +717,13 @@ int net_init_tap(const NetClientOptions *opts, const char *name,
 
 VHostNetState *tap_get_vhost_net(NetClientState *nc)
 {
-    TAPState *s = DO_UPCAST(TAPState, nc, nc);
-    assert(nc->info->type == NET_CLIENT_OPTIONS_KIND_TAP);
+    TAPState *s = TAP_NET_CLIENT(nc);
     return s->vhost_net;
 }
+
+static void tap_register_types(void)
+{
+    type_register_static(&tap_net_client_info);
+}
+
+type_init(tap_register_types)
diff --git a/net/vde.c b/net/vde.c
index 275bda9..dee9652 100644
--- a/net/vde.c
+++ b/net/vde.c
@@ -67,11 +67,22 @@ static void vde_cleanup(NetClientState *nc)
     vde_close(s->vde);
 }
 
-static NetClientInfo net_vde_info = {
-    .type = NET_CLIENT_OPTIONS_KIND_VDE,
-    .size = sizeof(VDEState),
-    .receive = vde_receive,
-    .cleanup = vde_cleanup,
+#define TYPE_VDE_NET_CLIENT "vde-net-client"
+
+static void vde_net_client_class_init(ObjectClass *klass, void *class_data)
+{
+    NetClientClass *ncc = NET_CLIENT_CLASS(klass);
+
+    ncc->type_str = "vde";
+    ncc->receive = vde_receive;
+    ncc->cleanup = vde_cleanup;
+}
+
+static TypeInfo vde_net_client_info = {
+    .name = TYPE_VDE_NET_CLIENT,
+    .parent = TYPE_NET_CLIENT,
+    .instance_size = sizeof(VDEState),
+    .class_init = vde_net_client_class_init,
 };
 
 static int net_vde_init(NetClientState *peer, const char *model,
@@ -125,3 +136,10 @@ int net_init_vde(const NetClientOptions *opts, const char *name,
 
     return 0;
 }
+
+static void vde_register_types(void)
+{
+    type_register_static(&vde_net_client_info);
+}
+
+type_init(vde_register_types)
