[qemu] Formalize netdev backends

Introduce the Tap backend as well.

Change-Id: I71e1270ab5a392ff83403fc8c650833b5fe1d663
diff --git a/qemu/config.go b/qemu/config.go
index 495e770..42047f8 100644
--- a/qemu/config.go
+++ b/qemu/config.go
@@ -46,6 +46,18 @@
 	// ID is the network device identifier.
 	ID string
 
+	// User is a netdev user backend.
+	User *NetdevUser
+
+	// Tap is a netdev tap backend.
+	Tap *NetdevTap
+
+	// MAC is the network device MAC address.
+	MAC string
+}
+
+// NetdevUser defines a netdev backend giving user networking.
+type NetdevUser struct {
 	// Network is the network block.
 	Network string
 
@@ -60,9 +72,12 @@
 
 	// Forwards are the host forwardings.
 	Forwards []Forward
+}
 
-	// MAC is the network device MAC address.
-	MAC string
+// NetdevTap defines a netdev backend giving a tap interface.
+type NetdevTap struct {
+	// Name is the name of the interface.
+	Name string
 }
 
 // Config gives a high-level configuration for QEMU on Fuchsia.
@@ -162,22 +177,37 @@
 	}
 
 	for _, n := range cfg.Networks {
+		if n.ID == "" {
+			return nil, fmt.Errorf("a network must have an ID")
+		}
+
 		var netdev strings.Builder
-		fmt.Fprintf(&netdev, "user,id=%s", n.ID)
-		if n.Network != "" {
-			fmt.Fprintf(&netdev, ",net=%s", n.Network)
-		}
-		if n.DHCPStart != "" {
-			fmt.Fprintf(&netdev, ",dhcpstart=%s", n.DHCPStart)
-		}
-		if n.DNS != "" {
-			fmt.Fprintf(&netdev, ",dns=%s", n.DNS)
-		}
-		if n.Host != "" {
-			fmt.Fprintf(&netdev, ",host=%s", n.Host)
-		}
-		for _, f := range n.Forwards {
-			fmt.Fprintf(&netdev, ",hostfwd=tcp::%d-:%d", f.HostPort, f.GuestPort)
+		if n.Tap != nil {
+			if n.Tap.Name == "" {
+				return nil, fmt.Errorf("network %q must specify a TAP interface name", n.ID)
+			}
+			// Overwrite any default configuration scripts with none; there is not currently a
+			// good use case for these parameters.
+			fmt.Fprintf(&netdev, "tap,id=%s,ifname=%s,script=no,downscript=no", n.ID, n.Tap.Name)
+		} else if n.User != nil {
+			fmt.Fprintf(&netdev, "user,id=%s", n.ID)
+			if n.User.Network != "" {
+				fmt.Fprintf(&netdev, ",net=%s", n.User.Network)
+			}
+			if n.User.DHCPStart != "" {
+				fmt.Fprintf(&netdev, ",dhcpstart=%s", n.User.DHCPStart)
+			}
+			if n.User.DNS != "" {
+				fmt.Fprintf(&netdev, ",dns=%s", n.User.DNS)
+			}
+			if n.User.Host != "" {
+				fmt.Fprintf(&netdev, ",host=%s", n.User.Host)
+			}
+			for _, f := range n.User.Forwards {
+				fmt.Fprintf(&netdev, ",hostfwd=tcp::%d-:%d", f.HostPort, f.GuestPort)
+			}
+		} else {
+			return nil, fmt.Errorf("network %q must specify a netdev backend", n.ID)
 		}
 		invocation = append(invocation, "-netdev", netdev.String())
 
@@ -188,6 +218,10 @@
 		}
 		invocation = append(invocation, "-device", device.String())
 	}
+	// Treat the absense of specified networks as a directive to disable networking entirely.
+	if len(cfg.Networks) == 0 {
+		invocation = append(invocation, "-net", "none")
+	}
 
 	invocation = append(invocation, "-append", strings.Join(cmdlineArgs, " "))
 	return invocation, nil