github.com/hugelgupf/u-root@v0.0.0-20191023214958-4807c632154c/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 // NewVM returns a Device that can be used with a new QEMU VM. 43 func (n *Network) NewVM() Device { 44 if n == nil { 45 return nil 46 } 47 48 newNum := atomic.AddUint32(&n.numVMs, 1) 49 num := newNum - 1 50 51 // MAC for the virtualized NIC. 52 // 53 // This is from the range of locally administered address ranges. 54 mac := net.HardwareAddr{0x0e, 0x00, 0x00, 0x00, 0x00, byte(num)} 55 56 args := []string{"-net", fmt.Sprintf("nic,macaddr=%s", mac)} 57 // Note: QEMU in CircleCI seems to in solve cases fail when using just ':1234' format. 58 // It fails with "address resolution failed for :1234: Name or service not known" 59 // hinting that this is somehow related to DNS resolution. To work around this, 60 // we explicitly bind to 127.0.0.1 (IPv6 [::1] is not parsed correctly by QEMU). 61 if num != 0 { 62 args = append(args, "-net", fmt.Sprintf("socket,connect=127.0.0.1:%d", n.port)) 63 } else { 64 args = append(args, "-net", fmt.Sprintf("socket,listen=127.0.0.1:%d", n.port)) 65 } 66 return networkImpl{args} 67 } 68 69 type networkImpl struct { 70 args []string 71 } 72 73 func (n networkImpl) Cmdline() []string { 74 return n.args 75 } 76 77 func (n networkImpl) KArgs() []string { return nil } 78 79 // ReadOnlyDirectory is a Device that exposes a directory as a /dev/sda1 80 // readonly vfat partition in the VM. 81 type ReadOnlyDirectory struct { 82 // Dir is the directory to expose as a read-only vfat partition. 83 Dir string 84 } 85 86 func (rod ReadOnlyDirectory) Cmdline() []string { 87 if len(rod.Dir) == 0 { 88 return nil 89 } 90 91 // Expose the temp directory to QEMU as /dev/sda1 92 return []string{ 93 "-drive", fmt.Sprintf("file=fat:rw:%s,if=none,id=tmpdir", rod.Dir), 94 "-device", "ich9-ahci,id=ahci", 95 "-device", "ide-drive,drive=tmpdir,bus=ahci.0", 96 } 97 } 98 99 func (ReadOnlyDirectory) KArgs() []string { return nil } 100 101 // P9Directory is a Device that exposes a directory as a Plan9 (9p) 102 // read-write filesystem in the VM. 103 type P9Directory struct { 104 // Dir is the directory to expose as read-write 9p filesystem. 105 Dir string 106 107 // Boot: if true, indicates this is the root volume. For this to work, 108 // kernel args will need to be added - use KArgs() to get the args. There 109 // can only be one boot 9pfs at a time. 110 Boot bool 111 112 // Tag is an identifier that is used within the VM when mounting an fs, 113 // e.g. 'mount -t 9p my-vol-ident mountpoint'. If not specified, a default 114 // tag of 'tmpdir' will be used. 115 // 116 // Ignored if Boot is true, as the tag in that case is special. 117 // 118 // For non-boot devices, this is also used as the id linking the `-fsdev` 119 // and `-device` args together. 120 // 121 // Because the tag must be unique for each dir, if multiple non-boot 122 // P9Directory's are used, tag may be omitted for no more than one. 123 Tag string 124 } 125 126 func (p P9Directory) Cmdline() []string { 127 if len(p.Dir) == 0 { 128 return nil 129 } 130 131 var tag, id string 132 if p.Boot { 133 tag = "/dev/root" 134 } else { 135 tag = p.Tag 136 if len(tag) == 0 { 137 tag = "tmpdir" 138 } 139 } 140 if p.Boot { 141 id = "rootdrv" 142 } else { 143 id = tag 144 } 145 146 // Expose the temp directory to QEMU 147 return []string{ 148 // security_model=mapped-file seems to be the best choice. It gives 149 // us control over uid/gid/mode seen in the guest, without requiring 150 // elevated perms on the host. 151 "-fsdev", fmt.Sprintf("local,id=%s,path=%s,security_model=mapped-file", id, p.Dir), 152 "-device", fmt.Sprintf("virtio-9p-pci,fsdev=%s,mount_tag=%s", id, tag), 153 } 154 } 155 156 func (p P9Directory) KArgs() []string { 157 if len(p.Dir) == 0 { 158 return nil 159 } 160 if p.Boot { 161 return []string{ 162 "devtmpfs.mount=1", 163 "root=/dev/root", 164 "rootfstype=9p", 165 "rootflags=trans=virtio,version=9p2000.L", 166 } 167 } 168 return []string{ 169 //seen as an env var by the init process 170 "UROOT_USE_9P=1", 171 } 172 } 173 174 // VirtioRandom is a Device that exposes a PCI random number generator to the 175 // QEMU VM. 176 type VirtioRandom struct{} 177 178 func (VirtioRandom) Cmdline() []string { 179 return []string{"-device", "virtio-rng-pci"} 180 } 181 182 func (VirtioRandom) KArgs() []string { return nil } 183 184 // ArbitraryArgs is a Device that allows users to add arbitrary arguments to 185 // the QEMU command line. 186 type ArbitraryArgs []string 187 188 func (aa ArbitraryArgs) Cmdline() []string { 189 return aa 190 } 191 192 func (ArbitraryArgs) KArgs() []string { return nil }