Files
Kir Kolyshkin 67840cce4b Enable gofumpt extra rules
Commit b2f8a74d "clothed" the naked return as inflicted by gofumpt
v0.9.0. Since gofumpt v0.9.2 this rule was moved to "extra" category,
not enabled by default. The only other "extra" rule is to group adjacent
parameters with the same type, which also makes sense.

Enable gofumpt "extra" rules, and reformat the code accordingly.

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2025-11-10 13:18:45 -08:00

248 lines
5.6 KiB
Go

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, 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 parent, child
}
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)
}
}
}