Skip to content
Open
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
1 change: 1 addition & 0 deletions runsc/boot/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ go_test(
"//runsc/flag",
"//runsc/fsgofer",
"//runsc/specutils",
"@com_github_google_go_cmp//cmp:go_default_library",
"@com_github_moby_sys_capability//:go_default_library",
"@com_github_opencontainers_runtime_spec//specs-go:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
Expand Down
34 changes: 34 additions & 0 deletions runsc/boot/controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ const (

// ContMgrContainerRuntimeState returns the runtime state of a container.
ContMgrContainerRuntimeState = "containerManager.ContainerRuntimeState"

// ContMgrSetNetworkConfig sets the network args in the loader.
ContMgrSetNetworkConfig = "containerManager.SetNetworkConfig"

// ContMgrNetworkConfig returns the network interfaces and routes applied
// during the creation of root container.
ContMgrNetworkConfig = "containerManager.NetworkConfig"
)

const (
Expand Down Expand Up @@ -1161,3 +1168,30 @@ func (cm *containerManager) GetSavings(_ *struct{}, s *Savings) error {
cm.l.mu.Unlock()
return nil
}

// SetNetworkConfig sets the network arguments.
func (cm *containerManager) SetNetworkConfig(args *CreateLinksAndRoutesArgs, _ *struct{}) error {
log.Debugf("containerManager.SetNetworkConfig")
cm.l.mu.Lock()
defer cm.l.mu.Unlock()
if args == nil {
return fmt.Errorf("cannot set nil networkArgs")
}
cm.l.networkArgs = args
return nil
}

// NetworkConfig returns the network interfaces and routes.
func (cm *containerManager) NetworkConfig(_ *struct{}, networkArgs *CreateLinksAndRoutesArgs) error {
log.Debugf("containerManager.NetworkConfig")
cm.l.mu.Lock()
if cm.l.networkArgs != nil {
*networkArgs = *cm.l.networkArgs
}
cm.l.mu.Unlock()

if networkArgs == nil {
log.Debugf("networks args is nil")
}
return nil
}
4 changes: 4 additions & 0 deletions runsc/boot/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,10 @@ type Loader struct {

fsRestore *fsRestore

// networkArgs contains the routes and links which were scraped from the
// host network namespace during sandbox creation.
networkArgs *CreateLinksAndRoutesArgs

LoaderExtra
}

Expand Down
41 changes: 41 additions & 0 deletions runsc/boot/loader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,13 @@ package boot
import (
"fmt"
"math/rand"
"net"
"os"
"strings"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/moby/sys/capability"
specs "github.com/opencontainers/runtime-spec/specs-go"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -623,3 +625,42 @@ func TestMain(m *testing.M) {
seccheck.Initialize()
os.Exit(m.Run())
}

func TestNetworkConfig(t *testing.T) {
l, cleanup, err := createLoader(testConfig(), testSpec())
if err != nil {
t.Fatalf("error creating loader: %v", err)
}
defer l.Destroy()
defer cleanup()

// We aren't going to wait on this application, so the control server
// needs to be shut down manually.
defer l.ctrl.srv.Stop(time.Hour)

go func() {
l.WaitForStartSignal()
l.ctrl.manager.startResultChan <- nil
}()

args := &CreateLinksAndRoutesArgs{
LoopbackLinks: []LoopbackLink{
{
Name: "lo",
Addresses: []IPWithPrefix{
{Address: net.IP("\x7f\x00\x00\x01"), PrefixLen: 8},
},
},
},
}
if err := l.ctrl.manager.SetNetworkConfig(args, nil); err != nil {
t.Errorf("error calling SetNetworkConfig: %v", err)
}
var networkArgs CreateLinksAndRoutesArgs
if err := l.ctrl.manager.NetworkConfig(nil, &networkArgs); err != nil {
t.Errorf("error calling NetworkConfig: %v", err)
}
if diff := cmp.Diff(networkArgs.LoopbackLinks, args.LoopbackLinks); diff != "" {
t.Errorf("Network config content mismatch (-want +got):\n%s", diff)
}
}
9 changes: 9 additions & 0 deletions runsc/container/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -2282,3 +2282,12 @@ func (c *Container) CheckStopped() {
}
}
}

// NetworkConfig returns the network configuration.
func (c *Container) NetworkConfig() (*boot.CreateLinksAndRoutesArgs, error) {
log.Debugf("Returns network config, cid: %s", c.ID)
if !c.IsSandboxRunning() {
return nil, fmt.Errorf("sandbox is not running")
}
return c.Sandbox.NetworkConfig()
}
37 changes: 37 additions & 0 deletions runsc/container/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,43 @@ func sleepSpecConf(t *testing.T) (*specs.Spec, *config.Config) {
return testutil.NewSpecWithArgs("sleep", "1000"), testutil.TestConfig(t)
}

func TestNetworkConfig(t *testing.T) {
for name, conf := range configs(t, false /* noOverlay */) {
t.Run(name, func(t *testing.T) {
spec, _ := sleepSpecConf(t)
conf.Network = config.NetworkSandbox
_, bundleDir, cleanup, err := testutil.SetupContainer(spec, conf)
if err != nil {
t.Fatalf("error setting up container: %v", err)
}
defer cleanup()

args := Args{
ID: testutil.RandomContainerID(),
Spec: spec,
BundleDir: bundleDir,
}
c, err := New(conf, args)
if err != nil {
t.Fatalf("error creating container: %v", err)
}
defer c.Destroy()

if err := c.Start(conf); err != nil {
t.Fatalf("error starting container: %v", err)
}

networkArgs, err := c.NetworkConfig()
if err != nil {
t.Fatalf("error calling NetworkConfig: %v", err)
}
if len(networkArgs.LoopbackLinks) == 0 {
t.Error("network config loopback links is empty, want not empty")
}
})
}
}

// TestLifecycle tests the basic Create/Start/Signal/Destroy container lifecycle.
// It verifies after each step that the container can be loaded from disk, and
// has the correct status.
Expand Down
58 changes: 30 additions & 28 deletions runsc/sandbox/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,32 +52,34 @@ import (
// Run the following container to test it:
//
// docker run -di --runtime=runsc -p 8080:80 -v $PWD:/usr/local/apache2/htdocs/ httpd:2.4
func setupNetwork(conn *urpc.Client, pid int, conf *config.Config, disableIPv6 bool) error {
func setupNetwork(conn *urpc.Client, pid int, conf *config.Config, disableIPv6 bool) (*boot.CreateLinksAndRoutesArgs, error) {
log.Infof("Setting up network")

switch conf.Network {
case config.NetworkNone:
log.Infof("Network is disabled, create loopback interface only")
if err := createDefaultLoopbackInterface(conf, conn); err != nil {
return fmt.Errorf("creating default loopback interface: %v", err)
return nil, fmt.Errorf("creating default loopback interface: %v", err)
}
case config.NetworkSandbox:
// Build the path to the net namespace of the sandbox process.
// This is what we will copy.
nsPath := filepath.Join("/proc", strconv.Itoa(pid), "ns/net")
if err := createInterfacesAndRoutesFromNS(conn, nsPath, conf, disableIPv6); err != nil {
return fmt.Errorf("creating interfaces from net namespace %q: %v", nsPath, err)
args, err := createInterfacesAndRoutesFromNS(conn, nsPath, conf, disableIPv6)
if err != nil {
return nil, fmt.Errorf("creating interfaces from net namespace %q: %v", nsPath, err)
}
return args, nil
case config.NetworkHost:
// Nothing to do here.
case config.NetworkPlugin:
if err := initPluginStack(conn, pid, conf); err != nil {
return fmt.Errorf("failed to initialize external stack, error: %v", err)
return nil, fmt.Errorf("failed to initialize external stack, error: %v", err)
}
default:
return fmt.Errorf("invalid network type: %v", conf.Network)
return nil, fmt.Errorf("invalid network type: %v", conf.Network)
}
return nil
return nil, nil
}

func createDefaultLoopbackInterface(conf *config.Config, conn *urpc.Client) error {
Expand Down Expand Up @@ -297,65 +299,65 @@ func collectLinksAndRoutes(conf *config.Config, disableIPv6 bool) (boot.CreateLi
// createInterfacesAndRoutesFromNS scrapes the interface and routes from the
// net namespace with the given path, creates them in the sandbox, and removes
// them from the host.
func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *config.Config, disableIPv6 bool) error {
func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *config.Config, disableIPv6 bool) (*boot.CreateLinksAndRoutesArgs, error) {
switch conf.XDP.Mode {
case config.XDPModeOff:
case config.XDPModeNS:
case config.XDPModeRedirect:
if err := createRedirectInterfacesAndRoutes(conn, conf); err != nil {
return fmt.Errorf("failed to create XDP redirect interface: %w", err)
return nil, fmt.Errorf("failed to create XDP redirect interface: %w", err)
}
return nil
return nil, nil
case config.XDPModeTunnel:
if err := createXDPTunnel(conn, nsPath, conf); err != nil {
return fmt.Errorf("failed to create XDP tunnel: %w", err)
return nil, fmt.Errorf("failed to create XDP tunnel: %w", err)
}
return nil
return nil, nil
default:
return fmt.Errorf("unknown XDP mode: %v", conf.XDP.Mode)
return nil, fmt.Errorf("unknown XDP mode: %v", conf.XDP.Mode)
}

// Join the network namespace that we will be copying.
restore, err := joinNetNS(nsPath)
if err != nil {
return err
return nil, err
}
defer restore()

isRoot, err := isRootNetNS()
if err != nil {
return err
return nil, err
}
if isRoot {
return fmt.Errorf("cannot run with network enabled in root network namespace")
return nil, fmt.Errorf("cannot run with network enabled in root network namespace")
}

// Collect addresses and routes from the interfaces.
args, err := collectLinksAndRoutes(conf, disableIPv6)
if err != nil {
return err
return nil, err
}

for i := range args.XDPLinks {
if err := removeLinkAddresses(args.XDPLinks[i].Name, args.XDPLinks[i].Addresses); err != nil {
return fmt.Errorf("removing link addresses for interface %q: %w", args.XDPLinks[i].Name, err)
return nil, fmt.Errorf("removing link addresses for interface %q: %w", args.XDPLinks[i].Name, err)
}
}
for i := range args.FDBasedLinks {
if err := removeLinkAddresses(args.FDBasedLinks[i].Name, args.FDBasedLinks[i].Addresses); err != nil {
return fmt.Errorf("removing link addresses for interface %q: %w", args.FDBasedLinks[i].Name, err)
return nil, fmt.Errorf("removing link addresses for interface %q: %w", args.FDBasedLinks[i].Name, err)
}
}

for i := range args.XDPLinks {
link := &args.XDPLinks[i]
iface, err := net.InterfaceByName(link.Name)
if err != nil {
return fmt.Errorf("getting interface by name %q: %w", link.Name, err)
return nil, fmt.Errorf("getting interface by name %q: %w", link.Name, err)
}
xdpSockFDs, err := createSocketXDP(*iface)
if err != nil {
return fmt.Errorf("failed to create XDP socket: %v", err)
return nil, fmt.Errorf("failed to create XDP socket: %v", err)
}
args.FilePayload.Files = append(args.FilePayload.Files, xdpSockFDs...)
}
Expand All @@ -364,11 +366,11 @@ func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *con
link := &args.FDBasedLinks[i]
iface, err := net.InterfaceByName(link.Name)
if err != nil {
return fmt.Errorf("getting interface by name %q: %w", link.Name, err)
return nil, fmt.Errorf("getting interface by name %q: %w", link.Name, err)
}
ifaceLink, err := netlink.LinkByName(link.Name)
if err != nil {
return fmt.Errorf("getting link for interface %q: %w", link.Name, err)
return nil, fmt.Errorf("getting link for interface %q: %w", link.Name, err)
}

log.Debugf("Setting up network channels")
Expand All @@ -377,13 +379,13 @@ func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *con
log.Debugf("Creating Channel %d", j)
socketEntry, err := createSocket(*iface, ifaceLink, conf.HostGSO)
if err != nil {
return fmt.Errorf("failed to createSocket for %s : %w", link.Name, err)
return nil, fmt.Errorf("failed to createSocket for %s : %w", link.Name, err)
}
if j == 0 {
link.GSOMaxSize = socketEntry.gsoMaxSize
} else {
if link.GSOMaxSize != socketEntry.gsoMaxSize {
return fmt.Errorf("inconsistent gsoMaxSize %d and %d when creating multiple channels for same interface: %s",
return nil, fmt.Errorf("inconsistent gsoMaxSize %d and %d when creating multiple channels for same interface: %s",
link.GSOMaxSize, socketEntry.gsoMaxSize, link.Name)
}
}
Expand All @@ -398,14 +400,14 @@ func createInterfacesAndRoutesFromNS(conn *urpc.Client, nsPath string, conf *con
}

if err := pcapAndNAT(&args, conf); err != nil {
return err
return nil, err
}

log.Debugf("Setting up network, config: %+v", args)
if err := conn.Call(boot.NetworkCreateLinksAndRoutes, &args, nil); err != nil {
return fmt.Errorf("creating links and routes: %w", err)
return nil, fmt.Errorf("creating links and routes: %w", err)
}
return nil
return &args, nil
}

func initPluginStack(conn *urpc.Client, pid int, conf *config.Config) error {
Expand Down
23 changes: 21 additions & 2 deletions runsc/sandbox/sandbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,10 +456,18 @@ func (s *Sandbox) StartRoot(conf *config.Config, spec *specs.Spec) error {
return err
}
// Configure the network.
if err := setupNetwork(conn, pid, conf, disableIPv6); err != nil {
args, err := setupNetwork(conn, pid, conf, disableIPv6)
if err != nil {
return fmt.Errorf("setting up network: %w", err)
}

// Store networkArgs in the Loader.
if args != nil {
if err := conn.Call(boot.ContMgrSetNetworkConfig, args, nil); err != nil {
return fmt.Errorf("setting network args on loader: %w", err)
}
}

// Send a message to the sandbox control server to start the root container.
if err := conn.Call(boot.ContMgrRootContainerStart, &s.ID, nil); err != nil {
return fmt.Errorf("starting root container: %w", err)
Expand Down Expand Up @@ -564,7 +572,7 @@ func (s *Sandbox) Restore(conf *config.Config, spec *specs.Spec, cid string, ima
return err
}
// Configure the network.
if err := setupNetwork(conn, s.Pid.Load(), conf, disableIPv6); err != nil {
if _, err := setupNetwork(conn, s.Pid.Load(), conf, disableIPv6); err != nil {
return fmt.Errorf("setting up network: %v", err)
}

Expand Down Expand Up @@ -2361,3 +2369,14 @@ func SetCloExeOnAllFDs() (retErr error) {
setCloseExecOnce.Do(func() { retErr = setCloExeOnAllFDs() })
return
}

// NetworkConfig returns the network links and routes config applied during
// root container creation.
func (s *Sandbox) NetworkConfig() (*boot.CreateLinksAndRoutesArgs, error) {
log.Debugf("NetworkConfig, sandbox: %q", s.ID)
var networkArgs boot.CreateLinksAndRoutesArgs
if err := s.call(boot.ContMgrNetworkConfig, nil, &networkArgs); err != nil {
return nil, fmt.Errorf("error getting network config (CID: %q): %w", s.ID, err)
}
return &networkArgs, nil
}
Loading