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

Commit

Permalink
network: Create network namespace from the CLI
Browse files Browse the repository at this point in the history
This commit moves the network namespace creation out of virtcontainers
in order to anticipate the move of the OCI hooks to the CLI through a
follow up commit.

Signed-off-by: Sebastien Boeuf <[email protected]>
  • Loading branch information
Sebastien Boeuf committed Aug 24, 2018
1 parent 44d2ec7 commit cb351dc
Show file tree
Hide file tree
Showing 11 changed files with 332 additions and 300 deletions.
7 changes: 7 additions & 0 deletions cli/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,13 @@ func createSandbox(ctx context.Context, ociSpec oci.CompatOCISpec, runtimeConfig
return vc.Process{}, err
}

// Important to create the network namespace before the sandbox is
// created, because it is not responsible for the creation of the
// netns if it does not exist.
if err := setupNetworkNamespace(&sandboxConfig.NetworkConfig); err != nil {
return vc.Process{}, err
}

sandbox, err := vci.CreateSandbox(ctx, sandboxConfig)
if err != nil {
return vc.Process{}, err
Expand Down
12 changes: 12 additions & 0 deletions cli/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,10 @@ func TestCreateContainerInvalid(t *testing.T) {
}

func TestCreateProcessCgroupsPathSuccessful(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip(testDisabledNeedNonRoot)
}

assert := assert.New(t)

sandbox := &vcmock.Sandbox{
Expand Down Expand Up @@ -725,6 +729,10 @@ func TestCreateCreateCreatePidFileFail(t *testing.T) {
}

func TestCreate(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip(testDisabledNeedNonRoot)
}

assert := assert.New(t)

sandbox := &vcmock.Sandbox{
Expand Down Expand Up @@ -891,6 +899,10 @@ func TestCreateSandboxConfigFail(t *testing.T) {
}

func TestCreateCreateSandboxFail(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip(testDisabledNeedNonRoot)
}

assert := assert.New(t)

path, err := ioutil.TempDir("", "containers-mapping")
Expand Down
131 changes: 131 additions & 0 deletions cli/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@
package main

import (
"bufio"
"context"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strings"

"golang.org/x/sys/unix"

"github.com/containernetworking/plugins/pkg/ns"
"github.com/kata-containers/agent/protocols/grpc"
vc "github.com/kata-containers/runtime/virtcontainers"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -227,3 +233,128 @@ func networkListCommand(ctx context.Context, containerID string, opType networkT
}
return err
}

const procMountInfoFile = "/proc/self/mountinfo"

// getNetNsFromBindMount returns the network namespace for the bind-mounted path
func getNetNsFromBindMount(nsPath string, procMountFile string) (string, error) {
netNsMountType := "nsfs"

// Resolve all symlinks in the path as the mountinfo file contains
// resolved paths.
nsPath, err := filepath.EvalSymlinks(nsPath)
if err != nil {
return "", err
}

f, err := os.Open(procMountFile)
if err != nil {
return "", err
}
defer f.Close()

scanner := bufio.NewScanner(f)
for scanner.Scan() {
text := scanner.Text()

// Scan the mountinfo file to search for the network namespace path
// This file contains mounts in the eg format:
// "711 26 0:3 net:[4026532009] /run/docker/netns/default rw shared:535 - nsfs nsfs rw"
//
// Reference: https://www.kernel.org/doc/Documentation/filesystems/proc.txt

// We are interested in the first 9 fields of this file,
// to check for the correct mount type.
fields := strings.Split(text, " ")
if len(fields) < 9 {
continue
}

// We check here if the mount type is a network namespace mount type, namely "nsfs"
mountTypeFieldIdx := 8
if fields[mountTypeFieldIdx] != netNsMountType {
continue
}

// This is the mount point/destination for the mount
mntDestIdx := 4
if fields[mntDestIdx] != nsPath {
continue
}

// This is the root/source of the mount
return fields[3], nil
}

return "", nil
}

// hostNetworkingRequested checks if the network namespace requested is the
// same as the current process.
func hostNetworkingRequested(configNetNs string) (bool, error) {
var evalNS, nsPath, currentNsPath string
var err error

// Net namespace provided as "/proc/pid/ns/net" or "/proc/<pid>/task/<tid>/ns/net"
if strings.HasPrefix(configNetNs, "/proc") && strings.HasSuffix(configNetNs, "/ns/net") {
if _, err := os.Stat(configNetNs); err != nil {
return false, err
}

// Here we are trying to resolve the path but it fails because
// namespaces links don't really exist. For this reason, the
// call to EvalSymlinks will fail when it will try to stat the
// resolved path found. As we only care about the path, we can
// retrieve it from the PathError structure.
if _, err = filepath.EvalSymlinks(configNetNs); err != nil {
nsPath = err.(*os.PathError).Path
} else {
return false, fmt.Errorf("Net namespace path %s is not a symlink", configNetNs)
}

_, evalNS = filepath.Split(nsPath)

} else {
// Bind-mounted path provided
evalNS, _ = getNetNsFromBindMount(configNetNs, procMountInfoFile)
}

currentNS := fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
if _, err = filepath.EvalSymlinks(currentNS); err != nil {
currentNsPath = err.(*os.PathError).Path
} else {
return false, fmt.Errorf("Unexpected: Current network namespace path is not a symlink")
}

_, evalCurrentNS := filepath.Split(currentNsPath)

if evalNS == evalCurrentNS {
return true, nil
}

return false, nil
}

func setupNetworkNamespace(config *vc.NetworkConfig) error {
if config.NetNSPath == "" {
n, err := ns.NewNS()
if err != nil {
return err
}

config.NetNSPath = n.Path()
config.NetNsCreated = true

return nil
}

isHostNs, err := hostNetworkingRequested(config.NetNSPath)
if err != nil {
return err
}
if isHostNs {
return fmt.Errorf("Host networking requested, not supported by runtime")
}

return nil
}
131 changes: 131 additions & 0 deletions cli/network_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ package main
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"

"golang.org/x/sys/unix"

"github.com/containernetworking/plugins/pkg/ns"
"github.com/kata-containers/agent/protocols/grpc"
vc "github.com/kata-containers/runtime/virtcontainers"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -87,3 +93,128 @@ func TestNetworkCliFunction(t *testing.T) {
f.Close()
execCLICommandFunc(assert, updateRoutesCommand, set, false)
}

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

tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)

mountFile := filepath.Join(tmpdir, "mountInfo")
nsPath := filepath.Join(tmpdir, "ns123")

// Non-existent namespace path
_, err = getNetNsFromBindMount(nsPath, mountFile)
assert.NotNil(err)

tmpNSPath := filepath.Join(tmpdir, "testNetNs")
f, err := os.Create(tmpNSPath)
assert.NoError(err)
defer f.Close()

type testData struct {
contents string
expectedResult string
}

data := []testData{
{fmt.Sprintf("711 26 0:3 net:[4026532008] %s rw shared:535 - nsfs nsfs rw", tmpNSPath), "net:[4026532008]"},
{"711 26 0:3 net:[4026532008] /run/netns/ns123 rw shared:535 - tmpfs tmpfs rw", ""},
{"a a a a a a a - b c d", ""},
{"", ""},
}

for i, d := range data {
err := ioutil.WriteFile(mountFile, []byte(d.contents), 0640)
assert.NoError(err)

path, err := getNetNsFromBindMount(tmpNSPath, mountFile)
assert.NoError(err, fmt.Sprintf("got %q, test data: %+v", path, d))

assert.Equal(d.expectedResult, path, "Test %d, expected %s, got %s", i, d.expectedResult, path)
}
}

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

if os.Geteuid() != 0 {
t.Skip(testDisabledNeedRoot)
}

// Network namespace same as the host
selfNsPath := "/proc/self/ns/net"
isHostNs, err := hostNetworkingRequested(selfNsPath)
assert.NoError(err)
assert.True(isHostNs)

// Non-existent netns path
nsPath := "/proc/123456789/ns/net"
_, err = hostNetworkingRequested(nsPath)
assert.Error(err)

// Bind-mounted Netns
tmpdir, err := ioutil.TempDir("", "")
assert.NoError(err)
defer os.RemoveAll(tmpdir)

// Create a bind mount to the current network namespace.
tmpFile := filepath.Join(tmpdir, "testNetNs")
f, err := os.Create(tmpFile)
assert.NoError(err)
defer f.Close()

err = syscall.Mount(selfNsPath, tmpFile, "bind", syscall.MS_BIND, "")
assert.Nil(err)

isHostNs, err = hostNetworkingRequested(tmpFile)
assert.NoError(err)
assert.True(isHostNs)

syscall.Unmount(tmpFile, 0)
}

func TestSetupNetworkNamespace(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip(testDisabledNeedNonRoot)
}

assert := assert.New(t)

// Network namespace same as the host
config := &vc.NetworkConfig{
NetNSPath: "/proc/self/ns/net",
}
err := setupNetworkNamespace(config)
assert.Error(err)

// Non-existent netns path
config = &vc.NetworkConfig{
NetNSPath: "/proc/123456789/ns/net",
}
err = setupNetworkNamespace(config)
assert.Error(err)

// Existent netns path
n, err := ns.NewNS()
assert.NoError(err)
config = &vc.NetworkConfig{
NetNSPath: n.Path(),
}
err = setupNetworkNamespace(config)
assert.NoError(err)
n.Close()

// Empty netns path
config = &vc.NetworkConfig{}
err = setupNetworkNamespace(config)
assert.NoError(err)
n, err = ns.GetNS(config.NetNSPath)
assert.NoError(err)
assert.NotNil(n)
assert.True(config.NetNsCreated)
n.Close()
unix.Unmount(config.NetNSPath, unix.MNT_DETACH)
os.RemoveAll(config.NetNSPath)
}
8 changes: 8 additions & 0 deletions cli/run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,10 @@ func testRunContainerSetup(t *testing.T) runContainerData {
}

func TestRunContainerSuccessful(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip(testDisabledNeedNonRoot)
}

assert := assert.New(t)

d := testRunContainerSetup(t)
Expand Down Expand Up @@ -295,6 +299,10 @@ func TestRunContainerSuccessful(t *testing.T) {
}

func TestRunContainerDetachSuccessful(t *testing.T) {
if os.Geteuid() != 0 {
t.Skip(testDisabledNeedNonRoot)
}

assert := assert.New(t)

d := testRunContainerSetup(t)
Expand Down
10 changes: 10 additions & 0 deletions virtcontainers/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"syscall"
"testing"

"github.com/containernetworking/plugins/pkg/ns"
"github.com/kata-containers/agent/protocols/grpc"
"github.com/kata-containers/runtime/virtcontainers/pkg/mock"
specs "github.com/opencontainers/runtime-spec/specs-go"
Expand Down Expand Up @@ -1392,6 +1393,15 @@ func TestStartStopSandboxHyperstartAgentSuccessfulWithDefaultNetwork(t *testing.

config := newTestSandboxConfigHyperstartAgentDefaultNetwork()

n, err := ns.NewNS()
if err != nil {
t.Fatal(err)
}
defer n.Close()

config.NetworkConfig.NetNSPath = n.Path()
config.NetworkConfig.NetNsCreated = true

sockDir, err := testGenerateCCProxySockDir()
if err != nil {
t.Fatal(err)
Expand Down
Loading

0 comments on commit cb351dc

Please sign in to comment.