github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/qemu/devices.go (about) 1 // Copyright 2018 the u-root Authors. All rights reserved 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package qemu 6 7 import ( 8 "fmt" 9 "net" 10 "sync/atomic" 11 ) 12 13 // Device is a QEMU device to expose to a VM. 14 type Device interface { 15 // Cmdline returns arguments to append to the QEMU command line for this device. 16 Cmdline() []string 17 // KArgs returns arguments that must be passed to the kernel for this device, or nil. 18 KArgs() []string 19 } 20 21 // Network is a Device that can connect multiple QEMU VMs to each other. 22 // 23 // Network uses the QEMU socket mechanism to connect multiple VMs with a simple 24 // TCP socket. 25 type Network struct { 26 port uint16 27 28 // numVMs must be atomically accessed so VMs can be started in parallel 29 // in goroutines. 30 numVMs uint32 31 } 32 33 // NewNetwork creates a new QEMU network between QEMU VMs. 34 // 35 // The network is closed from the world and only between the QEMU VMs. 36 func NewNetwork() *Network { 37 return &Network{ 38 port: 1234, 39 } 40 } 41 42 // NetworkOpt returns additional QEMU command-line parameters based on the net 43 // device ID. 44 type NetworkOpt func(netdev string) []string 45 46 // WithPCAP captures network traffic and saves it to outputFile. 47 func WithPCAP(outputFile string) NetworkOpt { 48 return func(netdev string) []string { 49 return []string{ 50 "-object", 51 // TODO(chrisko): generate an ID instead of 'f1'. 52 fmt.Sprintf("filter-dump,id=f1,netdev=%s,file=%s", netdev, outputFile), 53 } 54 } 55 } 56 57 // NewVM returns a Device that can be used with a new QEMU VM. 58 func (n *Network) NewVM(nopts ...NetworkOpt) Device { 59 if n == nil { 60 return nil 61 } 62 63 newNum := atomic.AddUint32(&n.numVMs, 1) 64 num := newNum - 1 65 66 // MAC for the virtualized NIC. 67 // 68 // This is from the range of locally administered address ranges. 69 mac := net.HardwareAddr{0x0e, 0x00, 0x00, 0x00, 0x00, byte(num)} 70 devID := fmt.Sprintf("vm%d", num) 71 72 args := []string{"-device", fmt.Sprintf("e1000,netdev=%s,mac=%s", devID, mac)} 73 // Note: QEMU in CircleCI seems to in solve cases fail when using just ':1234' format. 74 // It fails with "address resolution failed for :1234: Name or service not known" 75 // hinting that this is somehow related to DNS resolution. To work around this, 76 // we explicitly bind to 127.0.0.1 (IPv6 [::1] is not parsed correctly by QEMU). 77 if num != 0 { 78 args = append(args, "-netdev", fmt.Sprintf("socket,id=%s,connect=127.0.0.1:%d", devID, n.port)) 79 } else { 80 args = append(args, "-netdev", fmt.Sprintf("socket,id=%s,listen=127.0.0.1:%d", devID, n.port)) 81 } 82 83 for _, opt := range nopts { 84 args = append(args, opt(devID)...) 85 } 86 return networkImpl{args} 87 } 88 89 type networkImpl struct { 90 args []string 91 } 92 93 func (n networkImpl) Cmdline() []string { 94 return n.args 95 } 96 97 func (n networkImpl) KArgs() []string { return nil } 98 99 // ReadOnlyDirectory is a Device that exposes a directory as a /dev/sda1 100 // readonly vfat partition in the VM. 101 type ReadOnlyDirectory struct { 102 // Dir is the directory to expose as a read-only vfat partition. 103 Dir string 104 } 105 106 func (rod ReadOnlyDirectory) Cmdline() []string { 107 if len(rod.Dir) == 0 { 108 return nil 109 } 110 111 // Expose the temp directory to QEMU as /dev/sda1 112 return []string{ 113 "-drive", fmt.Sprintf("file=fat:rw:%s,if=none,id=tmpdir", rod.Dir), 114 "-device", "ich9-ahci,id=ahci", 115 "-device", "ide-drive,drive=tmpdir,bus=ahci.0", 116 } 117 } 118 119 func (ReadOnlyDirectory) KArgs() []string { return nil } 120 121 // IDEBlockDevice emulates an AHCI/IDE block device. 122 type IDEBlockDevice struct { 123 File string 124 } 125 126 func (ibd IDEBlockDevice) Cmdline() []string { 127 if len(ibd.File) == 0 { 128 return nil 129 } 130 131 // There's a better way to do this. I don't know what it is. Some day 132 // I'll learn QEMU's crazy command line. 133 // 134 // I wish someone would make a proto representation of the 135 // command-line. Would be so much more understandable how device 136 // backends and frontends relate to each other. 137 return []string{ 138 "-device", "ich9-ahci,id=ahci", 139 "-device", "ide-drive,drive=disk,bus=ahci.0", 140 "-drive", fmt.Sprintf("file=%s,if=none,id=disk", ibd.File), 141 } 142 } 143 func (IDEBlockDevice) KArgs() []string { return nil } 144 145 // P9Directory is a Device that exposes a directory as a Plan9 (9p) 146 // read-write filesystem in the VM. 147 type P9Directory struct { 148 // Dir is the directory to expose as read-write 9p filesystem. 149 Dir string 150 151 // Boot: if true, indicates this is the root volume. For this to work, 152 // kernel args will need to be added - use KArgs() to get the args. There 153 // can only be one boot 9pfs at a time. 154 Boot bool 155 156 // Tag is an identifier that is used within the VM when mounting an fs, 157 // e.g. 'mount -t 9p my-vol-ident mountpoint'. If not specified, a default 158 // tag of 'tmpdir' will be used. 159 // 160 // Ignored if Boot is true, as the tag in that case is special. 161 // 162 // For non-boot devices, this is also used as the id linking the `-fsdev` 163 // and `-device` args together. 164 // 165 // Because the tag must be unique for each dir, if multiple non-boot 166 // P9Directory's are used, tag may be omitted for no more than one. 167 Tag string 168 169 // Arch is the architecture under test. This is used to determine the 170 // QEMU command line args. 171 Arch string 172 } 173 174 func (p P9Directory) Cmdline() []string { 175 if len(p.Dir) == 0 { 176 return nil 177 } 178 179 var tag, id string 180 if p.Boot { 181 tag = "/dev/root" 182 } else { 183 tag = p.Tag 184 if len(tag) == 0 { 185 tag = "tmpdir" 186 } 187 } 188 if p.Boot { 189 id = "rootdrv" 190 } else { 191 id = tag 192 } 193 194 // Expose the temp directory to QEMU 195 var deviceArgs string 196 switch p.Arch { 197 case "arm": 198 deviceArgs = fmt.Sprintf("virtio-9p-device,fsdev=%s,mount_tag=%s", id, tag) 199 default: 200 deviceArgs = fmt.Sprintf("virtio-9p-pci,fsdev=%s,mount_tag=%s", id, tag) 201 } 202 203 return []string{ 204 // security_model=mapped-file seems to be the best choice. It gives 205 // us control over uid/gid/mode seen in the guest, without requiring 206 // elevated perms on the host. 207 "-fsdev", fmt.Sprintf("local,id=%s,path=%s,security_model=mapped-file", id, p.Dir), 208 "-device", deviceArgs, 209 } 210 } 211 212 func (p P9Directory) KArgs() []string { 213 if len(p.Dir) == 0 { 214 return nil 215 } 216 if p.Boot { 217 return []string{ 218 "devtmpfs.mount=1", 219 "root=/dev/root", 220 "rootfstype=9p", 221 "rootflags=trans=virtio,version=9p2000.L", 222 } 223 } 224 return []string{ 225 //seen as an env var by the init process 226 "UROOT_USE_9P=1", 227 } 228 } 229 230 // VirtioRandom is a Device that exposes a PCI random number generator to the 231 // QEMU VM. 232 type VirtioRandom struct{} 233 234 func (VirtioRandom) Cmdline() []string { 235 return []string{"-device", "virtio-rng-pci"} 236 } 237 238 func (VirtioRandom) KArgs() []string { return nil } 239 240 // ArbitraryArgs is a Device that allows users to add arbitrary arguments to 241 // the QEMU command line. 242 type ArbitraryArgs []string 243 244 func (aa ArbitraryArgs) Cmdline() []string { 245 return aa 246 } 247 248 func (ArbitraryArgs) KArgs() []string { return nil }