diff --git a/virtcontainers/netmon.go b/virtcontainers/netmon.go new file mode 100644 index 0000000000..f10d73d9f5 --- /dev/null +++ b/virtcontainers/netmon.go @@ -0,0 +1,96 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package virtcontainers + +import ( + "fmt" + "os/exec" + "syscall" + + "github.com/sirupsen/logrus" +) + +// NetmonConfig is the structure providing specific configuration +// for the network monitor. +type NetmonConfig struct { + Path string + Debug bool + Enable bool +} + +// netmonParams is the structure providing specific parameters needed +// for the execution of the network monitor binary. +type netmonParams struct { + netmonPath string + debug bool + logLevel string + runtime string + sandboxID string +} + +func netmonLogger() *logrus.Entry { + return virtLog.WithField("subsystem", "netmon") +} + +func prepareNetMonParams(params netmonParams) ([]string, error) { + if params.netmonPath == "" { + return []string{}, fmt.Errorf("Netmon path is empty") + } + if params.runtime == "" { + return []string{}, fmt.Errorf("Netmon runtime path is empty") + } + if params.sandboxID == "" { + return []string{}, fmt.Errorf("Netmon sandbox ID is empty") + } + + args := []string{params.netmonPath, + "-r", params.runtime, + "-s", params.sandboxID, + } + + if params.debug { + args = append(args, "-d") + } + if params.logLevel != "" { + args = append(args, []string{"-log", params.logLevel}...) + } + + return args, nil +} + +func startNetmon(params netmonParams) (int, error) { + args, err := prepareNetMonParams(params) + if err != nil { + return -1, err + } + + cmd := exec.Command(args[0], args[1:]...) + if err := cmd.Start(); err != nil { + return -1, err + } + + return cmd.Process.Pid, nil +} + +func stopNetmon(pid int) error { + if pid <= 0 { + return nil + } + + sig := syscall.SIGKILL + + netmonLogger().WithFields( + logrus.Fields{ + "netmon-pid": pid, + "netmon-signal": sig, + }).Info("Stopping netmon") + + if err := syscall.Kill(pid, sig); err != nil && err != syscall.ESRCH { + return err + } + + return nil +} diff --git a/virtcontainers/netmon_test.go b/virtcontainers/netmon_test.go new file mode 100644 index 0000000000..0a7f3bb53d --- /dev/null +++ b/virtcontainers/netmon_test.go @@ -0,0 +1,61 @@ +// Copyright (c) 2018 Intel Corporation +// +// SPDX-License-Identifier: Apache-2.0 +// + +package virtcontainers + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + testNetmonPath = "/foo/bar/netmon" + testRuntimePath = "/foo/bar/runtime" +) + +func TestNetmonLogger(t *testing.T) { + got := netmonLogger() + expected := virtLog.WithField("subsystem", "netmon") + assert.True(t, reflect.DeepEqual(expected, got), + "Got %+v\nExpected %+v", got, expected) +} + +func TestPrepareNetMonParams(t *testing.T) { + // Empty netmon path + params := netmonParams{} + got, err := prepareNetMonParams(params) + assert.NotNil(t, err) + assert.Equal(t, got, []string{}) + + // Empty runtime path + params.netmonPath = testNetmonPath + got, err = prepareNetMonParams(params) + assert.NotNil(t, err) + assert.Equal(t, got, []string{}) + + // Empty sandbox ID + params.runtime = testRuntimePath + got, err = prepareNetMonParams(params) + assert.NotNil(t, err) + assert.Equal(t, got, []string{}) + + // Successful case + params.sandboxID = testSandboxID + got, err = prepareNetMonParams(params) + assert.Nil(t, err) + expected := []string{testNetmonPath, + "-r", testRuntimePath, + "-s", testSandboxID} + assert.True(t, reflect.DeepEqual(expected, got), + "Got %+v\nExpected %+v", got, expected) +} + +func TestStopNetmon(t *testing.T) { + pid := -1 + err := stopNetmon(pid) + assert.Nil(t, err) +} diff --git a/virtcontainers/network.go b/virtcontainers/network.go index cbfaa28ee9..89a27a532a 100644 --- a/virtcontainers/network.go +++ b/virtcontainers/network.go @@ -142,6 +142,7 @@ type NetworkInterfacePair struct { type NetworkConfig struct { NetNSPath string NetNsCreated bool + NetmonConfig NetmonConfig InterworkingModel NetInterworkingModel } @@ -474,6 +475,7 @@ type NetworkNamespace struct { NetNsPath string NetNsCreated bool Endpoints []Endpoint + NetmonPID int } // TypedJSONEndpoint is used as an intermediate representation for diff --git a/virtcontainers/sandbox.go b/virtcontainers/sandbox.go index feefe289ff..7d249d975c 100644 --- a/virtcontainers/sandbox.go +++ b/virtcontainers/sandbox.go @@ -961,6 +961,40 @@ func (s *Sandbox) Delete() error { return s.storage.deleteSandboxResources(s.id, nil) } +func (s *Sandbox) startNetworkMonitor() error { + span, _ := s.trace("startNetworkMonitor") + defer span.Finish() + + binPath, err := os.Executable() + if err != nil { + return err + } + + logLevel := "info" + if s.config.NetworkConfig.NetmonConfig.Debug { + logLevel = "debug" + } + + params := netmonParams{ + netmonPath: s.config.NetworkConfig.NetmonConfig.Path, + debug: s.config.NetworkConfig.NetmonConfig.Debug, + logLevel: logLevel, + runtime: binPath, + sandboxID: s.id, + } + + return s.network.run(s.networkNS.NetNsPath, func() error { + pid, err := startNetmon(params) + if err != nil { + return err + } + + s.networkNS.NetmonPID = pid + + return nil + }) +} + func (s *Sandbox) createNetwork() error { span, _ := s.trace("createNetwork") defer span.Finish() @@ -983,6 +1017,12 @@ func (s *Sandbox) createNetwork() error { } } + if s.config.NetworkConfig.NetmonConfig.Enable { + if err := s.startNetworkMonitor(); err != nil { + return err + } + } + // Store the network return s.storage.storeSandboxNetwork(s.id, s.networkNS) } @@ -991,6 +1031,12 @@ func (s *Sandbox) removeNetwork() error { span, _ := s.trace("removeNetwork") defer span.Finish() + if s.config.NetworkConfig.NetmonConfig.Enable { + if err := stopNetmon(s.networkNS.NetmonPID); err != nil { + return err + } + } + // In case there is a factory, the network has been handled through // some API calls to hotplug some interfaces and routes. This means // the removal of the network should follow the same logic. diff --git a/virtcontainers/sandbox_test.go b/virtcontainers/sandbox_test.go index ce7d67f276..8cda6303ea 100644 --- a/virtcontainers/sandbox_test.go +++ b/virtcontainers/sandbox_test.go @@ -11,6 +11,7 @@ import ( "fmt" "io/ioutil" "os" + "os/exec" "path/filepath" "reflect" "sync" @@ -23,6 +24,7 @@ import ( "github.com/kata-containers/runtime/virtcontainers/device/drivers" "github.com/kata-containers/runtime/virtcontainers/device/manager" "github.com/kata-containers/runtime/virtcontainers/pkg/annotations" + "golang.org/x/sys/unix" ) func newHypervisorConfig(kernelParams []Param, hParams []Param) HypervisorConfig { @@ -1722,3 +1724,27 @@ func TestGetNetNs(t *testing.T) { netNs = s.GetNetNs() assert.Equal(t, netNs, expected) } + +func TestStartNetworkMonitor(t *testing.T) { + trueBinPath, err := exec.LookPath("true") + assert.Nil(t, err) + assert.NotEmpty(t, trueBinPath) + + s := &Sandbox{ + id: testSandboxID, + config: &SandboxConfig{ + NetworkConfig: NetworkConfig{ + NetmonConfig: NetmonConfig{ + Path: trueBinPath, + }, + }, + }, + network: &defNetwork{}, + networkNS: NetworkNamespace{ + NetNsPath: fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid()), + }, + } + + err = s.startNetworkMonitor() + assert.Nil(t, err) +}