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 }