mirror of
https://github.com/criyle/go-sandbox.git
synced 2025-11-04 14:49:53 +08:00
add benchmark to forkexec / seccomp
This commit is contained in:
parent
b8c180569f
commit
67c0621b7f
11
README.md
11
README.md
@ -53,6 +53,17 @@ Configuations:
|
||||
|
||||
- run_program/config.go: all configs toward running specs
|
||||
|
||||
Benchmarks for unshare:
|
||||
|
||||
- 1ms: fork, unshare pid / user / cgroup
|
||||
- 50ms: unshare ipc / mount
|
||||
- 100ms: unshare pid & user & cgroup & mount & pivot root
|
||||
- 400ms: unshare net
|
||||
- 800ms: unshare all
|
||||
- 880ms: unshare all & pivot root
|
||||
|
||||
It seems unshare net or ipc takes time, maybe limits action by seccomp instead.
|
||||
|
||||
TODO:
|
||||
|
||||
1. Use Linux Control Groups to limit & acct CPU & memory (elimilate wait4 rusage)
|
||||
|
||||
188
forkexec/bench_test.go
Normal file
188
forkexec/bench_test.go
Normal file
@ -0,0 +1,188 @@
|
||||
package forkexec
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/criyle/go-judger/types/mount"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// All testing data were from docker env on amd64 arch
|
||||
|
||||
const (
|
||||
roBind = unix.MS_BIND | unix.MS_NOSUID | unix.MS_PRIVATE | unix.MS_RDONLY
|
||||
)
|
||||
|
||||
var (
|
||||
defaultBind = []string{"/usr", "/lib", "/lib64", "/bin"}
|
||||
)
|
||||
|
||||
// BenchmarkSimpleFork is about 0.70ms/op
|
||||
func BenchmarkSimpleFork(b *testing.B) {
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
// BenchmarkUnsharePid is about 0.79ms/op
|
||||
func BenchmarkUnsharePid(b *testing.B) {
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
r.UnshareFlags = unix.CLONE_NEWPID
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
// BenchmarkUnshareUser is about 0.84ms/op
|
||||
func BenchmarkUnshareUser(b *testing.B) {
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
r.UnshareFlags = unix.CLONE_NEWUSER
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
// BenchmarkUnshareUts is about 0.78ms/op
|
||||
func BenchmarkUnshareUts(b *testing.B) {
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
r.UnshareFlags = unix.CLONE_NEWUTS
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
// BenchmarkUnshareCgroup is about 0.85ms/op
|
||||
func BenchmarkUnshareCgroup(b *testing.B) {
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
r.UnshareFlags = unix.CLONE_NEWCGROUP
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
// BenchmarkUnshareIpc is about 51ms/op
|
||||
func BenchmarkUnshareIpc(b *testing.B) {
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
r.UnshareFlags = unix.CLONE_NEWIPC
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
// BenchmarkUnshareMount is about 51ms/op
|
||||
func BenchmarkUnshareMount(b *testing.B) {
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
r.UnshareFlags = unix.CLONE_NEWNS
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
// BenchmarkUnshareNet is about 426ms/op
|
||||
func BenchmarkUnshareNet(b *testing.B) {
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
r.UnshareFlags = unix.CLONE_NEWNET
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
// BenchmarkFastUnshareMountPivot is about 104ms/op
|
||||
func BenchmarkFastUnshareMountPivot(b *testing.B) {
|
||||
root, err := ioutil.TempDir("", "ns")
|
||||
if err != nil {
|
||||
b.Errorf("failed to create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
r.UnshareFlags = unix.CLONE_NEWNS | unix.CLONE_NEWPID | unix.CLONE_NEWUSER | unix.CLONE_NEWUTS | unix.CLONE_NEWCGROUP
|
||||
r.PivotRoot = root
|
||||
r.NoNewPrivs = true
|
||||
r.DropCaps = true
|
||||
r.Mounts = getMounts(defaultBind)
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
// BenchmarkUnshareAll is about 800ms/op
|
||||
func BenchmarkUnshareAll(b *testing.B) {
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
r.UnshareFlags = UnshareFlags
|
||||
r.NoNewPrivs = true
|
||||
r.DropCaps = true
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
// BenchmarkUnshareMountPivot is about 880ms/op
|
||||
func BenchmarkUnshareMountPivot(b *testing.B) {
|
||||
root, err := ioutil.TempDir("", "ns")
|
||||
if err != nil {
|
||||
b.Errorf("failed to create temp dir")
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
r, f := getRunner(b)
|
||||
defer f.Close()
|
||||
r.UnshareFlags = UnshareFlags
|
||||
r.PivotRoot = root
|
||||
r.NoNewPrivs = true
|
||||
r.DropCaps = true
|
||||
r.Mounts = getMounts(defaultBind)
|
||||
benchmarkRun(r, b)
|
||||
}
|
||||
|
||||
func getRunner(b *testing.B) (*Runner, *os.File) {
|
||||
f := openNull(b)
|
||||
return &Runner{
|
||||
Args: []string{"/bin/echo"},
|
||||
Env: []string{"PATH=/bin"},
|
||||
Files: []uintptr{f.Fd(), f.Fd(), f.Fd()},
|
||||
WorkDir: "/bin",
|
||||
}, f
|
||||
}
|
||||
|
||||
func benchmarkRun(r *Runner, b *testing.B) {
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
pid, err := r.Start()
|
||||
if err != nil {
|
||||
b.Fail()
|
||||
}
|
||||
wait4(pid, b)
|
||||
}
|
||||
}
|
||||
|
||||
func getMounts(dirs []string) []*mount.Mount {
|
||||
ret := make([]*mount.Mount, 0, len(dirs))
|
||||
for _, d := range dirs {
|
||||
if _, err := os.Stat(d); !os.IsNotExist(err) {
|
||||
ret = append(ret, getMount(d))
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func getMount(dir string) *mount.Mount {
|
||||
return &mount.Mount{
|
||||
Source: dir,
|
||||
Target: dir[1:],
|
||||
Flags: roBind,
|
||||
}
|
||||
}
|
||||
|
||||
func openNull(b *testing.B) *os.File {
|
||||
f, err := os.OpenFile("/dev/null", os.O_RDWR, 0666)
|
||||
if err != nil {
|
||||
b.Errorf("Failed to open %v", err)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func wait4(pid int, b *testing.B) {
|
||||
var wstat syscall.WaitStatus
|
||||
for {
|
||||
syscall.Wait4(pid, &wstat, 0, nil)
|
||||
if wstat.Exited() {
|
||||
if s := wstat.ExitStatus(); s != 0 {
|
||||
b.Errorf("Exited: %d", s)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
2
go.mod
2
go.mod
@ -4,5 +4,5 @@ go 1.12
|
||||
|
||||
require (
|
||||
github.com/seccomp/libseccomp-golang v0.9.1
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb
|
||||
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7
|
||||
)
|
||||
|
||||
2
go.sum
2
go.sum
@ -2,3 +2,5 @@ github.com/seccomp/libseccomp-golang v0.9.1 h1:NJjM5DNFOs0s3kYE1WUOr6G8V97sdt46r
|
||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb h1:fgwFCsaw9buMuxNd6+DQfAuSFqbNiQZpcgJQAgJsK6k=
|
||||
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7 h1:LepdCS8Gf/MVejFIt8lsiexZATdoGVyp5bcyS+rYoUI=
|
||||
golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
||||
@ -6,6 +6,20 @@ import (
|
||||
libseccomp "github.com/seccomp/libseccomp-golang"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultSyscallAllows = []string{
|
||||
"read", "write", "readv", "writev", "close", "fstat", "lseek", "dup", "dup2", "dup3", "ioctl", "fcntl", "fadvise64",
|
||||
"mmap", "mprotect", "munmap", "brk", "mremap", "msync", "mincore", "madvise",
|
||||
"rt_sigaction", "rt_sigprocmask", "rt_sigreturn", "rt_sigpending", "sigaltstack",
|
||||
"getcwd", "exit", "exit_group", "arch_prctl",
|
||||
"gettimeofday", "getrlimit", "getrusage", "times", "time", "clock_gettime", "restart_syscall",
|
||||
}
|
||||
|
||||
defaultSyscallTraces = []string{
|
||||
"execve", "open", "openat", "unlink", "unlinkat", "readlink", "readlinkat", "lstat", "stat", "access", "faccessat",
|
||||
}
|
||||
)
|
||||
|
||||
func TestBuildFilter(t *testing.T) {
|
||||
defaultAction := libseccomp.ActKill
|
||||
filter, err := buildFilterMock(defaultAction)
|
||||
@ -64,6 +78,15 @@ func TestAddActionFail(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkBuildDefaultFilter is about 0.2ms/op
|
||||
func BenchmarkBuildDefaultFilter(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
filter, _ := BuildFilter(libseccomp.ActKill, libseccomp.ActTrace, defaultSyscallAllows, defaultSyscallTraces)
|
||||
_, _ = FilterToBPF(filter)
|
||||
filter.Release()
|
||||
}
|
||||
}
|
||||
|
||||
func buildFilterMock(d libseccomp.ScmpAction) (*libseccomp.ScmpFilter, error) {
|
||||
defaultTrace := libseccomp.ActTrace
|
||||
allow := []string{"fork"}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user