github.com/zaolin/u-root@v0.0.0-20200428085104-64aaafd46c6d/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 // P9Directory is a Device that exposes a directory as a Plan9 (9p) 122 // read-write filesystem in the VM. 123 type P9Directory struct { 124 // Dir is the directory to expose as read-write 9p filesystem. 125 Dir string 126 127 // Boot: if true, indicates this is the root volume. For this to work, 128 // kernel args will need to be added - use KArgs() to get the args. There 129 // can only be one boot 9pfs at a time. 130 Boot bool 131 132 // Tag is an identifier that is used within the VM when mounting an fs, 133 // e.g. 'mount -t 9p my-vol-ident mountpoint'. If not specified, a default 134 // tag of 'tmpdir' will be used. 135 // 136 // Ignored if Boot is true, as the tag in that case is special. 137 // 138 // For non-boot devices, this is also used as the id linking the `-fsdev` 139 // and `-device` args together. 140 // 141 // Because the tag must be unique for each dir, if multiple non-boot 142 // P9Directory's are used, tag may be omitted for no more than one. 143 Tag string 144 145 // Arch is the architecture under test. This is used to determine the 146 // QEMU command line args. 147 Arch string 148 } 149 150 func (p P9Directory) Cmdline() []string { 151 if len(p.Dir) == 0 { 152 return nil 153 } 154 155 var tag, id string 156 if p.Boot { 157 tag = "/dev/root" 158 } else { 159 tag = p.Tag 160 if len(tag) == 0 { 161 tag = "tmpdir" 162 } 163 } 164 if p.Boot { 165 id = "rootdrv" 166 } else { 167 id = tag 168 } 169 170 // Expose the temp directory to QEMU 171 var deviceArgs string 172 switch p.Arch { 173 case "arm": 174 deviceArgs = fmt.Sprintf("virtio-9p-device,fsdev=%s,mount_tag=%s", id, tag) 175 default: 176 deviceArgs = fmt.Sprintf("virtio-9p-pci,fsdev=%s,mount_tag=%s", id, tag) 177 } 178 179 return []string{ 180 // security_model=mapped-file seems to be the best choice. It gives 181 // us control over uid/gid/mode seen in the guest, without requiring 182 // elevated perms on the host. 183 "-fsdev", fmt.Sprintf("local,id=%s,path=%s,security_model=mapped-file", id, p.Dir), 184 "-device", deviceArgs, 185 } 186 } 187 188 func (p P9Directory) KArgs() []string { 189 if len(p.Dir) == 0 { 190 return nil 191 } 192 if p.Boot { 193 return []string{ 194 "devtmpfs.mount=1", 195 "root=/dev/root", 196 "rootfstype=9p", 197 "rootflags=trans=virtio,version=9p2000.L", 198 } 199 } 200 return []string{ 201 //seen as an env var by the init process 202 "UROOT_USE_9P=1", 203 } 204 } 205 206 // VirtioRandom is a Device that exposes a PCI random number generator to the 207 // QEMU VM. 208 type VirtioRandom struct{} 209 210 func (VirtioRandom) Cmdline() []string { 211 return []string{"-device", "virtio-rng-pci"} 212 } 213 214 func (VirtioRandom) KArgs() []string { return nil } 215 216 // ArbitraryArgs is a Device that allows users to add arbitrary arguments to 217 // the QEMU command line. 218 type ArbitraryArgs []string 219 220 func (aa ArbitraryArgs) Cmdline() []string { 221 return aa 222 } 223 224 func (ArbitraryArgs) KArgs() []string { return nil }