diff options
23 files changed, 1223 insertions, 169 deletions
diff --git a/pkg/abi/linux/file.go b/pkg/abi/linux/file.go index 4b0ea33dc..615e72646 100644 --- a/pkg/abi/linux/file.go +++ b/pkg/abi/linux/file.go @@ -253,6 +253,7 @@ type Statx struct { UID uint32 GID uint32 Mode uint16 + _ uint16 Ino uint64 Size uint64 Blocks uint64 diff --git a/pkg/sentry/platform/kvm/machine.go b/pkg/sentry/platform/kvm/machine.go index 679087e25..cc6c138b2 100644 --- a/pkg/sentry/platform/kvm/machine.go +++ b/pkg/sentry/platform/kvm/machine.go @@ -388,7 +388,10 @@ func (m *machine) Get() *vCPU { func (m *machine) Put(c *vCPU) { c.unlock() runtime.UnlockOSThread() + + m.mu.RLock() m.available.Signal() + m.mu.RUnlock() } // newDirtySet returns a new dirty set. diff --git a/pkg/sentry/syscalls/linux/sys_time.go b/pkg/sentry/syscalls/linux/sys_time.go index fe8725191..4b3f043a2 100644 --- a/pkg/sentry/syscalls/linux/sys_time.go +++ b/pkg/sentry/syscalls/linux/sys_time.go @@ -121,8 +121,15 @@ func getClock(t *kernel.Task, clockID int32) (ktime.Clock, error) { switch clockID { case linux.CLOCK_REALTIME, linux.CLOCK_REALTIME_COARSE: return t.Kernel().RealtimeClock(), nil - case linux.CLOCK_MONOTONIC, linux.CLOCK_MONOTONIC_COARSE, linux.CLOCK_MONOTONIC_RAW: + case linux.CLOCK_MONOTONIC, linux.CLOCK_MONOTONIC_COARSE, + linux.CLOCK_MONOTONIC_RAW, linux.CLOCK_BOOTTIME: // CLOCK_MONOTONIC approximates CLOCK_MONOTONIC_RAW. + // CLOCK_BOOTTIME is internally mapped to CLOCK_MONOTONIC, as: + // - CLOCK_BOOTTIME should behave as CLOCK_MONOTONIC while also + // including suspend time. + // - gVisor has no concept of suspend/resume. + // - CLOCK_MONOTONIC already includes save/restore time, which is + // the closest to suspend time. return t.Kernel().MonotonicClock(), nil case linux.CLOCK_PROCESS_CPUTIME_ID: return t.ThreadGroup().CPUClock(), nil diff --git a/pkg/sentry/syscalls/linux/sys_timerfd.go b/pkg/sentry/syscalls/linux/sys_timerfd.go index 1ce5ce4c3..cf49b43db 100644 --- a/pkg/sentry/syscalls/linux/sys_timerfd.go +++ b/pkg/sentry/syscalls/linux/sys_timerfd.go @@ -37,7 +37,7 @@ func TimerfdCreate(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel switch clockID { case linux.CLOCK_REALTIME: c = t.Kernel().RealtimeClock() - case linux.CLOCK_MONOTONIC: + case linux.CLOCK_MONOTONIC, linux.CLOCK_BOOTTIME: c = t.Kernel().MonotonicClock() default: return 0, nil, syserror.EINVAL diff --git a/pkg/tcpip/transport/udp/endpoint.go b/pkg/tcpip/transport/udp/endpoint.go index 70f4a2b8c..91f89a781 100644 --- a/pkg/tcpip/transport/udp/endpoint.go +++ b/pkg/tcpip/transport/udp/endpoint.go @@ -252,7 +252,7 @@ func (e *endpoint) connectRoute(nicid tcpip.NICID, addr tcpip.FullAddress) (stac if nicid == 0 { nicid = e.multicastNICID } - if localAddr == "" { + if localAddr == "" && nicid == 0 { localAddr = e.multicastAddr } } @@ -675,6 +675,9 @@ func sendUDP(r *stack.Route, data buffer.VectorisedView, localPort, remotePort u func (e *endpoint) checkV4Mapped(addr *tcpip.FullAddress, allowMismatch bool) (tcpip.NetworkProtocolNumber, *tcpip.Error) { netProto := e.netProto + if len(addr.Addr) == 0 { + return netProto, nil + } if header.IsV4MappedAddress(addr.Addr) { // Fail if using a v4 mapped address on a v6only endpoint. if e.v6only { diff --git a/pkg/tcpip/transport/udp/udp_test.go b/pkg/tcpip/transport/udp/udp_test.go index 75129a2ff..958d5712e 100644 --- a/pkg/tcpip/transport/udp/udp_test.go +++ b/pkg/tcpip/transport/udp/udp_test.go @@ -847,9 +847,7 @@ func TestWriteIncrementsPacketsSent(t *testing.T) { } } -func TestTTL(t *testing.T) { - payload := tcpip.SlicePayload(buffer.View(newPayload())) - +func setSockOptVariants(t *testing.T, optFunc func(*testing.T, string, tcpip.NetworkProtocolNumber, string)) { for _, name := range []string{"v4", "v6", "dual"} { t.Run(name, func(t *testing.T) { var networkProtocolNumber tcpip.NetworkProtocolNumber @@ -874,134 +872,219 @@ func TestTTL(t *testing.T) { for _, variant := range variants { t.Run(variant, func(t *testing.T) { - for _, typ := range []string{"unicast", "multicast"} { - t.Run(typ, func(t *testing.T) { - var addr tcpip.Address - var port uint16 - switch typ { - case "unicast": - port = testPort - switch variant { - case "v4": - addr = testAddr - case "mapped": - addr = testV4MappedAddr - case "v6": - addr = testV6Addr - default: - t.Fatal("unknown test variant") - } - case "multicast": - port = multicastPort - switch variant { - case "v4": - addr = multicastAddr - case "mapped": - addr = multicastV4MappedAddr - case "v6": - addr = multicastV6Addr - default: - t.Fatal("unknown test variant") - } - default: - t.Fatal("unknown test variant") - } - - c := newDualTestContext(t, defaultMTU) - defer c.cleanup() - - var err *tcpip.Error - c.ep, err = c.s.NewEndpoint(udp.ProtocolNumber, networkProtocolNumber, &c.wq) - if err != nil { - c.t.Fatalf("NewEndpoint failed: %v", err) - } - - switch name { - case "v4": - case "v6": - if err := c.ep.SetSockOpt(tcpip.V6OnlyOption(1)); err != nil { - c.t.Fatalf("SetSockOpt failed: %v", err) - } - case "dual": - if err := c.ep.SetSockOpt(tcpip.V6OnlyOption(0)); err != nil { - c.t.Fatalf("SetSockOpt failed: %v", err) - } - default: - t.Fatal("unknown test variant") - } + optFunc(t, name, networkProtocolNumber, variant) + }) + } + }) + } +} - const multicastTTL = 42 - if err := c.ep.SetSockOpt(tcpip.MulticastTTLOption(multicastTTL)); err != nil { - c.t.Fatalf("SetSockOpt failed: %v", err) - } +func TestTTL(t *testing.T) { + payload := tcpip.SlicePayload(buffer.View(newPayload())) - n, _, err := c.ep.Write(payload, tcpip.WriteOptions{To: &tcpip.FullAddress{Addr: addr, Port: port}}) - if err != nil { - c.t.Fatalf("Write failed: %v", err) - } - if n != uintptr(len(payload)) { - c.t.Fatalf("got c.ep.Write(...) = %d, want = %d", n, len(payload)) - } + setSockOptVariants(t, func(t *testing.T, name string, networkProtocolNumber tcpip.NetworkProtocolNumber, variant string) { + for _, typ := range []string{"unicast", "multicast"} { + t.Run(typ, func(t *testing.T) { + var addr tcpip.Address + var port uint16 + switch typ { + case "unicast": + port = testPort + switch variant { + case "v4": + addr = testAddr + case "mapped": + addr = testV4MappedAddr + case "v6": + addr = testV6Addr + default: + t.Fatal("unknown test variant") + } + case "multicast": + port = multicastPort + switch variant { + case "v4": + addr = multicastAddr + case "mapped": + addr = multicastV4MappedAddr + case "v6": + addr = multicastV6Addr + default: + t.Fatal("unknown test variant") + } + default: + t.Fatal("unknown test variant") + } + + c := newDualTestContext(t, defaultMTU) + defer c.cleanup() + + var err *tcpip.Error + c.ep, err = c.s.NewEndpoint(udp.ProtocolNumber, networkProtocolNumber, &c.wq) + if err != nil { + c.t.Fatalf("NewEndpoint failed: %v", err) + } + + switch name { + case "v4": + case "v6": + if err := c.ep.SetSockOpt(tcpip.V6OnlyOption(1)); err != nil { + c.t.Fatalf("SetSockOpt failed: %v", err) + } + case "dual": + if err := c.ep.SetSockOpt(tcpip.V6OnlyOption(0)); err != nil { + c.t.Fatalf("SetSockOpt failed: %v", err) + } + default: + t.Fatal("unknown test variant") + } + + const multicastTTL = 42 + if err := c.ep.SetSockOpt(tcpip.MulticastTTLOption(multicastTTL)); err != nil { + c.t.Fatalf("SetSockOpt failed: %v", err) + } + + n, _, err := c.ep.Write(payload, tcpip.WriteOptions{To: &tcpip.FullAddress{Addr: addr, Port: port}}) + if err != nil { + c.t.Fatalf("Write failed: %v", err) + } + if n != uintptr(len(payload)) { + c.t.Fatalf("got c.ep.Write(...) = %d, want = %d", n, len(payload)) + } + + checkerFn := checker.IPv4 + switch variant { + case "v4", "mapped": + case "v6": + checkerFn = checker.IPv6 + default: + t.Fatal("unknown test variant") + } + var wantTTL uint8 + var multicast bool + switch typ { + case "unicast": + multicast = false + switch variant { + case "v4", "mapped": + ep, err := ipv4.NewProtocol().NewEndpoint(0, "", nil, nil, nil) + if err != nil { + t.Fatal(err) + } + wantTTL = ep.DefaultTTL() + ep.Close() + case "v6": + ep, err := ipv6.NewProtocol().NewEndpoint(0, "", nil, nil, nil) + if err != nil { + t.Fatal(err) + } + wantTTL = ep.DefaultTTL() + ep.Close() + default: + t.Fatal("unknown test variant") + } + case "multicast": + wantTTL = multicastTTL + multicast = true + default: + t.Fatal("unknown test variant") + } + + var networkProtocolNumber tcpip.NetworkProtocolNumber + switch variant { + case "v4", "mapped": + networkProtocolNumber = ipv4.ProtocolNumber + case "v6": + networkProtocolNumber = ipv6.ProtocolNumber + default: + t.Fatal("unknown test variant") + } + + b := c.getPacket(networkProtocolNumber, multicast) + checkerFn(c.t, b, + checker.TTL(wantTTL), + checker.UDP( + checker.DstPort(port), + ), + ) + }) + } + }) +} - checkerFn := checker.IPv4 - switch variant { - case "v4", "mapped": - case "v6": - checkerFn = checker.IPv6 - default: - t.Fatal("unknown test variant") - } - var wantTTL uint8 - var multicast bool - switch typ { - case "unicast": - multicast = false - switch variant { - case "v4", "mapped": - ep, err := ipv4.NewProtocol().NewEndpoint(0, "", nil, nil, nil) - if err != nil { - t.Fatal(err) - } - wantTTL = ep.DefaultTTL() - ep.Close() - case "v6": - ep, err := ipv6.NewProtocol().NewEndpoint(0, "", nil, nil, nil) - if err != nil { - t.Fatal(err) - } - wantTTL = ep.DefaultTTL() - ep.Close() - default: - t.Fatal("unknown test variant") - } - case "multicast": - wantTTL = multicastTTL - multicast = true - default: - t.Fatal("unknown test variant") +func TestMulticastInterfaceOption(t *testing.T) { + setSockOptVariants(t, func(t *testing.T, name string, networkProtocolNumber tcpip.NetworkProtocolNumber, variant string) { + for _, bindTyp := range []string{"bound", "unbound"} { + t.Run(bindTyp, func(t *testing.T) { + for _, optTyp := range []string{"use local-addr", "use NICID", "use local-addr and NIC"} { + t.Run(optTyp, func(t *testing.T) { + var mcastAddr, localIfAddr tcpip.Address + switch variant { + case "v4": + mcastAddr = multicastAddr + localIfAddr = stackAddr + case "mapped": + mcastAddr = multicastV4MappedAddr + localIfAddr = stackAddr + case "v6": + mcastAddr = multicastV6Addr + localIfAddr = stackV6Addr + default: + t.Fatal("unknown test variant") + } + + var ifoptSet tcpip.MulticastInterfaceOption + switch optTyp { + case "use local-addr": + ifoptSet.InterfaceAddr = localIfAddr + case "use NICID": + ifoptSet.NIC = 1 + case "use local-addr and NIC": + ifoptSet.InterfaceAddr = localIfAddr + ifoptSet.NIC = 1 + default: + t.Fatal("unknown test variant") + } + + c := newDualTestContext(t, defaultMTU) + defer c.cleanup() + + var err *tcpip.Error + c.ep, err = c.s.NewEndpoint(udp.ProtocolNumber, networkProtocolNumber, &c.wq) + if err != nil { + c.t.Fatalf("NewEndpoint failed: %v", err) + } + + if bindTyp == "bound" { + // Bind the socket by connecting to the multicast address. + // This may have an influence on how the multicast interface + // is set. + addr := tcpip.FullAddress{ + Addr: mcastAddr, + Port: multicastPort, } - - var networkProtocolNumber tcpip.NetworkProtocolNumber - switch variant { - case "v4", "mapped": - networkProtocolNumber = ipv4.ProtocolNumber - case "v6": - networkProtocolNumber = ipv6.ProtocolNumber - default: - t.Fatal("unknown test variant") + if err := c.ep.Connect(addr); err != nil { + c.t.Fatalf("Connect failed: %v", err) } - - b := c.getPacket(networkProtocolNumber, multicast) - checkerFn(c.t, b, - checker.TTL(wantTTL), - checker.UDP( - checker.DstPort(port), - ), - ) - }) - } - }) - } - }) - } + } + + if err := c.ep.SetSockOpt(ifoptSet); err != nil { + c.t.Fatalf("SetSockOpt failed: %v", err) + } + + // Verify multicast interface addr and NIC were set correctly. + // Note that NIC must be 1 since this is our outgoing interface. + ifoptWant := tcpip.MulticastInterfaceOption{NIC: 1, InterfaceAddr: ifoptSet.InterfaceAddr} + var ifoptGot tcpip.MulticastInterfaceOption + if err := c.ep.GetSockOpt(&ifoptGot); err != nil { + c.t.Fatalf("GetSockOpt failed: %v", err) + } + if ifoptGot != ifoptWant { + c.t.Errorf("got GetSockOpt() = %#v, want = %#v", ifoptGot, ifoptWant) + } + }) + } + }) + } + }) } diff --git a/runsc/test/runtimes/BUILD b/runsc/test/runtimes/BUILD new file mode 100644 index 000000000..36d0a761e --- /dev/null +++ b/runsc/test/runtimes/BUILD @@ -0,0 +1,50 @@ +# These packages are used to run language runtime tests inside gVisor sandboxes. + +load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library") +load("//runsc/test:build_defs.bzl", "runtime_test") + +package(licenses = ["notice"]) + +go_library( + name = "runtimes", + srcs = ["runtimes.go"], + importpath = "gvisor.dev/gvisor/runsc/test/runtimes", +) + +runtime_test( + name = "runtimes_test", + size = "small", + srcs = ["runtimes_test.go"], + embed = [":runtimes"], + tags = [ + # Requires docker and runsc to be configured before the test runs. + "manual", + "local", + ], + deps = ["//runsc/test/testutil"], +) + +go_binary( + name = "proctor-go", + srcs = ["proctor-go.go"], +) + +go_binary( + name = "proctor-java", + srcs = ["proctor-java.go"], +) + +go_binary( + name = "proctor-nodejs", + srcs = ["proctor-nodejs.go"], +) + +go_binary( + name = "proctor-php", + srcs = ["proctor-php.go"], +) + +go_binary( + name = "proctor-python", + srcs = ["proctor-python.go"], +) diff --git a/runsc/test/runtimes/proctor-go.go b/runsc/test/runtimes/proctor-go.go new file mode 100644 index 000000000..619887327 --- /dev/null +++ b/runsc/test/runtimes/proctor-go.go @@ -0,0 +1,152 @@ +// Copyright 2019 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Binary proctor-go is a utility that facilitates language testing for Go. + +// There are two types of Go tests: "Go tool tests" and "Go tests on disk". +// "Go tool tests" are found and executed using `go tool dist test`. +// "Go tests on disk" are found in the /test directory and are +// executed using `go run run.go`. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" +) + +var ( + list = flag.Bool("list", false, "list all available tests") + test = flag.String("test", "", "run a single test from the list of available tests") + version = flag.Bool("v", false, "print out the version of node that is installed") + + dir = os.Getenv("LANG_DIR") + testDir = filepath.Join(dir, "test") + testRegEx = regexp.MustCompile(`^.+\.go$`) + + // Directories with .dir contain helper files for tests. + // Exclude benchmarks and stress tests. + exclDirs = regexp.MustCompile(`^.+\/(bench|stress)\/.+$|^.+\.dir.+$`) +) + +func main() { + flag.Parse() + + if *list && *test != "" { + flag.PrintDefaults() + os.Exit(1) + } + if *list { + listTests() + return + } + if *version { + fmt.Println("Go version: ", os.Getenv("LANG_VER"), " is installed.") + return + } + runTest(*test) +} + +func listTests() { + // Go tool dist test tests. + args := []string{"tool", "dist", "test", "-list"} + cmd := exec.Command(filepath.Join(dir, "bin/go"), args...) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.Fatalf("Failed to list: %v", err) + } + + // Go tests on disk. + var tests []string + if err := filepath.Walk(testDir, func(path string, info os.FileInfo, err error) error { + name := filepath.Base(path) + + if info.IsDir() { + return nil + } + + if !testRegEx.MatchString(name) { + return nil + } + + if exclDirs.MatchString(path) { + return nil + } + + tests = append(tests, path) + return nil + }); err != nil { + log.Fatalf("Failed to walk %q: %v", dir, err) + } + + for _, file := range tests { + fmt.Println(file) + } +} + +func runTest(test string) { + toolArgs := []string{ + "tool", + "dist", + "test", + } + diskArgs := []string{ + "run", + "run.go", + "-v", + } + if test != "" { + // Check if test exists on disk by searching for file of the same name. + // This will determine whether or not it is a Go test on disk. + if _, err := os.Stat(test); err == nil { + relPath, err := filepath.Rel(testDir, test) + if err != nil { + log.Fatalf("Failed to get rel path: %v", err) + } + diskArgs = append(diskArgs, "--", relPath) + cmd := exec.Command(filepath.Join(dir, "bin/go"), diskArgs...) + cmd.Dir = testDir + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.Fatalf("Failed to run: %v", err) + } + } else if os.IsNotExist(err) { + // File was not found, try running as Go tool test. + toolArgs = append(toolArgs, "-run", test) + cmd := exec.Command(filepath.Join(dir, "bin/go"), toolArgs...) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.Fatalf("Failed to run: %v", err) + } + } else { + log.Fatalf("Error searching for test: %v", err) + } + return + } + runAllTool := exec.Command(filepath.Join(dir, "bin/go"), toolArgs...) + runAllTool.Stdout, runAllTool.Stderr = os.Stdout, os.Stderr + if err := runAllTool.Run(); err != nil { + log.Fatalf("Failed to run: %v", err) + } + runAllDisk := exec.Command(filepath.Join(dir, "bin/go"), diskArgs...) + runAllDisk.Dir = testDir + runAllDisk.Stdout, runAllDisk.Stderr = os.Stdout, os.Stderr + if err := runAllDisk.Run(); err != nil { + log.Fatalf("Failed to run disk tests: %v", err) + } +} diff --git a/runsc/test/runtimes/proctor-java.go b/runsc/test/runtimes/proctor-java.go new file mode 100644 index 000000000..50f3789dc --- /dev/null +++ b/runsc/test/runtimes/proctor-java.go @@ -0,0 +1,93 @@ +// Copyright 2019 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Binary proctor-java is a utility that facilitates language testing for Java. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" +) + +var ( + list = flag.Bool("list", false, "list all available tests") + test = flag.String("test", "", "run a single test from the list of available tests") + version = flag.Bool("v", false, "print out the version of node that is installed") + + dir = os.Getenv("LANG_DIR") + jtreg = filepath.Join(dir, "jtreg/bin/jtreg") + exclDirs = regexp.MustCompile(`(^(sun\/security)|(java\/util\/stream)|(java\/time)| )`) +) + +func main() { + flag.Parse() + + if *list && *test != "" { + flag.PrintDefaults() + os.Exit(1) + } + if *list { + listTests() + return + } + if *version { + fmt.Println("Java version: ", os.Getenv("LANG_VER"), " is installed.") + return + } + runTest(*test) +} + +func listTests() { + args := []string{ + "-dir:test/jdk", + "-ignore:quiet", + "-a", + "-listtests", + ":jdk_core", + ":jdk_svc", + ":jdk_sound", + ":jdk_imageio", + } + cmd := exec.Command(jtreg, args...) + cmd.Stderr = os.Stderr + out, err := cmd.Output() + if err != nil { + log.Fatalf("Failed to list: %v", err) + } + allTests := string(out) + for _, test := range strings.Split(allTests, "\n") { + if !exclDirs.MatchString(test) { + fmt.Println(test) + } + } +} + +func runTest(test string) { + // TODO(brettlandau): Change to use listTests() for running all tests. + cmd := exec.Command("make", "run-test-tier1") + if test != "" { + args := []string{"-dir:test/jdk/", test} + cmd = exec.Command(jtreg, args...) + } + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.Fatalf("Failed to run: %v", err) + } +} diff --git a/runsc/test/runtimes/proctor-nodejs.go b/runsc/test/runtimes/proctor-nodejs.go new file mode 100644 index 000000000..771286dd1 --- /dev/null +++ b/runsc/test/runtimes/proctor-nodejs.go @@ -0,0 +1,93 @@ +// Copyright 2019 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Binary proctor-nodejs is a utility that facilitates language testing for NodeJS. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" +) + +var ( + list = flag.Bool("list", false, "list all available tests") + test = flag.String("test", "", "run a single test from the list of available tests") + version = flag.Bool("v", false, "print out the version of node that is installed") + + dir = os.Getenv("LANG_DIR") + testRegEx = regexp.MustCompile(`^test-.+\.js$`) +) + +func main() { + flag.Parse() + + if *list && *test != "" { + flag.PrintDefaults() + os.Exit(1) + } + if *list { + listTests() + return + } + if *version { + fmt.Println("Node.js version: ", os.Getenv("LANG_VER"), " is installed.") + return + } + runTest(*test) +} + +func listTests() { + var files []string + root := filepath.Join(dir, "test") + + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + name := filepath.Base(path) + + if info.IsDir() || !testRegEx.MatchString(name) { + return nil + } + + relPath, err := filepath.Rel(root, path) + if err != nil { + return err + } + files = append(files, relPath) + return nil + }) + + if err != nil { + log.Fatalf("Failed to walk %q: %v", root, err) + } + + for _, file := range files { + fmt.Println(file) + } +} + +func runTest(test string) { + args := []string{filepath.Join(dir, "tools", "test.py")} + if test != "" { + args = append(args, test) + } + cmd := exec.Command("/usr/bin/python", args...) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.Fatalf("Failed to run: %v", err) + } +} diff --git a/runsc/test/runtimes/proctor-php.go b/runsc/test/runtimes/proctor-php.go new file mode 100644 index 000000000..3d305c709 --- /dev/null +++ b/runsc/test/runtimes/proctor-php.go @@ -0,0 +1,92 @@ +// Copyright 2019 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Binary proctor-php is a utility that facilitates language testing for PHP. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" +) + +var ( + list = flag.Bool("list", false, "list all available tests") + test = flag.String("test", "", "run a single test from the list of available tests") + version = flag.Bool("v", false, "print out the version of node that is installed") + + dir = os.Getenv("LANG_DIR") + testRegEx = regexp.MustCompile(`^.+\.phpt$`) +) + +func main() { + flag.Parse() + + if *list && *test != "" { + flag.PrintDefaults() + os.Exit(1) + } + if *list { + listTests() + return + } + if *version { + fmt.Println("PHP version: ", os.Getenv("LANG_VER"), " is installed.") + return + } + runTest(*test) +} + +func listTests() { + var files []string + + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + name := filepath.Base(path) + + if info.IsDir() || !testRegEx.MatchString(name) { + return nil + } + + relPath, err := filepath.Rel(dir, path) + if err != nil { + return err + } + files = append(files, relPath) + return nil + }) + + if err != nil { + log.Fatalf("Failed to walk %q: %v", dir, err) + } + + for _, file := range files { + fmt.Println(file) + } +} + +func runTest(test string) { + args := []string{"test", "TESTS="} + if test != "" { + args[1] = args[1] + test + } + cmd := exec.Command("make", args...) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.Fatalf("Failed to run: %v", err) + } +} diff --git a/runsc/test/runtimes/proctor-python.go b/runsc/test/runtimes/proctor-python.go new file mode 100644 index 000000000..5e8d830e1 --- /dev/null +++ b/runsc/test/runtimes/proctor-python.go @@ -0,0 +1,93 @@ +// Copyright 2019 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Binary proctor-python is a utility that facilitates language testing for Pyhton. +package main + +import ( + "flag" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "regexp" +) + +var ( + list = flag.Bool("list", false, "list all available tests") + test = flag.String("test", "", "run a single test from the list of available tests") + version = flag.Bool("v", false, "print out the version of node that is installed") + + dir = os.Getenv("LANG_DIR") + testRegEx = regexp.MustCompile(`^test_.+\.py$`) +) + +func main() { + flag.Parse() + + if *list && *test != "" { + flag.PrintDefaults() + os.Exit(1) + } + if *list { + listTests() + return + } + if *version { + fmt.Println("Python version: ", os.Getenv("LANG_VER"), " is installed.") + return + } + runTest(*test) +} + +func listTests() { + var files []string + root := filepath.Join(dir, "Lib/test") + + err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { + name := filepath.Base(path) + + if info.IsDir() || !testRegEx.MatchString(name) { + return nil + } + + relPath, err := filepath.Rel(root, path) + if err != nil { + return err + } + files = append(files, relPath) + return nil + }) + + if err != nil { + log.Fatalf("Failed to walk %q: %v", root, err) + } + + for _, file := range files { + fmt.Println(file) + } +} + +func runTest(test string) { + args := []string{"-m", "test"} + if test != "" { + args = append(args, test) + } + cmd := exec.Command(filepath.Join(dir, "python"), args...) + cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + log.Fatalf("Failed to run: %v", err) + } +} diff --git a/runsc/test/runtimes/runtimes.go b/runsc/test/runtimes/runtimes.go new file mode 100644 index 000000000..2568e07fe --- /dev/null +++ b/runsc/test/runtimes/runtimes.go @@ -0,0 +1,20 @@ +// Copyright 2019 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Package runtimes provides language tests for runsc runtimes. +// Each test calls docker commands to start up a container for each supported runtime, +// and tests that its respective language tests are behaving as expected, like +// connecting to a port or looking at the output. The container is killed and deleted +// at the end. +package runtimes diff --git a/runsc/test/runtimes/runtimes_test.go b/runsc/test/runtimes/runtimes_test.go new file mode 100644 index 000000000..6bf954e78 --- /dev/null +++ b/runsc/test/runtimes/runtimes_test.go @@ -0,0 +1,67 @@ +// Copyright 2019 The gVisor Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package runtimes + +import ( + "strings" + "testing" + "time" + + "gvisor.dev/gvisor/runsc/test/testutil" +) + +func TestNodeJS(t *testing.T) { + const img = "gcr.io/gvisor-proctor/nodejs" + if err := testutil.Pull(img); err != nil { + t.Fatalf("docker pull failed: %v", err) + } + + c := testutil.MakeDocker("gvisor-list") + + list, err := c.RunFg(img, "--list") + if err != nil { + t.Fatalf("docker run failed: %v", err) + } + c.CleanUp() + + tests := strings.Fields(list) + + for _, tc := range tests { + tc := tc + t.Run(tc, func(t *testing.T) { + t.Parallel() + + d := testutil.MakeDocker("gvisor-test") + if err := d.Run(img, "--test", tc); err != nil { + t.Fatalf("docker test %q failed to run: %v", tc, err) + } + defer d.CleanUp() + + status, err := d.Wait(60 * time.Second) + if err != nil { + t.Fatalf("docker test %q failed to wait: %v", tc, err) + } + if status == 0 { + t.Logf("test %q passed", tc) + return + } + logs, err := d.Logs() + if err != nil { + t.Fatalf("docker test %q failed to supply logs: %v", tc, err) + } + t.Errorf("test %q failed: %v", tc, logs) + }) + } +} diff --git a/test/syscalls/linux/clock_gettime.cc b/test/syscalls/linux/clock_gettime.cc index 335a38d41..c9e3ed6b2 100644 --- a/test/syscalls/linux/clock_gettime.cc +++ b/test/syscalls/linux/clock_gettime.cc @@ -132,6 +132,9 @@ std::string PrintClockId(::testing::TestParamInfo<clockid_t> info) { return "CLOCK_MONOTONIC_COARSE"; case CLOCK_MONOTONIC_RAW: return "CLOCK_MONOTONIC_RAW"; + case CLOCK_BOOTTIME: + // CLOCK_BOOTTIME is a monotonic clock. + return "CLOCK_BOOTTIME"; default: return absl::StrCat(info.param); } @@ -140,15 +143,13 @@ std::string PrintClockId(::testing::TestParamInfo<clockid_t> info) { INSTANTIATE_TEST_SUITE_P(ClockGettime, MonotonicClockTest, ::testing::Values(CLOCK_MONOTONIC, CLOCK_MONOTONIC_COARSE, - CLOCK_MONOTONIC_RAW), + CLOCK_MONOTONIC_RAW, CLOCK_BOOTTIME), PrintClockId); TEST(ClockGettime, UnimplementedReturnsEINVAL) { SKIP_IF(!IsRunningOnGvisor()); struct timespec tp; - EXPECT_THAT(clock_gettime(CLOCK_BOOTTIME, &tp), - SyscallFailsWithErrno(EINVAL)); EXPECT_THAT(clock_gettime(CLOCK_REALTIME_ALARM, &tp), SyscallFailsWithErrno(EINVAL)); EXPECT_THAT(clock_gettime(CLOCK_BOOTTIME_ALARM, &tp), diff --git a/test/syscalls/linux/ip_socket_test_util.cc b/test/syscalls/linux/ip_socket_test_util.cc index 5fc4e9115..c73262e72 100644 --- a/test/syscalls/linux/ip_socket_test_util.cc +++ b/test/syscalls/linux/ip_socket_test_util.cc @@ -120,5 +120,68 @@ SocketKind IPv4TCPUnboundSocket(int type) { UnboundSocketCreator(AF_INET, type | SOCK_STREAM, IPPROTO_TCP)}; } +PosixError IfAddrHelper::Load() { + Release(); + RETURN_ERROR_IF_SYSCALL_FAIL(getifaddrs(&ifaddr_)); + return PosixError(0); +} + +void IfAddrHelper::Release() { + if (ifaddr_) { + freeifaddrs(ifaddr_); + } + ifaddr_ = nullptr; +} + +std::vector<std::string> IfAddrHelper::InterfaceList(int family) { + std::vector<std::string> names; + for (auto ifa = ifaddr_; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != family) { + continue; + } + names.emplace(names.end(), ifa->ifa_name); + } + return names; +} + +sockaddr* IfAddrHelper::GetAddr(int family, std::string name) { + for (auto ifa = ifaddr_; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || ifa->ifa_addr->sa_family != family) { + continue; + } + if (name == ifa->ifa_name) { + return ifa->ifa_addr; + } + } + return nullptr; +} + +PosixErrorOr<int> IfAddrHelper::GetIndex(std::string name) { + return InterfaceIndex(name); +} + +std::string GetAddr4Str(in_addr* a) { + char str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, a, str, sizeof(str)); + return std::string(str); +} + +std::string GetAddr6Str(in6_addr* a) { + char str[INET6_ADDRSTRLEN]; + inet_ntop(AF_INET6, a, str, sizeof(str)); + return std::string(str); +} + +std::string GetAddrStr(sockaddr* a) { + if (a->sa_family == AF_INET) { + auto src = &(reinterpret_cast<sockaddr_in*>(a)->sin_addr); + return GetAddr4Str(src); + } else if (a->sa_family == AF_INET6) { + auto src = &(reinterpret_cast<sockaddr_in6*>(a)->sin6_addr); + return GetAddr6Str(src); + } + return std::string("<invalid>"); +} + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/ip_socket_test_util.h b/test/syscalls/linux/ip_socket_test_util.h index 6898effb8..b498a053d 100644 --- a/test/syscalls/linux/ip_socket_test_util.h +++ b/test/syscalls/linux/ip_socket_test_util.h @@ -15,7 +15,12 @@ #ifndef GVISOR_TEST_SYSCALLS_IP_SOCKET_TEST_UTIL_H_ #define GVISOR_TEST_SYSCALLS_IP_SOCKET_TEST_UTIL_H_ +#include <arpa/inet.h> +#include <ifaddrs.h> +#include <sys/types.h> + #include <string> + #include "test/syscalls/linux/socket_test_util.h" namespace gvisor { @@ -66,6 +71,35 @@ SocketKind IPv4UDPUnboundSocket(int type); // a SimpleSocket created with AF_INET, SOCK_STREAM and the given type. SocketKind IPv4TCPUnboundSocket(int type); +// IfAddrHelper is a helper class that determines the local interfaces present +// and provides functions to obtain their names, index numbers, and IP address. +class IfAddrHelper { + public: + IfAddrHelper() : ifaddr_(nullptr) {} + ~IfAddrHelper() { Release(); } + + PosixError Load(); + void Release(); + + std::vector<std::string> InterfaceList(int family); + + struct sockaddr* GetAddr(int family, std::string name); + PosixErrorOr<int> GetIndex(std::string name); + + private: + struct ifaddrs* ifaddr_; +}; + +// GetAddr4Str returns the given IPv4 network address structure as a string. +std::string GetAddr4Str(in_addr* a); + +// GetAddr6Str returns the given IPv6 network address structure as a string. +std::string GetAddr6Str(in6_addr* a); + +// GetAddrStr returns the given IPv4 or IPv6 network address structure as a +// string. +std::string GetAddrStr(sockaddr* a); + } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc index 0ec828d8d..d9aa7ff3f 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc @@ -40,7 +40,7 @@ TestAddress V4Multicast() { return t; } -// Check that packets are not received without a group memebership. Default send +// Check that packets are not received without a group membership. Default send // interface configured by bind. TEST_P(IPv4UDPUnboundSocketPairTest, IpMulticastLoopbackNoGroup) { auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc index 6b92e05aa..c85ae30dc 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.cc @@ -15,10 +15,13 @@ #include "test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h" #include <arpa/inet.h> +#include <ifaddrs.h> #include <netinet/in.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> + +#include <cstdint> #include <cstdio> #include <cstring> @@ -32,6 +35,52 @@ namespace gvisor { namespace testing { +TestAddress V4EmptyAddress() { + TestAddress t("V4Empty"); + t.addr.ss_family = AF_INET; + t.addr_len = sizeof(sockaddr_in); + return t; +} + +void IPv4UDPUnboundExternalNetworkingSocketTest::SetUp() { + got_if_infos_ = false; + + // Get interface list. + std::vector<std::string> if_names; + ASSERT_NO_ERRNO(if_helper_.Load()); + if_names = if_helper_.InterfaceList(AF_INET); + if (if_names.size() != 2) { + return; + } + + // Figure out which interface is where. + int lo = 0, eth = 1; + if (if_names[lo] != "lo") { + lo = 1; + eth = 0; + } + + if (if_names[lo] != "lo") { + return; + } + + lo_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(if_names[lo])); + lo_if_addr_ = if_helper_.GetAddr(AF_INET, if_names[lo]); + if (lo_if_addr_ == nullptr) { + return; + } + lo_if_sin_addr_ = reinterpret_cast<sockaddr_in*>(lo_if_addr_)->sin_addr; + + eth_if_idx_ = ASSERT_NO_ERRNO_AND_VALUE(if_helper_.GetIndex(if_names[eth])); + eth_if_addr_ = if_helper_.GetAddr(AF_INET, if_names[eth]); + if (eth_if_addr_ == nullptr) { + return; + } + eth_if_sin_addr_ = reinterpret_cast<sockaddr_in*>(eth_if_addr_)->sin_addr; + + got_if_infos_ = true; +} + // Verifies that a newly instantiated UDP socket does not have the // broadcast socket option enabled. TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, UDPBroadcastDefault) { @@ -658,7 +707,7 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, sender->get(), reinterpret_cast<sockaddr*>(&sendto_addr.addr), sendto_addr.addr_len), SyscallSucceeds()); - TestAddress sender_addr(""); + auto sender_addr = V4EmptyAddress(); ASSERT_THAT( getsockname(sender->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr), &sender_addr.addr_len), @@ -674,7 +723,7 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, // Receive a multicast packet. char recv_buf[sizeof(send_buf)] = {}; - TestAddress src_addr(""); + auto src_addr = V4EmptyAddress(); ASSERT_THAT( RetryEINTR(recvfrom)(receiver->get(), recv_buf, sizeof(recv_buf), 0, reinterpret_cast<sockaddr*>(&src_addr.addr), @@ -688,5 +737,113 @@ TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, EXPECT_EQ(sender_addr_in->sin_addr.s_addr, src_addr_in->sin_addr.s_addr); } +// Check that when setting the IP_MULTICAST_IF option to both an index pointing +// to the loopback interface and an address pointing to the non-loopback +// interface, a multicast packet sent out uses the latter as its source address. +TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, + IpMulticastLoopbackIfNicAndAddr) { + // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its + // IPv4 address on eth0. + SKIP_IF(!got_if_infos_); + + // Create receiver, bind to ANY and join the multicast group. + auto receiver = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + auto receiver_addr = V4Any(); + ASSERT_THAT( + bind(receiver->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr), + receiver_addr.addr_len), + SyscallSucceeds()); + socklen_t receiver_addr_len = receiver_addr.addr_len; + ASSERT_THAT(getsockname(receiver->get(), + reinterpret_cast<sockaddr*>(&receiver_addr.addr), + &receiver_addr_len), + SyscallSucceeds()); + EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len); + int receiver_port = + reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)->sin_port; + ip_mreqn group = {}; + group.imr_multiaddr.s_addr = inet_addr(kMulticastAddress); + group.imr_ifindex = lo_if_idx_; + ASSERT_THAT(setsockopt(receiver->get(), IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, + sizeof(group)), + SyscallSucceeds()); + + // Set outgoing multicast interface config, with NIC and addr pointing to + // different interfaces. + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + ip_mreqn iface = {}; + iface.imr_ifindex = lo_if_idx_; + iface.imr_address = eth_if_sin_addr_; + ASSERT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, + sizeof(iface)), + SyscallSucceeds()); + + // Send a multicast packet. + auto sendto_addr = V4Multicast(); + reinterpret_cast<sockaddr_in*>(&sendto_addr.addr)->sin_port = receiver_port; + char send_buf[4] = {}; + ASSERT_THAT(RetryEINTR(sendto)(sender->get(), send_buf, sizeof(send_buf), 0, + reinterpret_cast<sockaddr*>(&sendto_addr.addr), + sendto_addr.addr_len), + SyscallSucceedsWithValue(sizeof(send_buf))); + + // Receive a multicast packet. + char recv_buf[sizeof(send_buf)] = {}; + auto src_addr = V4EmptyAddress(); + ASSERT_THAT( + RetryEINTR(recvfrom)(receiver->get(), recv_buf, sizeof(recv_buf), 0, + reinterpret_cast<sockaddr*>(&src_addr.addr), + &src_addr.addr_len), + SyscallSucceedsWithValue(sizeof(recv_buf))); + ASSERT_EQ(sizeof(struct sockaddr_in), src_addr.addr_len); + sockaddr_in* src_addr_in = reinterpret_cast<sockaddr_in*>(&src_addr.addr); + + // FIXME (b/137781162): When sending a multicast packet use the proper logic + // to determine the packet's src-IP. + SKIP_IF(IsRunningOnGvisor()); + + // Verify the received source address. + EXPECT_EQ(eth_if_sin_addr_.s_addr, src_addr_in->sin_addr.s_addr); +} + +// Check that when we are bound to one interface we can set IP_MULTICAST_IF to +// another interface. +TEST_P(IPv4UDPUnboundExternalNetworkingSocketTest, + IpMulticastLoopbackBindToOneIfSetMcastIfToAnother) { + // FIXME(b/137899561): Linux instance for syscall tests sometimes misses its + // IPv4 address on eth0. + SKIP_IF(!got_if_infos_); + + // FIXME (b/137790511): When bound to one interface it is not possible to set + // IP_MULTICAST_IF to a different interface. + SKIP_IF(IsRunningOnGvisor()); + + // Create sender and bind to eth interface. + auto sender = ASSERT_NO_ERRNO_AND_VALUE(NewSocket()); + ASSERT_THAT(bind(sender->get(), eth_if_addr_, sizeof(sockaddr_in)), + SyscallSucceeds()); + + // Run through all possible combinations of index and address for + // IP_MULTICAST_IF that selects the loopback interface. + struct { + int imr_ifindex; + struct in_addr imr_address; + } test_data[] = { + {lo_if_idx_, {}}, + {0, lo_if_sin_addr_}, + {lo_if_idx_, lo_if_sin_addr_}, + {lo_if_idx_, eth_if_sin_addr_}, + }; + for (auto t : test_data) { + ip_mreqn iface = {}; + iface.imr_ifindex = t.imr_ifindex; + iface.imr_address = t.imr_address; + EXPECT_THAT(setsockopt(sender->get(), IPPROTO_IP, IP_MULTICAST_IF, &iface, + sizeof(iface)), + SyscallSucceeds()) + << "imr_index=" << iface.imr_ifindex + << " imr_address=" << GetAddr4Str(&iface.imr_address); + } +} } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h index 45e1d37ea..bec2e96ee 100644 --- a/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h +++ b/test/syscalls/linux/socket_ipv4_udp_unbound_external_networking.h @@ -15,6 +15,7 @@ #ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ #define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_EXTERNAL_NETWORKING_H_ +#include "test/syscalls/linux/ip_socket_test_util.h" #include "test/syscalls/linux/socket_test_util.h" namespace gvisor { @@ -22,7 +23,24 @@ namespace testing { // Test fixture for tests that apply to unbound IPv4 UDP sockets in a sandbox // with external networking support. -using IPv4UDPUnboundExternalNetworkingSocketTest = SimpleSocketTest; +class IPv4UDPUnboundExternalNetworkingSocketTest : public SimpleSocketTest { + protected: + void SetUp(); + + IfAddrHelper if_helper_; + + // got_if_infos_ is set to false if SetUp() could not obtain all interface + // infos that we need. + bool got_if_infos_; + + // Interface infos. + int lo_if_idx_; + int eth_if_idx_; + sockaddr* lo_if_addr_; + sockaddr* eth_if_addr_; + in_addr lo_if_sin_addr_; + in_addr eth_if_sin_addr_; +}; } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/timerfd.cc b/test/syscalls/linux/timerfd.cc index 9df53612f..86ed87b7c 100644 --- a/test/syscalls/linux/timerfd.cc +++ b/test/syscalls/linux/timerfd.cc @@ -44,21 +44,24 @@ PosixErrorOr<FileDescriptor> TimerfdCreate(int clockid, int flags) { // // - Because clock_gettime(CLOCK_MONOTONIC) is implemented through the VDSO, // it technically uses a closely-related, but distinct, time domain from the -// CLOCK_MONOTONIC used to trigger timerfd expirations. +// CLOCK_MONOTONIC used to trigger timerfd expirations. The same applies to +// CLOCK_BOOTTIME which is an alias for CLOCK_MONOTONIC. absl::Duration TimerSlack() { return absl::Milliseconds(500); } -TEST(TimerfdTest, IsInitiallyStopped) { - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_MONOTONIC, 0)); +class TimerfdTest : public ::testing::TestWithParam<int> {}; + +TEST_P(TimerfdTest, IsInitiallyStopped) { + auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); struct itimerspec its = {}; ASSERT_THAT(timerfd_gettime(tfd.get(), &its), SyscallSucceeds()); EXPECT_EQ(0, its.it_value.tv_sec); EXPECT_EQ(0, its.it_value.tv_nsec); } -TEST(TimerfdTest, SingleShot) { +TEST_P(TimerfdTest, SingleShot) { constexpr absl::Duration kDelay = absl::Seconds(1); - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_MONOTONIC, 0)); + auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); struct itimerspec its = {}; its.it_value = absl::ToTimespec(kDelay); ASSERT_THAT(timerfd_settime(tfd.get(), /* flags = */ 0, &its, nullptr), @@ -72,11 +75,11 @@ TEST(TimerfdTest, SingleShot) { EXPECT_EQ(1, val); } -TEST(TimerfdTest, Periodic) { +TEST_P(TimerfdTest, Periodic) { constexpr absl::Duration kDelay = absl::Seconds(1); constexpr int kPeriods = 3; - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_MONOTONIC, 0)); + auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); struct itimerspec its = {}; its.it_value = absl::ToTimespec(kDelay); its.it_interval = absl::ToTimespec(kDelay); @@ -92,10 +95,10 @@ TEST(TimerfdTest, Periodic) { EXPECT_GE(val, kPeriods); } -TEST(TimerfdTest, BlockingRead) { +TEST_P(TimerfdTest, BlockingRead) { constexpr absl::Duration kDelay = absl::Seconds(3); - auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_MONOTONIC, 0)); + auto const tfd = ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), 0)); struct itimerspec its = {}; its.it_value.tv_sec = absl::ToInt64Seconds(kDelay); auto const start_time = absl::Now(); @@ -111,11 +114,11 @@ TEST(TimerfdTest, BlockingRead) { EXPECT_GE((end_time - start_time) + TimerSlack(), kDelay); } -TEST(TimerfdTest, NonblockingRead_NoRandomSave) { +TEST_P(TimerfdTest, NonblockingRead_NoRandomSave) { constexpr absl::Duration kDelay = absl::Seconds(5); auto const tfd = - ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_MONOTONIC, TFD_NONBLOCK)); + ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK)); // Since the timer is initially disabled and has never fired, read should // return EAGAIN. @@ -148,11 +151,11 @@ TEST(TimerfdTest, NonblockingRead_NoRandomSave) { SyscallFailsWithErrno(EAGAIN)); } -TEST(TimerfdTest, BlockingPoll_SetTimeResetsExpirations) { +TEST_P(TimerfdTest, BlockingPoll_SetTimeResetsExpirations) { constexpr absl::Duration kDelay = absl::Seconds(3); auto const tfd = - ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_MONOTONIC, TFD_NONBLOCK)); + ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK)); struct itimerspec its = {}; its.it_value.tv_sec = absl::ToInt64Seconds(kDelay); auto const start_time = absl::Now(); @@ -181,15 +184,15 @@ TEST(TimerfdTest, BlockingPoll_SetTimeResetsExpirations) { SyscallFailsWithErrno(EAGAIN)); } -TEST(TimerfdTest, SetAbsoluteTime) { +TEST_P(TimerfdTest, SetAbsoluteTime) { constexpr absl::Duration kDelay = absl::Seconds(3); // Use a non-blocking timerfd so that if TFD_TIMER_ABSTIME is incorrectly // non-functional, we get EAGAIN rather than a test timeout. auto const tfd = - ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_MONOTONIC, TFD_NONBLOCK)); + ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK)); struct itimerspec its = {}; - ASSERT_THAT(clock_gettime(CLOCK_MONOTONIC, &its.it_value), SyscallSucceeds()); + ASSERT_THAT(clock_gettime(GetParam(), &its.it_value), SyscallSucceeds()); its.it_value.tv_sec += absl::ToInt64Seconds(kDelay); ASSERT_THAT(timerfd_settime(tfd.get(), TFD_TIMER_ABSTIME, &its, nullptr), SyscallSucceeds()); @@ -201,7 +204,34 @@ TEST(TimerfdTest, SetAbsoluteTime) { EXPECT_EQ(1, val); } -TEST(TimerfdTest, ClockRealtime) { +TEST_P(TimerfdTest, IllegalReadWrite) { + auto const tfd = + ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(GetParam(), TFD_NONBLOCK)); + uint64_t val = 0; + EXPECT_THAT(PreadFd(tfd.get(), &val, sizeof(val), 0), + SyscallFailsWithErrno(ESPIPE)); + EXPECT_THAT(WriteFd(tfd.get(), &val, sizeof(val)), + SyscallFailsWithErrno(EINVAL)); + EXPECT_THAT(PwriteFd(tfd.get(), &val, sizeof(val), 0), + SyscallFailsWithErrno(ESPIPE)); +} + +std::string PrintClockId(::testing::TestParamInfo<int> info) { + switch (info.param) { + case CLOCK_MONOTONIC: + return "CLOCK_MONOTONIC"; + case CLOCK_BOOTTIME: + return "CLOCK_BOOTTIME"; + default: + return absl::StrCat(info.param); + } +} + +INSTANTIATE_TEST_SUITE_P(AllTimerTypes, TimerfdTest, + ::testing::Values(CLOCK_MONOTONIC, CLOCK_BOOTTIME), + PrintClockId); + +TEST(TimerfdClockRealtimeTest, ClockRealtime) { // Since CLOCK_REALTIME can, by definition, change, we can't make any // non-flaky assertions about the amount of time it takes for a // CLOCK_REALTIME-based timer to expire. Just check that it expires at all, @@ -220,18 +250,6 @@ TEST(TimerfdTest, ClockRealtime) { EXPECT_EQ(1, val); } -TEST(TimerfdTest, IllegalReadWrite) { - auto const tfd = - ASSERT_NO_ERRNO_AND_VALUE(TimerfdCreate(CLOCK_MONOTONIC, TFD_NONBLOCK)); - uint64_t val = 0; - EXPECT_THAT(PreadFd(tfd.get(), &val, sizeof(val), 0), - SyscallFailsWithErrno(ESPIPE)); - EXPECT_THAT(WriteFd(tfd.get(), &val, sizeof(val)), - SyscallFailsWithErrno(EINVAL)); - EXPECT_THAT(PwriteFd(tfd.get(), &val, sizeof(val), 0), - SyscallFailsWithErrno(ESPIPE)); -} - } // namespace } // namespace testing diff --git a/test/syscalls/linux/vdso_clock_gettime.cc b/test/syscalls/linux/vdso_clock_gettime.cc index 759a50569..40c0014b9 100644 --- a/test/syscalls/linux/vdso_clock_gettime.cc +++ b/test/syscalls/linux/vdso_clock_gettime.cc @@ -39,6 +39,8 @@ std::string PrintClockId(::testing::TestParamInfo<clockid_t> info) { return "CLOCK_MONOTONIC"; case CLOCK_REALTIME: return "CLOCK_REALTIME"; + case CLOCK_BOOTTIME: + return "CLOCK_BOOTTIME"; default: return absl::StrCat(info.param); } @@ -95,7 +97,8 @@ TEST_P(CorrectVDSOClockTest, IsCorrect) { } INSTANTIATE_TEST_SUITE_P(ClockGettime, CorrectVDSOClockTest, - ::testing::Values(CLOCK_MONOTONIC, CLOCK_REALTIME), + ::testing::Values(CLOCK_MONOTONIC, CLOCK_REALTIME, + CLOCK_BOOTTIME), PrintClockId); } // namespace diff --git a/vdso/vdso.cc b/vdso/vdso.cc index 6265ad217..8bb80a7a4 100644 --- a/vdso/vdso.cc +++ b/vdso/vdso.cc @@ -33,6 +33,8 @@ int __common_clock_gettime(clockid_t clock, struct timespec* ts) { ret = ClockRealtime(ts); break; + case CLOCK_BOOTTIME: + // Fallthrough, CLOCK_BOOTTIME is an alias for CLOCK_MONOTONIC case CLOCK_MONOTONIC: ret = ClockMonotonic(ts); break; @@ -122,7 +124,8 @@ extern "C" int __kernel_clock_getres(clockid_t clock, struct timespec* res) { switch (clock) { case CLOCK_REALTIME: - case CLOCK_MONOTONIC: { + case CLOCK_MONOTONIC: + case CLOCK_BOOTTIME: { res->tv_sec = 0; res->tv_nsec = 1; break; |