blob: c0b4e9b47e4c19c2134155df0ad3c2af46350e9b [file] [log] [blame] [edit]
package nsenter
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"strings"
"testing"
"github.com/opencontainers/runc/libcontainer"
"github.com/vishvananda/netlink/nl"
"golang.org/x/sys/unix"
)
func TestNsenterValidPaths(t *testing.T) {
args := []string{"nsenter-exec"}
parent, child := newPipe(t)
namespaces := []string{
// join pid ns of the current process
fmt.Sprintf("pid:/proc/%d/ns/pid", os.Getpid()),
}
cmd := &exec.Cmd{
Path: os.Args[0],
Args: args,
ExtraFiles: []*os.File{child},
Env: []string{"_LIBCONTAINER_INITPIPE=3"},
Stdout: os.Stdout,
Stderr: os.Stderr,
}
if err := cmd.Start(); err != nil {
t.Fatalf("nsenter failed to start: %v", err)
}
child.Close()
// write cloneFlags
r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
r.AddData(&libcontainer.Int32msg{
Type: libcontainer.CloneFlagsAttr,
Value: uint32(unix.CLONE_NEWNET),
})
r.AddData(&libcontainer.Bytemsg{
Type: libcontainer.NsPathsAttr,
Value: []byte(strings.Join(namespaces, ",")),
})
if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
t.Fatal(err)
}
if err := cmd.Wait(); err != nil {
t.Fatalf("nsenter error: %v", err)
}
reapChildren(t, parent)
}
func TestNsenterInvalidPaths(t *testing.T) {
args := []string{"nsenter-exec"}
parent, child := newPipe(t)
namespaces := []string{
fmt.Sprintf("pid:/proc/%d/ns/pid", -1),
}
cmd := &exec.Cmd{
Path: os.Args[0],
Args: args,
ExtraFiles: []*os.File{child},
Env: []string{"_LIBCONTAINER_INITPIPE=3"},
}
if err := cmd.Start(); err != nil {
t.Fatalf("nsenter failed to start: %v", err)
}
child.Close()
// write cloneFlags
r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
r.AddData(&libcontainer.Int32msg{
Type: libcontainer.CloneFlagsAttr,
Value: uint32(unix.CLONE_NEWNET),
})
r.AddData(&libcontainer.Bytemsg{
Type: libcontainer.NsPathsAttr,
Value: []byte(strings.Join(namespaces, ",")),
})
if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
t.Fatal(err)
}
if err := cmd.Wait(); err == nil {
t.Fatalf("nsenter exits with a zero exit status")
}
}
func TestNsenterIncorrectPathType(t *testing.T) {
args := []string{"nsenter-exec"}
parent, child := newPipe(t)
namespaces := []string{
fmt.Sprintf("net:/proc/%d/ns/pid", os.Getpid()),
}
cmd := &exec.Cmd{
Path: os.Args[0],
Args: args,
ExtraFiles: []*os.File{child},
Env: []string{"_LIBCONTAINER_INITPIPE=3"},
}
if err := cmd.Start(); err != nil {
t.Fatalf("nsenter failed to start: %v", err)
}
child.Close()
// write cloneFlags
r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
r.AddData(&libcontainer.Int32msg{
Type: libcontainer.CloneFlagsAttr,
Value: uint32(unix.CLONE_NEWNET),
})
r.AddData(&libcontainer.Bytemsg{
Type: libcontainer.NsPathsAttr,
Value: []byte(strings.Join(namespaces, ",")),
})
if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
t.Fatal(err)
}
if err := cmd.Wait(); err == nil {
t.Fatalf("nsenter error: %v", err)
}
}
func TestNsenterChildLogging(t *testing.T) {
args := []string{"nsenter-exec"}
parent, child := newPipe(t)
logread, logwrite := newPipe(t)
namespaces := []string{
// join pid ns of the current process
fmt.Sprintf("pid:/proc/%d/ns/pid", os.Getpid()),
}
cmd := &exec.Cmd{
Path: os.Args[0],
Args: args,
ExtraFiles: []*os.File{child, logwrite},
Env: []string{"_LIBCONTAINER_INITPIPE=3", "_LIBCONTAINER_LOGPIPE=4"},
Stdout: os.Stdout,
Stderr: os.Stderr,
}
if err := cmd.Start(); err != nil {
t.Fatalf("nsenter failed to start: %v", err)
}
child.Close()
logwrite.Close()
// write cloneFlags
r := nl.NewNetlinkRequest(int(libcontainer.InitMsg), 0)
r.AddData(&libcontainer.Int32msg{
Type: libcontainer.CloneFlagsAttr,
Value: uint32(unix.CLONE_NEWNET),
})
r.AddData(&libcontainer.Bytemsg{
Type: libcontainer.NsPathsAttr,
Value: []byte(strings.Join(namespaces, ",")),
})
if _, err := io.Copy(parent, bytes.NewReader(r.Serialize())); err != nil {
t.Fatal(err)
}
getLogs(t, logread)
if err := cmd.Wait(); err != nil {
t.Fatalf("nsenter error: %v", err)
}
reapChildren(t, parent)
}
func init() {
if strings.HasPrefix(os.Args[0], "nsenter-") {
os.Exit(0)
}
}
func newPipe(t *testing.T) (parent *os.File, child *os.File) {
t.Helper()
fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0)
if err != nil {
t.Fatal("socketpair failed:", err)
}
parent = os.NewFile(uintptr(fds[1]), "parent")
child = os.NewFile(uintptr(fds[0]), "child")
t.Cleanup(func() {
parent.Close()
child.Close()
})
return
}
func reapChildren(t *testing.T, parent *os.File) {
t.Helper()
decoder := json.NewDecoder(parent)
decoder.DisallowUnknownFields()
var pid struct {
Pid2 int `json:"stage2_pid"`
Pid1 int `json:"stage1_pid"`
}
if err := decoder.Decode(&pid); err != nil {
t.Fatal(err)
}
// Reap children.
_, _ = unix.Wait4(pid.Pid1, nil, 0, nil)
_, _ = unix.Wait4(pid.Pid2, nil, 0, nil)
// Sanity check.
if pid.Pid1 == 0 || pid.Pid2 == 0 {
t.Fatal("got pids:", pid)
}
}
func getLogs(t *testing.T, logread *os.File) {
logsDecoder := json.NewDecoder(logread)
logsDecoder.DisallowUnknownFields()
var logentry struct {
Level string `json:"level"`
Msg string `json:"msg"`
}
for {
if err := logsDecoder.Decode(&logentry); err != nil {
if errors.Is(err, io.EOF) {
return
}
t.Fatal("init log decoding error:", err)
}
t.Logf("logentry: %+v", logentry)
if logentry.Level == "" || logentry.Msg == "" {
t.Fatalf("init log: empty log entry: %+v", logentry)
}
}
}