Skip to content
This repository has been archived by the owner on May 12, 2021. It is now read-only.

channel: Check for channel type in kernel cmdline options #508

Merged
merged 1 commit into from
Apr 2, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,22 @@ var collatedTrace = false
// if true, coredump when an internal error occurs or a fatal signal is received
var crashOnError = false

// commType is used to denote the communication channel type used.
type commType int

egernst marked this conversation as resolved.
Show resolved Hide resolved
const (
// virtio-serial channel
serialCh commType = iota

// vsock channel
vsockCh
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because there's a new line and comments between serialCh and vsockCh, and unknownCh, I think both vsockCh and unknownCh are going to be typed as int instead of commType.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sboeuf I was able to assign between those values, I think compile would have complained in that case.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can still compile since it's a cast later in the code, but unless the Go compiler got smarter, this was a problem I had encountered before.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No golang will never cast automatically.
Looks like newline are allowed according to this wiki: https://github.com/golang/go/wiki/Iota


// channel type not passed explicitly
unknownCh
)

var commCh = unknownCh

// This is the list of file descriptors we can properly close after the process
// has been started. When the new process is exec(), those file descriptors are
// duplicated and it is our responsibility to close them since we have opened
Expand Down
114 changes: 91 additions & 23 deletions channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,24 +50,29 @@ func newChannel(ctx context.Context) (channel, error) {
defer span.Finish()

var serialErr error
var serialPath string
var vsockErr error
var vSockSupported bool
var ch channel

for i := 0; i < channelExistMaxTries; i++ {
// check vsock path
if _, err := os.Stat(vSockDevPath); err == nil {
if vSockSupported, vsockErr = isAFVSockSupportedFunc(); vSockSupported && vsockErr == nil {
span.SetTag("channel-type", "vsock")
return &vSockChannel{}, nil
switch commCh {
case serialCh:
if ch, serialErr = checkForSerialChannel(ctx); serialErr == nil && ch.(*serialChannel) != nil {
return ch, nil
}
case vsockCh:
if ch, vsockErr = checkForVsockChannel(ctx); vsockErr == nil && ch.(*vSockChannel) != nil {
return ch, nil
}
}

// Check serial port path
if serialPath, serialErr = findVirtualSerialPath(serialChannelName); serialErr == nil {
span.SetTag("channel-type", "serial")
span.SetTag("serial-path", serialPath)
return &serialChannel{serialPath: serialPath}, nil
case unknownCh:
// If we have not been explicitly passed if vsock is used or not, maybe due to
// an older runtime, try to check for vsock support.
if ch, vsockErr = checkForVsockChannel(ctx); vsockErr == nil && ch.(*vSockChannel) != nil {
return ch, nil
}
if ch, serialErr = checkForSerialChannel(ctx); serialErr == nil && ch.(*serialChannel) != nil {
return ch, nil
}
}

time.Sleep(channelExistWaitTime)
Expand All @@ -84,6 +89,41 @@ func newChannel(ctx context.Context) (channel, error) {
return nil, fmt.Errorf("Neither vsocks nor serial ports were found")
}

func checkForSerialChannel(ctx context.Context) (*serialChannel, error) {
span, _ := trace(ctx, "channel", "checkForSerialChannel")
defer span.Finish()

// Check serial port path
serialPath, serialErr := findVirtualSerialPath(serialChannelName)
if serialErr == nil {
span.SetTag("channel-type", "serial")
span.SetTag("serial-path", serialPath)
agentLog.Debug("Serial channel type detected")
return &serialChannel{serialPath: serialPath}, nil
}

return nil, serialErr
}

func checkForVsockChannel(ctx context.Context) (*vSockChannel, error) {
span, _ := trace(ctx, "channel", "checkForVsockChannel")
defer span.Finish()

// check vsock path
if _, err := os.Stat(vSockDevPath); err != nil {
return nil, err
}

vSockSupported, vsockErr := isAFVSockSupportedFunc()
if vSockSupported && vsockErr == nil {
span.SetTag("channel-type", "vsock")
agentLog.Debug("Vsock channel type detected")
return &vSockChannel{}, nil
}

return nil, fmt.Errorf("Vsock not found : %s", vsockErr)
}

type vSockChannel struct {
}

Expand Down Expand Up @@ -228,23 +268,51 @@ func (c *serialChannel) teardown() error {
return c.serialConn.Close()
}

// isAFVSockSupported checks if vsock channel is used by the runtime
// by checking for devices under the vhost-vsock driver path.
// It returns true if a device is found for the vhost-vsock driver.
func isAFVSockSupported() (bool, error) {
fd, err := unix.Socket(unix.AF_VSOCK, unix.SOCK_STREAM, 0)
if err != nil {
// This case is valid. It means AF_VSOCK is not a supported
// domain on this system.
if err == unix.EAFNOSUPPORT {
return false, nil
}
// Driver path for virtio-vsock
sysVsockPath := "/sys/bus/virtio/drivers/vmw_vsock_virtio_transport/"

files, err := ioutil.ReadDir(sysVsockPath)

// This should not happen for a hypervisor with vsock driver
if err != nil {
return false, err
}

if err := unix.Close(fd); err != nil {
return true, err
// standard driver files that should be ignored
driverFiles := []string{"bind", "uevent", "unbind"}

for _, file := range files {
for _, f := range driverFiles {
if file.Name() == f {
continue
}
}

fPath := filepath.Join(sysVsockPath, file.Name())
fInfo, err := os.Lstat(fPath)
if err != nil {
return false, err
}

if fInfo.Mode()&os.ModeSymlink == 0 {
continue
}

link, err := os.Readlink(fPath)
if err != nil {
return false, err
}

if strings.Contains(link, "devices") {
return true, nil
}
}

return true, nil
return false, nil
}

func findVirtualSerialPath(serialName string) (string, error) {
Expand Down
14 changes: 14 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package main

import (
"io/ioutil"
"strconv"
"strings"

"github.com/sirupsen/logrus"
Expand All @@ -20,6 +21,7 @@ const (
logLevelFlag = optionPrefix + "log"
devModeFlag = optionPrefix + "devmode"
traceModeFlag = optionPrefix + "trace"
useVsockFlag = optionPrefix + "use_vsock"
egernst marked this conversation as resolved.
Show resolved Hide resolved
kernelCmdlineFile = "/proc/cmdline"
traceValueIsolated = "isolated"
traceValueCollated = "collated"
Expand Down Expand Up @@ -102,6 +104,18 @@ func (c *agentConfig) parseCmdlineOption(option string) error {
case traceValueCollated:
enableTracing(true)
}
case useVsockFlag:
flag, err := strconv.ParseBool(split[valuePosition])
if err != nil {
return err
}
if flag {
agentLog.Debug("Param passed to use vsock channel")
commCh = vsockCh
} else {
agentLog.Debug("Param passed to NOT use vsock channel")
commCh = serialCh
}
default:
if strings.HasPrefix(split[optionPosition], optionPrefix) {
return grpcStatus.Errorf(codes.NotFound, "Unknown option %s", split[optionPosition])
Expand Down
43 changes: 43 additions & 0 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,3 +285,46 @@ func TestEnableTracing(t *testing.T) {
}
}
}

func TestParseCmdlineOptionWrongOptionVsock(t *testing.T) {
t.Skip()
assert := assert.New(t)

a := &agentConfig{}

wrongOption := "use_vsockkk=true"

err := a.parseCmdlineOption(wrongOption)
assert.Errorf(err, "Parsing should fail because wrong option %q", wrongOption)
}

func TestParseCmdlineOptionsVsock(t *testing.T) {
assert := assert.New(t)

a := &agentConfig{}

type testData struct {
val string
shouldErr bool
expectedCommCh commType
}

data := []testData{
{"true", false, vsockCh},
{"false", false, serialCh},
{"blah", true, unknownCh},
}

for _, d := range data {
commCh = unknownCh
option := useVsockFlag + "=" + d.val

err := a.parseCmdlineOption(option)
if d.shouldErr {
assert.Error(err)
} else {
assert.NoError(err)
}
assert.Equal(commCh, d.expectedCommCh)
}
}