Merge pull request #497 from jcvenegas/vsock-runtime5

Use VSOCK when is available in the host.
This commit is contained in:
Peng Tao
2018-08-01 09:55:05 +08:00
committed by GitHub
21 changed files with 411 additions and 83 deletions

4
Gopkg.lock generated
View File

@@ -92,7 +92,7 @@
[[projects]]
name = "github.com/intel/govmm"
packages = ["qemu"]
revision = "ff2401825e0930811919c86c36d64b113aa00083"
revision = "6ff20ae2f409df976574d0139b5ec2fa3e314769"
[[projects]]
name = "github.com/kata-containers/agent"
@@ -264,6 +264,6 @@
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
inputs-digest = "ea3d6532c4375832a1c79d70af45e6722e526bde97f6caf23d90b91267a3cf0b"
inputs-digest = "f57c595789df5fbc01e237cc52c7454911dbc37b9282304b6b1f076606e4c157"
solver-name = "gps-cdcl"
solver-version = 1

View File

@@ -56,7 +56,7 @@
[[constraint]]
name = "github.com/intel/govmm"
revision = "ff2401825e0930811919c86c36d64b113aa00083"
revision = "6ff20ae2f409df976574d0139b5ec2fa3e314769"
[[constraint]]
name = "github.com/kata-containers/agent"

View File

@@ -15,6 +15,7 @@ import (
"github.com/BurntSushi/toml"
vc "github.com/kata-containers/runtime/virtcontainers"
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
"github.com/kata-containers/runtime/virtcontainers/utils"
"github.com/sirupsen/logrus"
)
@@ -91,6 +92,7 @@ type hypervisor struct {
Debug bool `toml:"enable_debug"`
DisableNestingChecks bool `toml:"disable_nesting_checks"`
EnableIOThreads bool `toml:"enable_iothreads"`
UseVSock bool `toml:"use_vsock"`
}
type proxy struct {
@@ -267,6 +269,10 @@ func (h hypervisor) msize9p() uint32 {
return h.Msize9p
}
func (h hypervisor) useVSock() bool {
return h.UseVSock
}
func (p proxy) path() string {
if p.Path == "" {
return defaultProxyPath
@@ -333,6 +339,16 @@ func newQemuHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
return vc.HypervisorConfig{}, err
}
useVSock := false
if h.useVSock() {
if utils.SupportsVsocks() {
kataLog.Info("vsock supported")
useVSock = true
} else {
kataLog.Warn("No vsock support, falling back to legacy serial port")
}
}
return vc.HypervisorConfig{
HypervisorPath: hypervisor,
KernelPath: kernel,
@@ -355,6 +371,7 @@ func newQemuHypervisorConfig(h hypervisor) (vc.HypervisorConfig, error) {
BlockDeviceDriver: blockDriver,
EnableIOThreads: h.EnableIOThreads,
Msize9p: h.msize9p(),
UseVSock: useVSock,
}, nil
}
@@ -411,8 +428,9 @@ func updateRuntimeConfig(configPath string, tomlConf tomlConfig, config *oci.Run
case kataAgentTableType:
config.AgentType = kataAgentTableType
config.AgentConfig = vc.KataAgentConfig{}
config.AgentConfig = vc.KataAgentConfig{
UseVSock: config.HypervisorConfig.UseVSock,
}
}
}
@@ -544,6 +562,13 @@ func loadConfiguration(configPath string, ignoreLogging bool) (resolvedConfigPat
return "", config, err
}
// use no proxy if HypervisorConfig.UseVSock is true
if config.HypervisorConfig.UseVSock {
kataLog.Info("VSOCK supported, configure to not use proxy")
config.ProxyType = vc.NoProxyType
config.ProxyConfig = vc.ProxyConfig{}
}
return resolved, config, nil
}

View File

@@ -134,6 +134,12 @@ enable_iothreads = @DEFENABLEIOTHREADS@
# used for 9p packet payload.
#msize_9p = @DEFMSIZE9P@
# If true and vsocks are supported, use vsocks to communicate directly
# with the agent and no proxy is started, otherwise use unix
# sockets and start a proxy to communicate with the agent.
# Default false
#use_vsock = true
[factory]
# VM templating support. Once enabled, new VMs are created from template
# using vm cloning. They will share the same initial kernel, initramfs and

View File

@@ -20,6 +20,7 @@ import (
vc "github.com/kata-containers/runtime/virtcontainers"
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
"github.com/kata-containers/runtime/virtcontainers/utils"
"github.com/stretchr/testify/assert"
)
@@ -552,6 +553,52 @@ func TestMinimalRuntimeConfig(t *testing.T) {
t.Fatalf("Got %+v\n expecting %+v", config, expectedConfig)
}
// minimal config with vsock enabled
runtimeMinimalConfig = `
# Runtime configuration file
[hypervisor.qemu]
use_vsock = true
[proxy.kata]
path = "` + proxyPath + `"
[shim.kata]
path = "` + shimPath + `"
[agent.kata]
`
orgVHostVSockDevicePath := utils.VHostVSockDevicePath
orgVSockDevicePath := utils.VSockDevicePath
defer func() {
utils.VHostVSockDevicePath = orgVHostVSockDevicePath
utils.VSockDevicePath = orgVSockDevicePath
}()
utils.VHostVSockDevicePath = "/dev/null"
utils.VSockDevicePath = "/dev/null"
configPath = path.Join(dir, "runtime.toml")
err = createConfig(configPath, runtimeMinimalConfig)
if err != nil {
t.Fatal(err)
}
_, config, err = loadConfiguration(configPath, false)
if err != nil {
t.Fatal(err)
}
if config.ProxyType != vc.NoProxyType {
t.Fatalf("Proxy type must be NoProxy, got %+v", config.ProxyType)
}
if !reflect.DeepEqual(config.ProxyConfig, vc.ProxyConfig{}) {
t.Fatalf("Got %+v\n expecting %+v", config.ProxyConfig, vc.ProxyConfig{})
}
if config.HypervisorConfig.UseVSock != true {
t.Fatalf("use_vsock must be true, got %v", config.HypervisorConfig.UseVSock)
}
if err := os.Remove(configPath); err != nil {
t.Fatal(err)
}
@@ -570,6 +617,14 @@ func TestNewQemuHypervisorConfig(t *testing.T) {
machineType := "machineType"
disableBlock := true
enableIOThreads := true
orgVSockDevicePath := utils.VSockDevicePath
orgVHostVSockDevicePath := utils.VHostVSockDevicePath
defer func() {
utils.VSockDevicePath = orgVSockDevicePath
utils.VHostVSockDevicePath = orgVHostVSockDevicePath
}()
utils.VSockDevicePath = "/dev/abc/xyz"
utils.VHostVSockDevicePath = "/dev/abc/xyz"
hypervisor := hypervisor{
Path: hypervisorPath,
@@ -578,6 +633,7 @@ func TestNewQemuHypervisorConfig(t *testing.T) {
MachineType: machineType,
DisableBlockDeviceUse: disableBlock,
EnableIOThreads: enableIOThreads,
UseVSock: true,
}
files := []string{hypervisorPath, kernelPath, imagePath}
@@ -597,12 +653,21 @@ func TestNewQemuHypervisorConfig(t *testing.T) {
}
}
// all paths exist now
// falling back to legacy serial port
config, err := newQemuHypervisorConfig(hypervisor)
if err != nil {
t.Fatal(err)
}
utils.VSockDevicePath = "/dev/null"
utils.VHostVSockDevicePath = "/dev/null"
// all paths exist now
config, err = newQemuHypervisorConfig(hypervisor)
if err != nil {
t.Fatal(err)
}
if config.HypervisorPath != hypervisor.Path {
t.Errorf("Expected hypervisor path %v, got %v", hypervisor.Path, config.HypervisorPath)
}

View File

@@ -11,19 +11,20 @@ import (
"os"
"strings"
runtim "runtime"
"github.com/BurntSushi/toml"
vc "github.com/kata-containers/runtime/virtcontainers"
"github.com/kata-containers/runtime/virtcontainers/pkg/oci"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/urfave/cli"
runtim "runtime"
)
// Semantic version for the output of the command.
//
// XXX: Increment for every change to the output format
// (meaning any change to the EnvInfo type).
const formatVersion = "1.0.12"
const formatVersion = "1.0.13"
// MetaInfo stores information on the format of the output itself
type MetaInfo struct {
@@ -80,6 +81,7 @@ type HypervisorInfo struct {
BlockDeviceDriver string
Msize9p uint32
Debug bool
UseVSock bool
}
// ProxyInfo stores proxy details
@@ -212,6 +214,10 @@ func getHostInfo() (HostInfo, error) {
}
func getProxyInfo(config oci.RuntimeConfig) (ProxyInfo, error) {
if config.ProxyType == vc.NoProxyType {
return ProxyInfo{Type: string(config.ProxyType)}, nil
}
version, err := getCommandVersion(defaultProxyPath)
if err != nil {
version = unknown
@@ -276,6 +282,7 @@ func getHypervisorInfo(config oci.RuntimeConfig) HypervisorInfo {
Path: hypervisorPath,
BlockDeviceDriver: config.HypervisorConfig.BlockDeviceDriver,
Msize9p: config.HypervisorConfig.Msize9p,
UseVSock: config.HypervisorConfig.UseVSock,
}
}

View File

@@ -82,6 +82,9 @@ const (
// VirtioSerialPort is the serial port device driver.
VirtioSerialPort = "virtserialport"
// VHostVSockPCI is the vhost vsock pci driver.
VHostVSockPCI = "vhost-vsock-pci"
)
// ObjectType is a string representing a qemu object type.
@@ -962,6 +965,12 @@ type VSOCKDevice struct {
ID string
ContextID uint32
// VHostFD vhost file descriptor that holds the ContextID
VHostFD *os.File
// DisableModern prevents qemu from relying on fast MMIO.
DisableModern bool
}
const (
@@ -986,12 +995,22 @@ func (vsock VSOCKDevice) Valid() bool {
// QemuParams returns the qemu parameters built out of the VSOCK device.
func (vsock VSOCKDevice) QemuParams(config *Config) []string {
var deviceParams []string
var qemuParams []string
deviceParam := fmt.Sprintf("%s,id=%s,%s=%d", VhostVSOCKPCI, vsock.ID, VSOCKGuestCID, vsock.ContextID)
deviceParams = append(deviceParams, fmt.Sprintf("%s", VhostVSOCKPCI))
if vsock.DisableModern {
deviceParams = append(deviceParams, ",disable-modern=true")
}
if vsock.VHostFD != nil {
qemuFDs := config.appendFDs([]*os.File{vsock.VHostFD})
deviceParams = append(deviceParams, fmt.Sprintf(",vhostfd=%d", qemuFDs[0]))
}
deviceParams = append(deviceParams, fmt.Sprintf(",id=%s", vsock.ID))
deviceParams = append(deviceParams, fmt.Sprintf(",%s=%d", VSOCKGuestCID, vsock.ContextID))
qemuParams = append(qemuParams, "-device")
qemuParams = append(qemuParams, deviceParam)
qemuParams = append(qemuParams, strings.Join(deviceParams, ""))
return qemuParams
}

View File

@@ -881,3 +881,13 @@ func (q *QMP) ExecHotplugMemory(ctx context.Context, qomtype, id, mempath string
return err
}
// ExecutePCIVSockAdd adds a vhost-vsock-pci bus
func (q *QMP) ExecutePCIVSockAdd(ctx context.Context, id, guestCID string) error {
args := map[string]interface{}{
"driver": VHostVSockPCI,
"id": id,
"guest-cid": guestCID,
}
return q.executeCommand(ctx, "device_add", args, nil)
}

View File

@@ -128,7 +128,7 @@ func TestNewAgentConfigFromHyperstartAgentType(t *testing.T) {
}
func TestNewAgentConfigFromKataAgentType(t *testing.T) {
agentConfig := KataAgentConfig{}
agentConfig := KataAgentConfig{UseVSock: true}
sandboxConfig := SandboxConfig{
AgentType: KataContainersAgent,

View File

@@ -68,6 +68,9 @@ const (
// SerialPortDev is the serial port device type.
serialPortDev
// vSockPCIDev is the vhost vsock PCI device type.
vSockPCIDev
// VFIODevice is VFIO device type
vfioDev
@@ -217,6 +220,9 @@ type HypervisorConfig struct {
// Msize9p is used as the msize for 9p shares
Msize9p uint32
// UseVSock use a vsock for agent communication
UseVSock bool
// BootToBeTemplate used to indicate if the VM is created to be a template VM
BootToBeTemplate bool

View File

@@ -47,25 +47,29 @@ var (
kataGuestSandboxDir = "/run/kata-containers/sandbox/"
type9pFs = "9p"
vsockSocketScheme = "vsock"
kata9pDevType = "9p"
kataBlkDevType = "blk"
kataSCSIDevType = "scsi"
sharedDir9pOptions = []string{"trans=virtio,version=9p2000.L", "nodev"}
shmDir = "shm"
kataEphemeralDevType = "ephemeral"
ephemeralPath = filepath.Join(kataGuestSandboxDir, kataEphemeralDevType)
// port numbers below 1024 are called privileged ports. Only a process with
// CAP_NET_BIND_SERVICE capability may bind to these port numbers.
vSockPort = 1024
kata9pDevType = "9p"
kataBlkDevType = "blk"
kataSCSIDevType = "scsi"
sharedDir9pOptions = []string{"trans=virtio,version=9p2000.L", "nodev"}
shmDir = "shm"
kataEphemeralDevType = "ephemeral"
ephemeralPath = filepath.Join(kataGuestSandboxDir, kataEphemeralDevType)
)
// KataAgentConfig is a structure storing information needed
// to reach the Kata Containers agent.
type KataAgentConfig struct {
GRPCSocket string
LongLiveConn bool
UseVSock bool
}
type kataVSOCK struct {
contextID uint32
port uint32
vhostFd *os.File
}
func (s *kataVSOCK) String() string {
@@ -99,27 +103,6 @@ func (k *kataAgent) Logger() *logrus.Entry {
return virtLog.WithField("subsystem", "kata_agent")
}
func parseVSOCKAddr(sock string) (uint32, uint32, error) {
sp := strings.Split(sock, ":")
if len(sp) != 3 {
return 0, 0, fmt.Errorf("Invalid vsock address: %s", sock)
}
if sp[0] != vsockSocketScheme {
return 0, 0, fmt.Errorf("Invalid vsock URL scheme: %s", sp[0])
}
cid, err := strconv.ParseUint(sp[1], 10, 32)
if err != nil {
return 0, 0, fmt.Errorf("Invalid vsock cid: %s", sp[1])
}
port, err := strconv.ParseUint(sp[2], 10, 32)
if err != nil {
return 0, 0, fmt.Errorf("Invalid vsock port: %s", sp[2])
}
return uint32(cid), uint32(port), nil
}
func (k *kataAgent) getVMPath(id string) string {
return filepath.Join(RunVMStoragePath, id)
}
@@ -129,8 +112,13 @@ func (k *kataAgent) getSharePath(id string) string {
}
func (k *kataAgent) generateVMSocket(id string, c KataAgentConfig) error {
cid, port, err := parseVSOCKAddr(c.GRPCSocket)
if err != nil {
if c.UseVSock {
// We want to go through VSOCK. The VM VSOCK endpoint will be our gRPC.
k.Logger().Debug("agent: Using vsock VM socket endpoint")
// We dont know yet the context ID - set empty vsock configuration
k.vmSocket = kataVSOCK{}
} else {
k.Logger().Debug("agent: Using unix socket form VM socket endpoint")
// We need to generate a host UNIX socket path for the emulated serial port.
kataSock, err := utils.BuildSocketPath(k.getVMPath(id), defaultKataSocketName)
if err != nil {
@@ -143,12 +131,6 @@ func (k *kataAgent) generateVMSocket(id string, c KataAgentConfig) error {
HostPath: kataSock,
Name: defaultKataChannel,
}
} else {
// We want to go through VSOCK. The VM VSOCK endpoint will be our gRPC.
k.vmSocket = kataVSOCK{
contextID: cid,
port: port,
}
}
return nil
@@ -225,7 +207,16 @@ func (k *kataAgent) configure(h hypervisor, id, sharePath string, builtin bool,
return err
}
case kataVSOCK:
// TODO Add an hypervisor vsock
var err error
s.vhostFd, s.contextID, err = utils.FindContextID()
if err != nil {
return err
}
s.port = uint32(vSockPort)
if err := h.addDevice(s, vSockPCIDev); err != nil {
return err
}
k.vmSocket = s
default:
return fmt.Errorf("Invalid config type")
}
@@ -1201,6 +1192,7 @@ func (k *kataAgent) connect() error {
return nil
}
k.Logger().WithField("url", k.state.URL).Info("New client")
client, err := kataclient.NewAgentClient(k.state.URL, k.proxyBuiltIn)
if err != nil {
return err

View File

@@ -671,13 +671,12 @@ func TestAgentPathAPI(t *testing.T) {
assert.Nil(err)
assert.Equal(k1, k2)
c.GRPCSocket = "unixsocket"
err = k1.generateVMSocket(id, c)
assert.Nil(err)
_, ok := k1.vmSocket.(Socket)
assert.True(ok)
c.GRPCSocket = "vsock:100:200"
c.UseVSock = true
err = k2.generateVMSocket(id, c)
assert.Nil(err)
_, ok = k2.vmSocket.(kataVSOCK)
@@ -692,7 +691,7 @@ func TestAgentConfigure(t *testing.T) {
k := &kataAgent{}
h := &mockHypervisor{}
c := KataAgentConfig{GRPCSocket: "vsock:100:200"}
c := KataAgentConfig{}
id := "foobar"
invalidAgent := HyperConfig{}
@@ -702,7 +701,6 @@ func TestAgentConfigure(t *testing.T) {
err = k.configure(h, id, dir, true, c)
assert.Nil(err)
c.GRPCSocket = "foobarfoobar"
err = k.configure(h, id, dir, true, c)
assert.Nil(err)
@@ -710,36 +708,6 @@ func TestAgentConfigure(t *testing.T) {
assert.Nil(err)
}
func TestParseVSOCKAddr(t *testing.T) {
assert := assert.New(t)
sock := "randomfoobar"
_, _, err := parseVSOCKAddr(sock)
assert.Error(err)
sock = "vsock://1:2"
_, _, err = parseVSOCKAddr(sock)
assert.Error(err)
sock = "unix:1:2"
_, _, err = parseVSOCKAddr(sock)
assert.Error(err)
sock = "vsock:foo:2"
_, _, err = parseVSOCKAddr(sock)
assert.Error(err)
sock = "vsock:1:bar"
_, _, err = parseVSOCKAddr(sock)
assert.Error(err)
sock = "vsock:1:2"
cid, port, err := parseVSOCKAddr(sock)
assert.Nil(err)
assert.Equal(cid, uint32(1))
assert.Equal(port, uint32(2))
}
func TestCmdToKataProcess(t *testing.T) {
assert := assert.New(t)

View File

@@ -24,6 +24,7 @@ func (p *kataProxy) consoleWatched() bool {
// start is kataProxy start implementation for proxy interface.
func (p *kataProxy) start(sandbox *Sandbox, params proxyParams) (int, string, error) {
sandbox.Logger().Info("Starting regular Kata proxy rather than built-in")
if sandbox.agent == nil {
return -1, "", fmt.Errorf("No agent")
}

View File

@@ -24,6 +24,7 @@ type noProxy struct {
// start is noProxy start implementation for proxy interface.
func (p *noProxy) start(sandbox *Sandbox, params proxyParams) (int, string, error) {
sandbox.Logger().Info("No proxy started because of no-proxy implementation")
if params.agentURL == "" {
return -1, "", fmt.Errorf("AgentURL cannot be empty")
}

View File

@@ -62,6 +62,10 @@ type qemu struct {
state QemuState
arch qemuArch
// fds is a list of file descriptors inherited by QEMU process
// they'll be closed once QEMU process is running
fds []*os.File
}
const (
@@ -497,6 +501,14 @@ func (q *qemu) startSandbox() error {
q.Logger().WithField("default-kernel-parameters", formatted).Debug()
}
defer func() {
for _, fd := range q.fds {
if err := fd.Close(); err != nil {
q.Logger().WithError(err).Error("After launching Qemu")
}
}
}()
strErr, err := govmmQemu.LaunchQemu(q.qemuConfig, newQMPLogger())
if err != nil {
return fmt.Errorf("%s", strErr)
@@ -956,6 +968,9 @@ func (q *qemu) addDevice(devInfo interface{}, devType deviceType) error {
q.qemuConfig.Devices = q.arch.append9PVolume(q.qemuConfig.Devices, v)
case Socket:
q.qemuConfig.Devices = q.arch.appendSocket(q.qemuConfig.Devices, v)
case kataVSOCK:
q.fds = append(q.fds, v.vhostFd)
q.qemuConfig.Devices = q.arch.appendVSockPCI(q.qemuConfig.Devices, v)
case Endpoint:
q.qemuConfig.Devices = q.arch.appendNetwork(q.qemuConfig.Devices, v)
case deviceDrivers.Drive:

View File

@@ -68,6 +68,9 @@ type qemuArch interface {
// appendSocket appends a socket to devices
appendSocket(devices []govmmQemu.Device, socket Socket) []govmmQemu.Device
// appendVSockPCI appends a vsock PCI to devices
appendVSockPCI(devices []govmmQemu.Device, vsock kataVSOCK) []govmmQemu.Device
// appendNetwork appends a endpoint device to devices
appendNetwork(devices []govmmQemu.Device, endpoint Endpoint) []govmmQemu.Device
@@ -389,6 +392,20 @@ func (q *qemuArchBase) appendSocket(devices []govmmQemu.Device, socket Socket) [
return devices
}
func (q *qemuArchBase) appendVSockPCI(devices []govmmQemu.Device, vsock kataVSOCK) []govmmQemu.Device {
devices = append(devices,
govmmQemu.VSOCKDevice{
ID: fmt.Sprintf("vsock-%d", vsock.contextID),
ContextID: vsock.contextID,
VHostFD: vsock.vhostFd,
DisableModern: q.nestedRun,
},
)
return devices
}
func networkModelToQemuType(model NetInterworkingModel) govmmQemu.NetDeviceType {
switch model {
case NetXConnectBridgedModel:

View File

@@ -246,6 +246,28 @@ func TestQemuAddDeviceSerialPortDev(t *testing.T) {
testQemuAddDevice(t, socket, serialPortDev, expectedOut)
}
func TestQemuAddDeviceKataVSOCK(t *testing.T) {
contextID := uint32(3)
port := uint32(1024)
vHostFD := os.NewFile(1, "vsock")
expectedOut := []govmmQemu.Device{
govmmQemu.VSOCKDevice{
ID: fmt.Sprintf("vsock-%d", contextID),
ContextID: contextID,
VHostFD: vHostFD,
},
}
vsock := kataVSOCK{
contextID: contextID,
port: port,
vhostFd: vHostFD,
}
testQemuAddDevice(t, vsock, vSockPCIDev, expectedOut)
}
func TestQemuGetSandboxConsole(t *testing.T) {
q := &qemu{}
sandboxID := "testSandboxID"

View File

@@ -23,6 +23,12 @@ const fileMode0755 = os.FileMode(0755)
// See unix(7).
const MaxSocketPathLen = 107
// VSockDevicePath path to vsock device
var VSockDevicePath = "/dev/vsock"
// VHostVSockDevicePath path to vhost-vsock device
var VHostVSockDevicePath = "/dev/vhost-vsock"
// FileCopy copys files from srcPath to dstPath
func FileCopy(srcPath, dstPath string) error {
if srcPath == "" {
@@ -200,3 +206,16 @@ func BuildSocketPath(elements ...string) (string, error) {
return result, nil
}
// SupportsVsocks returns true if vsocks are supported, otherwise false
func SupportsVsocks() bool {
if _, err := os.Stat(VSockDevicePath); err != nil {
return false
}
if _, err := os.Stat(VHostVSockDevicePath); err != nil {
return false
}
return true
}

View File

@@ -0,0 +1,89 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package utils
import (
"crypto/rand"
"fmt"
"math/big"
"os"
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
// from <linux/vhost.h>
// VHOST_VSOCK_SET_GUEST_CID = _IOW(VHOST_VIRTIO, 0x60, __u64)
const ioctlVhostVsockSetGuestCid = 0x4008AF60
var ioctlFunc = ioctl
var maxUInt uint32 = 1<<32 - 1
func ioctl(fd uintptr, request int, arg1 uint32) error {
if _, _, errno := unix.Syscall(
unix.SYS_IOCTL,
fd,
uintptr(request),
uintptr(unsafe.Pointer(&arg1)),
); errno != 0 {
return os.NewSyscallError("ioctl", fmt.Errorf("%d", int(errno)))
}
return nil
}
// FindContextID finds a unique context ID by generating a random number between 3 and max unsigned int (maxUint).
// Using the ioctl VHOST_VSOCK_SET_GUEST_CID, findContextID asks to the kernel if the given
// context ID (N) is available, when the context ID is not available, incrementing by 1 findContextID
// iterates from N to maxUint until an available context ID is found, otherwise decrementing by 1
// findContextID iterates from N to 3 until an available context ID is found, this is the last chance
// to find a context ID available.
// On success vhost file and a context ID greater or equal than 3 are returned, otherwise 0 and an error are returned.
// vhost file can be used to send vhost file decriptor to QEMU. It's the caller's responsibility to
// close vhost file descriptor.
//
// Benefits of using random context IDs:
// - Reduce the probability of a *DoS attack*, since other processes don't know whatis the initial context ID
// used by findContextID to find a context ID available
//
func FindContextID() (*os.File, uint32, error) {
// context IDs 0x0, 0x1 and 0x2 are reserved, 0x3 is the first context ID usable.
var firstContextID uint32 = 0x3
var contextID = firstContextID
// Generate a random number
n, err := rand.Int(rand.Reader, big.NewInt(int64(maxUInt)))
if err == nil && n.Int64() >= int64(firstContextID) {
contextID = uint32(n.Int64())
}
// Open vhost-vsock device to check what context ID is available.
// This file descriptor holds/locks the context ID and it should be
// inherited by QEMU process.
vsockFd, err := os.OpenFile(VHostVSockDevicePath, syscall.O_RDWR, 0666)
if err != nil {
return nil, 0, err
}
// Looking for the first available context ID.
for cid := contextID; cid <= maxUInt; cid++ {
if err := ioctlFunc(vsockFd.Fd(), ioctlVhostVsockSetGuestCid, cid); err == nil {
return vsockFd, cid, nil
}
}
// Last chance to get a free context ID.
for cid := contextID - 1; cid >= firstContextID; cid-- {
if err := ioctlFunc(vsockFd.Fd(), ioctlVhostVsockSetGuestCid, cid); err == nil {
return vsockFd, cid, nil
}
}
vsockFd.Close()
return nil, 0, fmt.Errorf("Could not get a unique context ID for the vsock")
}

View File

@@ -0,0 +1,35 @@
// Copyright (c) 2018 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package utils
import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
)
func TestFindContextID(t *testing.T) {
assert := assert.New(t)
ioctlFunc = func(fd uintptr, request int, arg1 uint32) error {
return errors.New("ioctl")
}
orgVHostVSockDevicePath := VHostVSockDevicePath
orgMaxUInt := maxUInt
defer func() {
VHostVSockDevicePath = orgVHostVSockDevicePath
maxUInt = orgMaxUInt
}()
VHostVSockDevicePath = "/dev/null"
maxUInt = uint32(1000000)
f, cid, err := FindContextID()
assert.Nil(f)
assert.Zero(cid)
assert.Error(err)
}

View File

@@ -294,3 +294,34 @@ func TestBuildSocketPath(t *testing.T) {
assert.Equal(d.expected, result)
}
}
func TestSupportsVsocks(t *testing.T) {
assert := assert.New(t)
orgVSockDevicePath := VSockDevicePath
orgVHostVSockDevicePath := VHostVSockDevicePath
defer func() {
VSockDevicePath = orgVSockDevicePath
VHostVSockDevicePath = orgVHostVSockDevicePath
}()
VSockDevicePath = "/abc/xyz/123"
VHostVSockDevicePath = "/abc/xyz/123"
assert.False(SupportsVsocks())
vSockDeviceFile, err := ioutil.TempFile("", "vsock")
assert.NoError(err)
defer os.Remove(vSockDeviceFile.Name())
defer vSockDeviceFile.Close()
VSockDevicePath = vSockDeviceFile.Name()
assert.False(SupportsVsocks())
vHostVSockFile, err := ioutil.TempFile("", "vhost-vsock")
assert.NoError(err)
defer os.Remove(vHostVSockFile.Name())
defer vHostVSockFile.Close()
VHostVSockDevicePath = vHostVSockFile.Name()
assert.True(SupportsVsocks())
}