diff options
Diffstat (limited to 'test')
68 files changed, 1219 insertions, 663 deletions
diff --git a/test/benchmarks/fs/BUILD b/test/benchmarks/fs/BUILD index b4f967441..c94caab60 100644 --- a/test/benchmarks/fs/BUILD +++ b/test/benchmarks/fs/BUILD @@ -11,6 +11,7 @@ benchmark_test( "//pkg/test/dockerutil", "//test/benchmarks/harness", "//test/benchmarks/tools", + "@com_github_docker_docker//api/types/mount:go_default_library", ], ) diff --git a/test/benchmarks/fs/bazel_test.go b/test/benchmarks/fs/bazel_test.go index 8baeff0db..7ced963f6 100644 --- a/test/benchmarks/fs/bazel_test.go +++ b/test/benchmarks/fs/bazel_test.go @@ -25,6 +25,13 @@ import ( "gvisor.dev/gvisor/test/benchmarks/tools" ) +// Dimensions here are clean/dirty cache (do or don't drop caches) +// and if the mount on which we are compiling is a tmpfs/bind mount. +type benchmark struct { + clearCache bool // clearCache drops caches before running. + fstype string // type of filesystem to use. +} + // Note: CleanCache versions of this test require running with root permissions. func BenchmarkBuildABSL(b *testing.B) { runBuildBenchmark(b, "benchmarks/absl", "/abseil-cpp", "absl/base/...") @@ -45,17 +52,18 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { } defer machine.CleanUp() - // Dimensions here are clean/dirty cache (do or don't drop caches) - // and if the mount on which we are compiling is a tmpfs/bind mount. - benchmarks := []struct { - clearCache bool // clearCache drops caches before running. - tmpfs bool // tmpfs will run compilation on a tmpfs. - }{ - {clearCache: true, tmpfs: false}, - {clearCache: false, tmpfs: false}, - {clearCache: true, tmpfs: true}, - {clearCache: false, tmpfs: true}, + benchmarks := make([]benchmark, 0, 6) + for _, filesys := range []string{harness.BindFS, harness.TmpFS, harness.RootFS} { + benchmarks = append(benchmarks, benchmark{ + clearCache: true, + fstype: filesys, + }) + benchmarks = append(benchmarks, benchmark{ + clearCache: false, + fstype: filesys, + }) } + for _, bm := range benchmarks { pageCache := tools.Parameter{ Name: "page_cache", @@ -67,10 +75,7 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { filesystem := tools.Parameter{ Name: "filesystem", - Value: "bind", - } - if bm.tmpfs { - filesystem.Value = "tmpfs" + Value: bm.fstype, } name, err := tools.ParametersToName(pageCache, filesystem) if err != nil { @@ -83,21 +88,25 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { container := machine.GetContainer(ctx, b) defer container.CleanUp(ctx) + mts, prefix, cleanup, err := harness.MakeMount(machine, bm.fstype) + if err != nil { + b.Fatalf("Failed to make mount: %v", err) + } + defer cleanup() + + runOpts := dockerutil.RunOpts{ + Image: image, + Mounts: mts, + } + // Start a container and sleep. - if err := container.Spawn(ctx, dockerutil.RunOpts{ - Image: image, - }, "sleep", fmt.Sprintf("%d", 1000000)); err != nil { + if err := container.Spawn(ctx, runOpts, "sleep", fmt.Sprintf("%d", 1000000)); err != nil { b.Fatalf("run failed with: %v", err) } - // If we are running on a tmpfs, copy to /tmp which is a tmpfs. - prefix := "" - if bm.tmpfs { - if out, err := container.Exec(ctx, dockerutil.ExecOpts{}, - "cp", "-r", workdir, "/tmp/."); err != nil { - b.Fatalf("failed to copy directory: %v (%s)", err, out) - } - prefix = "/tmp" + if out, err := container.Exec(ctx, dockerutil.ExecOpts{}, + "cp", "-rf", workdir, prefix+"/."); err != nil { + b.Fatalf("failed to copy directory: %v (%s)", err, out) } b.ResetTimer() @@ -118,7 +127,7 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) { WorkDir: prefix + workdir, }, "bazel", "build", "-c", "opt", target) if err != nil { - b.Fatalf("build failed with: %v", err) + b.Fatalf("build failed with: %v logs: %s", err, got) } b.StopTimer() diff --git a/test/benchmarks/fs/fio_test.go b/test/benchmarks/fs/fio_test.go index cc2d1cbbc..f783a2b33 100644 --- a/test/benchmarks/fs/fio_test.go +++ b/test/benchmarks/fs/fio_test.go @@ -21,7 +21,6 @@ import ( "strings" "testing" - "github.com/docker/docker/api/types/mount" "gvisor.dev/gvisor/pkg/test/dockerutil" "gvisor.dev/gvisor/test/benchmarks/harness" "gvisor.dev/gvisor/test/benchmarks/tools" @@ -70,7 +69,7 @@ func BenchmarkFio(b *testing.B) { } defer machine.CleanUp() - for _, fsType := range []mount.Type{mount.TypeBind, mount.TypeTmpfs} { + for _, fsType := range []string{harness.BindFS, harness.TmpFS, harness.RootFS} { for _, tc := range testCases { operation := tools.Parameter{ Name: "operation", @@ -82,7 +81,7 @@ func BenchmarkFio(b *testing.B) { } filesystem := tools.Parameter{ Name: "filesystem", - Value: string(fsType), + Value: fsType, } name, err := tools.ParametersToName(operation, blockSize, filesystem) if err != nil { @@ -95,13 +94,7 @@ func BenchmarkFio(b *testing.B) { container := machine.GetContainer(ctx, b) defer container.CleanUp(ctx) - // Directory and filename inside container where fio will read/write. - outdir := "/data" - outfile := filepath.Join(outdir, "test.txt") - - // Make the required mount and grab a cleanup for bind mounts - // as they are backed by a temp directory (mktemp). - mnt, mountCleanup, err := makeMount(machine, fsType, outdir) + mnts, outdir, mountCleanup, err := harness.MakeMount(machine, fsType) if err != nil { b.Fatalf("failed to make mount: %v", err) } @@ -109,12 +102,9 @@ func BenchmarkFio(b *testing.B) { // Start the container with the mount. if err := container.Spawn( - ctx, - dockerutil.RunOpts{ - Image: "benchmarks/fio", - Mounts: []mount.Mount{ - mnt, - }, + ctx, dockerutil.RunOpts{ + Image: "benchmarks/fio", + Mounts: mnts, }, // Sleep on the order of b.N. "sleep", fmt.Sprintf("%d", 1000*b.N), @@ -122,6 +112,9 @@ func BenchmarkFio(b *testing.B) { b.Fatalf("failed to start fio container with: %v", err) } + // Directory and filename inside container where fio will read/write. + outfile := filepath.Join(outdir, "test.txt") + // For reads, we need a file to read so make one inside the container. if strings.Contains(tc.Test, "read") { fallocateCmd := fmt.Sprintf("fallocate -l %dK %s", tc.Size, outfile) @@ -135,6 +128,7 @@ func BenchmarkFio(b *testing.B) { if err := harness.DropCaches(machine); err != nil { b.Skipf("failed to drop caches with %v. You probably need root.", err) } + cmd := tc.MakeCmd(outfile) if err := harness.DropCaches(machine); err != nil { @@ -154,39 +148,6 @@ func BenchmarkFio(b *testing.B) { } } -// makeMount makes a mount and cleanup based on the requested type. Bind -// and volume mounts are backed by a temp directory made with mktemp. -// tmpfs mounts require no such backing and are just made. -// It is up to the caller to call the returned cleanup. -func makeMount(machine harness.Machine, mountType mount.Type, target string) (mount.Mount, func(), error) { - switch mountType { - case mount.TypeVolume, mount.TypeBind: - dir, err := machine.RunCommand("mktemp", "-d") - if err != nil { - return mount.Mount{}, func() {}, fmt.Errorf("failed to create tempdir: %v", err) - } - dir = strings.TrimSuffix(dir, "\n") - - out, err := machine.RunCommand("chmod", "777", dir) - if err != nil { - machine.RunCommand("rm", "-rf", dir) - return mount.Mount{}, func() {}, fmt.Errorf("failed modify directory: %v %s", err, out) - } - return mount.Mount{ - Target: target, - Source: dir, - Type: mount.TypeBind, - }, func() { machine.RunCommand("rm", "-rf", dir) }, nil - case mount.TypeTmpfs: - return mount.Mount{ - Target: target, - Type: mount.TypeTmpfs, - }, func() {}, nil - default: - return mount.Mount{}, func() {}, fmt.Errorf("illegal mount time not supported: %v", mountType) - } -} - // TestMain is the main method for package fs. func TestMain(m *testing.M) { harness.Init() diff --git a/test/benchmarks/harness/BUILD b/test/benchmarks/harness/BUILD index c2e316709..116610938 100644 --- a/test/benchmarks/harness/BUILD +++ b/test/benchmarks/harness/BUILD @@ -14,5 +14,6 @@ go_library( deps = [ "//pkg/test/dockerutil", "//pkg/test/testutil", + "@com_github_docker_docker//api/types/mount:go_default_library", ], ) diff --git a/test/benchmarks/harness/util.go b/test/benchmarks/harness/util.go index aeac7ebff..36abe1069 100644 --- a/test/benchmarks/harness/util.go +++ b/test/benchmarks/harness/util.go @@ -18,8 +18,10 @@ import ( "context" "fmt" "net" + "strings" "testing" + "github.com/docker/docker/api/types/mount" "gvisor.dev/gvisor/pkg/test/dockerutil" "gvisor.dev/gvisor/pkg/test/testutil" ) @@ -55,3 +57,53 @@ func DebugLog(b *testing.B, msg string, args ...interface{}) { b.Logf(msg, args...) } } + +const ( + // BindFS indicates a bind mount should be created. + BindFS = "bindfs" + // TmpFS indicates a tmpfs mount should be created. + TmpFS = "tmpfs" + // RootFS indicates no mount should be created and the root mount should be used. + RootFS = "rootfs" +) + +// MakeMount makes a mount and cleanup based on the requested type. Bind +// and volume mounts are backed by a temp directory made with mktemp. +// tmpfs mounts require no such backing and are just made. +// rootfs mounts do not make a mount, but instead return a target direectory at root. +// It is up to the caller to call the returned cleanup. +func MakeMount(machine Machine, fsType string) ([]mount.Mount, string, func(), error) { + mounts := make([]mount.Mount, 0, 1) + switch fsType { + case BindFS: + dir, err := machine.RunCommand("mktemp", "-d") + if err != nil { + return mounts, "", func() {}, fmt.Errorf("failed to create tempdir: %v", err) + } + dir = strings.TrimSuffix(dir, "\n") + + out, err := machine.RunCommand("chmod", "777", dir) + if err != nil { + machine.RunCommand("rm", "-rf", dir) + return mounts, "", func() {}, fmt.Errorf("failed modify directory: %v %s", err, out) + } + target := "/data" + mounts = append(mounts, mount.Mount{ + Target: target, + Source: dir, + Type: mount.TypeBind, + }) + return mounts, target, func() { machine.RunCommand("rm", "-rf", dir) }, nil + case RootFS: + return mounts, "/", func() {}, nil + case TmpFS: + target := "/data" + mounts = append(mounts, mount.Mount{ + Target: target, + Type: mount.TypeTmpfs, + }) + return mounts, target, func() {}, nil + default: + return mounts, "", func() {}, fmt.Errorf("illegal mount type not supported: %v", fsType) + } +} diff --git a/test/benchmarks/tcp/tcp_proxy.go b/test/benchmarks/tcp/tcp_proxy.go index 780e4f7ae..be74e4d4a 100644 --- a/test/benchmarks/tcp/tcp_proxy.go +++ b/test/benchmarks/tcp/tcp_proxy.go @@ -29,7 +29,6 @@ import ( "runtime" "runtime/pprof" "strconv" - "syscall" "time" "golang.org/x/sys/unix" @@ -112,33 +111,33 @@ func setupNetwork(ifaceName string, numChannels int) (fds []int, err error) { const protocol = 0x0300 // htons(ETH_P_ALL) fds := make([]int, numChannels) for i := range fds { - fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, protocol) + fd, err := unix.Socket(unix.AF_PACKET, unix.SOCK_RAW, protocol) if err != nil { return nil, fmt.Errorf("unable to create raw socket: %v", err) } // Bind to the appropriate device. - ll := syscall.SockaddrLinklayer{ + ll := unix.SockaddrLinklayer{ Protocol: protocol, Ifindex: iface.Index, - Pkttype: syscall.PACKET_HOST, + Pkttype: unix.PACKET_HOST, } - if err := syscall.Bind(fd, &ll); err != nil { + if err := unix.Bind(fd, &ll); err != nil { return nil, fmt.Errorf("unable to bind to %q: %v", iface.Name, err) } // RAW Sockets by default have a very small SO_RCVBUF of 256KB, // up it to at least 4MB to reduce packet drops. - if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_RCVBUF, bufSize); err != nil { + if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_RCVBUF, bufSize); err != nil { return nil, fmt.Errorf("setsockopt(..., SO_RCVBUF, %v,..) = %v", bufSize, err) } - if err := syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_SNDBUF, bufSize); err != nil { + if err := unix.SetsockoptInt(fd, unix.SOL_SOCKET, unix.SO_SNDBUF, bufSize); err != nil { return nil, fmt.Errorf("setsockopt(..., SO_SNDBUF, %v,..) = %v", bufSize, err) } if !*swgso && *gso != 0 { - if err := syscall.SetsockoptInt(fd, syscall.SOL_PACKET, unix.PACKET_VNET_HDR, 1); err != nil { + if err := unix.SetsockoptInt(fd, unix.SOL_PACKET, unix.PACKET_VNET_HDR, 1); err != nil { return nil, fmt.Errorf("unable to enable the PACKET_VNET_HDR option: %v", err) } } @@ -403,7 +402,7 @@ func main() { log.Printf("client=%v, server=%v, ready.", *client, *server) sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGTERM) + signal.Notify(sigs, unix.SIGTERM) go func() { <-sigs if *cpuprofile != "" { diff --git a/test/fuse/linux/mount_test.cc b/test/fuse/linux/mount_test.cc index 8a5478116..276f842ea 100644 --- a/test/fuse/linux/mount_test.cc +++ b/test/fuse/linux/mount_test.cc @@ -15,6 +15,7 @@ #include <errno.h> #include <fcntl.h> #include <sys/mount.h> +#include <unistd.h> #include "gtest/gtest.h" #include "test/util/mount_util.h" @@ -29,7 +30,9 @@ namespace { TEST(FuseMount, Success) { const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/dev/fuse", O_WRONLY)); - std::string mopts = absl::StrCat("fd=", std::to_string(fd.get())); + std::string mopts = + absl::StrFormat("fd=%d,user_id=%d,group_id=%d,rootmode=0777", fd.get(), + getuid(), getgid()); const auto dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir()); diff --git a/test/iptables/BUILD b/test/iptables/BUILD index ae4bba847..94d4ca2d4 100644 --- a/test/iptables/BUILD +++ b/test/iptables/BUILD @@ -18,6 +18,7 @@ go_library( "//pkg/binary", "//pkg/test/testutil", "//pkg/usermem", + "@org_golang_x_sys//unix:go_default_library", ], ) diff --git a/test/iptables/iptables_unsafe.go b/test/iptables/iptables_unsafe.go index bd85a8fea..dd1a1c082 100644 --- a/test/iptables/iptables_unsafe.go +++ b/test/iptables/iptables_unsafe.go @@ -16,12 +16,13 @@ package iptables import ( "fmt" - "syscall" "unsafe" + + "golang.org/x/sys/unix" ) type originalDstError struct { - errno syscall.Errno + errno unix.Errno } func (e originalDstError) Error() string { @@ -32,27 +33,27 @@ func (e originalDstError) Error() string { // getsockopt. const SO_ORIGINAL_DST = 80 -func originalDestination4(connfd int) (syscall.RawSockaddrInet4, error) { - var addr syscall.RawSockaddrInet4 - var addrLen uint32 = syscall.SizeofSockaddrInet4 - if errno := originalDestination(connfd, syscall.SOL_IP, unsafe.Pointer(&addr), &addrLen); errno != 0 { - return syscall.RawSockaddrInet4{}, originalDstError{errno} +func originalDestination4(connfd int) (unix.RawSockaddrInet4, error) { + var addr unix.RawSockaddrInet4 + var addrLen uint32 = unix.SizeofSockaddrInet4 + if errno := originalDestination(connfd, unix.SOL_IP, unsafe.Pointer(&addr), &addrLen); errno != 0 { + return unix.RawSockaddrInet4{}, originalDstError{errno} } return addr, nil } -func originalDestination6(connfd int) (syscall.RawSockaddrInet6, error) { - var addr syscall.RawSockaddrInet6 - var addrLen uint32 = syscall.SizeofSockaddrInet6 - if errno := originalDestination(connfd, syscall.SOL_IPV6, unsafe.Pointer(&addr), &addrLen); errno != 0 { - return syscall.RawSockaddrInet6{}, originalDstError{errno} +func originalDestination6(connfd int) (unix.RawSockaddrInet6, error) { + var addr unix.RawSockaddrInet6 + var addrLen uint32 = unix.SizeofSockaddrInet6 + if errno := originalDestination(connfd, unix.SOL_IPV6, unsafe.Pointer(&addr), &addrLen); errno != 0 { + return unix.RawSockaddrInet6{}, originalDstError{errno} } return addr, nil } -func originalDestination(connfd int, level uintptr, optval unsafe.Pointer, optlen *uint32) syscall.Errno { - _, _, errno := syscall.Syscall6( - syscall.SYS_GETSOCKOPT, +func originalDestination(connfd int, level uintptr, optval unsafe.Pointer, optlen *uint32) unix.Errno { + _, _, errno := unix.Syscall6( + unix.SYS_GETSOCKOPT, uintptr(connfd), level, SO_ORIGINAL_DST, diff --git a/test/iptables/nat.go b/test/iptables/nat.go index 7f1d6d7ad..70d8a1832 100644 --- a/test/iptables/nat.go +++ b/test/iptables/nat.go @@ -19,8 +19,8 @@ import ( "errors" "fmt" "net" - "syscall" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/binary" "gvisor.dev/gvisor/pkg/usermem" ) @@ -584,33 +584,33 @@ func listenForRedirectedConn(ctx context.Context, ipv6 bool, originalDsts []net. // traditional syscalls. // Create the listening socket, bind, listen, and accept. - family := syscall.AF_INET + family := unix.AF_INET if ipv6 { - family = syscall.AF_INET6 + family = unix.AF_INET6 } - sockfd, err := syscall.Socket(family, syscall.SOCK_STREAM, 0) + sockfd, err := unix.Socket(family, unix.SOCK_STREAM, 0) if err != nil { return err } - defer syscall.Close(sockfd) + defer unix.Close(sockfd) - var bindAddr syscall.Sockaddr + var bindAddr unix.Sockaddr if ipv6 { - bindAddr = &syscall.SockaddrInet6{ + bindAddr = &unix.SockaddrInet6{ Port: acceptPort, Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any } } else { - bindAddr = &syscall.SockaddrInet4{ + bindAddr = &unix.SockaddrInet4{ Port: acceptPort, Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY } } - if err := syscall.Bind(sockfd, bindAddr); err != nil { + if err := unix.Bind(sockfd, bindAddr); err != nil { return err } - if err := syscall.Listen(sockfd, 1); err != nil { + if err := unix.Listen(sockfd, 1); err != nil { return err } @@ -619,8 +619,8 @@ func listenForRedirectedConn(ctx context.Context, ipv6 bool, originalDsts []net. errCh := make(chan error) go func() { for { - connFD, _, err := syscall.Accept(sockfd) - if errors.Is(err, syscall.EINTR) { + connFD, _, err := unix.Accept(sockfd) + if errors.Is(err, unix.EINTR) { continue } if err != nil { @@ -641,7 +641,7 @@ func listenForRedirectedConn(ctx context.Context, ipv6 bool, originalDsts []net. return err case connFD = <-connCh: } - defer syscall.Close(connFD) + defer unix.Close(connFD) // Verify that, despite listening on acceptPort, SO_ORIGINAL_DST // indicates the packet was sent to originalDst:dropPort. @@ -764,35 +764,35 @@ func recvWithRECVORIGDSTADDR(ctx context.Context, ipv6 bool, expectedDst *net.IP // Create the listening socket. var ( - family = syscall.AF_INET - level = syscall.SOL_IP - option = syscall.IP_RECVORIGDSTADDR - bindAddr syscall.Sockaddr = &syscall.SockaddrInet4{ + family = unix.AF_INET + level = unix.SOL_IP + option = unix.IP_RECVORIGDSTADDR + bindAddr unix.Sockaddr = &unix.SockaddrInet4{ Port: int(port), Addr: [4]byte{0, 0, 0, 0}, // INADDR_ANY } ) if ipv6 { - family = syscall.AF_INET6 - level = syscall.SOL_IPV6 + family = unix.AF_INET6 + level = unix.SOL_IPV6 option = 74 // IPV6_RECVORIGDSTADDR, which is missing from the syscall package. - bindAddr = &syscall.SockaddrInet6{ + bindAddr = &unix.SockaddrInet6{ Port: int(port), Addr: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, // in6addr_any } } - sockfd, err := syscall.Socket(family, syscall.SOCK_DGRAM, 0) + sockfd, err := unix.Socket(family, unix.SOCK_DGRAM, 0) if err != nil { - return fmt.Errorf("failed Socket(%d, %d, 0): %w", family, syscall.SOCK_DGRAM, err) + return fmt.Errorf("failed Socket(%d, %d, 0): %w", family, unix.SOCK_DGRAM, err) } - defer syscall.Close(sockfd) + defer unix.Close(sockfd) - if err := syscall.Bind(sockfd, bindAddr); err != nil { + if err := unix.Bind(sockfd, bindAddr); err != nil { return fmt.Errorf("failed Bind(%d, %+v): %v", sockfd, bindAddr, err) } // Enable IP_RECVORIGDSTADDR. - if err := syscall.SetsockoptInt(sockfd, level, option, 1); err != nil { + if err := unix.SetsockoptInt(sockfd, level, option, 1); err != nil { return fmt.Errorf("failed SetsockoptByte(%d, %d, %d, 1): %v", sockfd, level, option, err) } @@ -837,41 +837,41 @@ func recvWithRECVORIGDSTADDR(ctx context.Context, ipv6 bool, expectedDst *net.IP // Verify that the address has the post-NAT port and address. if ipv6 { - return addrMatches6(addr.(syscall.RawSockaddrInet6), localAddrs, redirectPort) + return addrMatches6(addr.(unix.RawSockaddrInet6), localAddrs, redirectPort) } - return addrMatches4(addr.(syscall.RawSockaddrInet4), localAddrs, redirectPort) + return addrMatches4(addr.(unix.RawSockaddrInet4), localAddrs, redirectPort) } -func recvOrigDstAddr4(sockfd int) (syscall.RawSockaddrInet4, error) { - buf, err := recvOrigDstAddr(sockfd, syscall.SOL_IP, syscall.SizeofSockaddrInet4) +func recvOrigDstAddr4(sockfd int) (unix.RawSockaddrInet4, error) { + buf, err := recvOrigDstAddr(sockfd, unix.SOL_IP, unix.SizeofSockaddrInet4) if err != nil { - return syscall.RawSockaddrInet4{}, err + return unix.RawSockaddrInet4{}, err } - var addr syscall.RawSockaddrInet4 + var addr unix.RawSockaddrInet4 binary.Unmarshal(buf, usermem.ByteOrder, &addr) return addr, nil } -func recvOrigDstAddr6(sockfd int) (syscall.RawSockaddrInet6, error) { - buf, err := recvOrigDstAddr(sockfd, syscall.SOL_IP, syscall.SizeofSockaddrInet6) +func recvOrigDstAddr6(sockfd int) (unix.RawSockaddrInet6, error) { + buf, err := recvOrigDstAddr(sockfd, unix.SOL_IP, unix.SizeofSockaddrInet6) if err != nil { - return syscall.RawSockaddrInet6{}, err + return unix.RawSockaddrInet6{}, err } - var addr syscall.RawSockaddrInet6 + var addr unix.RawSockaddrInet6 binary.Unmarshal(buf, usermem.ByteOrder, &addr) return addr, nil } func recvOrigDstAddr(sockfd int, level uintptr, addrSize int) ([]byte, error) { buf := make([]byte, 64) - oob := make([]byte, syscall.CmsgSpace(addrSize)) + oob := make([]byte, unix.CmsgSpace(addrSize)) for { - _, oobn, _, _, err := syscall.Recvmsg( + _, oobn, _, _, err := unix.Recvmsg( sockfd, buf, // Message buffer. oob, // Out-of-band buffer. 0) // Flags. - if errors.Is(err, syscall.EINTR) { + if errors.Is(err, unix.EINTR) { continue } if err != nil { @@ -880,7 +880,7 @@ func recvOrigDstAddr(sockfd int, level uintptr, addrSize int) ([]byte, error) { oob = oob[:oobn] // Parse out the control message. - msgs, err := syscall.ParseSocketControlMessage(oob) + msgs, err := unix.ParseSocketControlMessage(oob) if err != nil { return nil, fmt.Errorf("failed to parse control message: %w", err) } @@ -888,10 +888,10 @@ func recvOrigDstAddr(sockfd int, level uintptr, addrSize int) ([]byte, error) { } } -func addrMatches4(got syscall.RawSockaddrInet4, wantAddrs []net.IP, port uint16) error { +func addrMatches4(got unix.RawSockaddrInet4, wantAddrs []net.IP, port uint16) error { for _, wantAddr := range wantAddrs { - want := syscall.RawSockaddrInet4{ - Family: syscall.AF_INET, + want := unix.RawSockaddrInet4{ + Family: unix.AF_INET, Port: htons(port), } copy(want.Addr[:], wantAddr.To4()) @@ -902,10 +902,10 @@ func addrMatches4(got syscall.RawSockaddrInet4, wantAddrs []net.IP, port uint16) return fmt.Errorf("got %+v, but wanted one of %+v (note: port numbers are in network byte order)", got, wantAddrs) } -func addrMatches6(got syscall.RawSockaddrInet6, wantAddrs []net.IP, port uint16) error { +func addrMatches6(got unix.RawSockaddrInet6, wantAddrs []net.IP, port uint16) error { for _, wantAddr := range wantAddrs { - want := syscall.RawSockaddrInet6{ - Family: syscall.AF_INET6, + want := unix.RawSockaddrInet6{ + Family: unix.AF_INET6, Port: htons(port), } copy(want.Addr[:], wantAddr.To16()) diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc index 0d93b806e..ea83bbe72 100644 --- a/test/packetimpact/dut/posix_server.cc +++ b/test/packetimpact/dut/posix_server.cc @@ -395,7 +395,8 @@ class PosixImpl final : public posix_server::Posix::Service { ::grpc::Status Shutdown(grpc::ServerContext *context, const ::posix_server::ShutdownRequest *request, ::posix_server::ShutdownResponse *response) override { - if (shutdown(request->fd(), request->how()) < 0) { + response->set_ret(shutdown(request->fd(), request->how())); + if (response->ret() < 0) { response->set_errno_(errno); } return ::grpc::Status::OK; diff --git a/test/packetimpact/proto/posix_server.proto b/test/packetimpact/proto/posix_server.proto index 521f03465..175a65336 100644 --- a/test/packetimpact/proto/posix_server.proto +++ b/test/packetimpact/proto/posix_server.proto @@ -214,7 +214,8 @@ message ShutdownRequest { } message ShutdownResponse { - int32 errno_ = 1; // "errno" may fail to compile in c++. + int32 ret = 1; + int32 errno_ = 2; // "errno" may fail to compile in c++. } message RecvRequest { diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl index 8ce5edf2b..567f64c41 100644 --- a/test/packetimpact/runner/defs.bzl +++ b/test/packetimpact/runner/defs.bzl @@ -203,6 +203,11 @@ ALL_TESTS = [ name = "tcp_outside_the_window", ), PacketimpactTestInfo( + name = "tcp_outside_the_window_closing", + # TODO(b/181625316): Fix netstack then merge into tcp_outside_the_window. + expect_netstack_failure = True, + ), + PacketimpactTestInfo( name = "tcp_noaccept_close_rst", ), PacketimpactTestInfo( @@ -212,6 +217,11 @@ ALL_TESTS = [ name = "tcp_unacc_seq_ack", ), PacketimpactTestInfo( + name = "tcp_unacc_seq_ack_closing", + # TODO(b/181625316): Fix netstack then merge into tcp_unacc_seq_ack. + expect_netstack_failure = True, + ), + PacketimpactTestInfo( name = "tcp_paws_mechanism", # TODO(b/156682000): Fix netstack then remove the line below. expect_netstack_failure = True, @@ -277,6 +287,11 @@ ALL_TESTS = [ PacketimpactTestInfo( name = "tcp_info", ), + PacketimpactTestInfo( + name = "tcp_fin_retransmission", + # TODO(b/181625316): Fix netstack then remove the line below. + expect_netstack_failure = True, + ), ] def validate_all_tests(): diff --git a/test/packetimpact/runner/dut.go b/test/packetimpact/runner/dut.go index 3da265b78..1064ca976 100644 --- a/test/packetimpact/runner/dut.go +++ b/test/packetimpact/runner/dut.go @@ -109,6 +109,7 @@ type dutInfo struct { dut DUT ctrlNet, testNet *dockerutil.Network netInfo *testbench.DUTTestNet + uname *testbench.DUTUname } // setUpDUT will set up one DUT and return information for setting up the @@ -182,6 +183,10 @@ func setUpDUT(ctx context.Context, t *testing.T, id int, mkDevice func(*dockerut POSIXServerIP: AddressInSubnet(DUTAddr, *ctrlNet.Subnet), POSIXServerPort: CtrlPort, } + info.uname, err = dut.Uname(ctx) + if err != nil { + return dutInfo{}, fmt.Errorf("failed to get uname information on DUT: %w", err) + } return info, nil } @@ -195,7 +200,7 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co dutInfoChan := make(chan dutInfo, numDUTs) errChan := make(chan error, numDUTs) var dockerNetworks []*dockerutil.Network - var dutTestNets []*testbench.DUTTestNet + var dutInfos []*testbench.DUTInfo var duts []DUT setUpCtx, cancelSetup := context.WithCancel(ctx) @@ -214,7 +219,10 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co select { case info := <-dutInfoChan: dockerNetworks = append(dockerNetworks, info.ctrlNet, info.testNet) - dutTestNets = append(dutTestNets, info.netInfo) + dutInfos = append(dutInfos, &testbench.DUTInfo{ + Net: info.netInfo, + Uname: info.uname, + }) duts = append(duts, info.dut) case err := <-errChan: t.Fatal(err) @@ -241,28 +249,29 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co testbenchContainer, testbenchAddr, dockerNetworks, + nil, /* sysctls */ "tail", "-f", "/dev/null", ); err != nil { t.Fatalf("cannot start testbench container: %s", err) } - for i := range dutTestNets { - name, info, err := deviceByIP(ctx, testbenchContainer, dutTestNets[i].LocalIPv4) + for i := range dutInfos { + name, info, err := deviceByIP(ctx, testbenchContainer, dutInfos[i].Net.LocalIPv4) if err != nil { - t.Fatalf("failed to get the device name associated with %s: %s", dutTestNets[i].LocalIPv4, err) + t.Fatalf("failed to get the device name associated with %s: %s", dutInfos[i].Net.LocalIPv4, err) } - dutTestNets[i].LocalDevName = name - dutTestNets[i].LocalDevID = info.ID - dutTestNets[i].LocalMAC = info.MAC + dutInfos[i].Net.LocalDevName = name + dutInfos[i].Net.LocalDevID = info.ID + dutInfos[i].Net.LocalMAC = info.MAC localIPv6, err := getOrAssignIPv6Addr(ctx, testbenchContainer, name) if err != nil { t.Fatalf("failed to get IPV6 address on %s: %s", testbenchContainer.Name, err) } - dutTestNets[i].LocalIPv6 = localIPv6 + dutInfos[i].Net.LocalIPv6 = localIPv6 } - dutTestNetsBytes, err := json.Marshal(dutTestNets) + dutInfosBytes, err := json.Marshal(dutInfos) if err != nil { - t.Fatalf("failed to marshal %v into json: %s", dutTestNets, err) + t.Fatalf("failed to marshal %v into json: %s", dutInfos, err) } baseSnifferArgs := []string{ @@ -296,7 +305,8 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co "-n", } } - for _, n := range dutTestNets { + for _, info := range dutInfos { + n := info.Net snifferArgs := append(baseSnifferArgs, "-i", n.LocalDevName) if !tshark { snifferArgs = append( @@ -351,7 +361,7 @@ func TestWithDUT(ctx context.Context, t *testing.T, mkDevice func(*dockerutil.Co testArgs = append(testArgs, extraTestArgs...) testArgs = append(testArgs, fmt.Sprintf("--native=%t", native), - "--dut_test_nets_json", string(dutTestNetsBytes), + "--dut_infos_json", string(dutInfosBytes), ) testbenchLogs, err := testbenchContainer.Exec(ctx, dockerutil.ExecOpts{}, testArgs...) if (err != nil) != expectFailure { @@ -388,6 +398,10 @@ type DUT interface { // The t parameter is supposed to be used for t.Cleanup. Don't use it for // t.Fatal/FailNow functions. Prepare(ctx context.Context, t *testing.T, runOpts dockerutil.RunOpts, ctrlNet, testNet *dockerutil.Network) (net.IP, net.HardwareAddr, uint32, string, error) + + // Uname gathers information of DUT using command uname. + Uname(ctx context.Context) (*testbench.DUTUname, error) + // Logs retrieves the logs from the dut. Logs(ctx context.Context) (string, error) } @@ -415,6 +429,10 @@ func (dut *DockerDUT) Prepare(ctx context.Context, _ *testing.T, runOpts dockeru dut.c, DUTAddr, []*dockerutil.Network{ctrlNet, testNet}, + map[string]string{ + // This enables creating ICMP sockets on Linux. + "net.ipv4.ping_group_range": "0 0", + }, containerPosixServerBinary, "--ip=0.0.0.0", fmt.Sprintf("--port=%d", CtrlPort), @@ -440,6 +458,38 @@ func (dut *DockerDUT) Prepare(ctx context.Context, _ *testing.T, runOpts dockeru return remoteIPv6, dutDeviceInfo.MAC, dutDeviceInfo.ID, testNetDev, nil } +// Uname implements DUT.Uname. +func (dut *DockerDUT) Uname(ctx context.Context) (*testbench.DUTUname, error) { + machine, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-m") + if err != nil { + return nil, err + } + kernelRelease, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-r") + if err != nil { + return nil, err + } + kernelVersion, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-v") + if err != nil { + return nil, err + } + kernelName, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-s") + if err != nil { + return nil, err + } + // TODO(gvisor.dev/issues/5586): -o is not supported on macOS. + operatingSystem, err := dut.c.Exec(ctx, dockerutil.ExecOpts{}, "uname", "-o") + if err != nil { + return nil, err + } + return &testbench.DUTUname{ + Machine: strings.TrimRight(machine, "\n"), + KernelName: strings.TrimRight(kernelName, "\n"), + KernelRelease: strings.TrimRight(kernelRelease, "\n"), + KernelVersion: strings.TrimRight(kernelVersion, "\n"), + OperatingSystem: strings.TrimRight(operatingSystem, "\n"), + }, nil +} + // Logs implements DUT.Logs. func (dut *DockerDUT) Logs(ctx context.Context) (string, error) { logs, err := dut.c.Logs(ctx) @@ -545,11 +595,14 @@ func createDockerNetwork(ctx context.Context, n *dockerutil.Network) error { // StartContainer will create a container instance from runOpts, connect it // with the specified docker networks and start executing the specified cmd. -func StartContainer(ctx context.Context, runOpts dockerutil.RunOpts, c *dockerutil.Container, containerAddr net.IP, ns []*dockerutil.Network, cmd ...string) error { +func StartContainer(ctx context.Context, runOpts dockerutil.RunOpts, c *dockerutil.Container, containerAddr net.IP, ns []*dockerutil.Network, sysctls map[string]string, cmd ...string) error { conf, hostconf, netconf := c.ConfigsFrom(runOpts, cmd...) _ = netconf hostconf.AutoRemove = true hostconf.Sysctls = map[string]string{"net.ipv6.conf.all.disable_ipv6": "0"} + for k, v := range sysctls { + hostconf.Sysctls[k] = v + } if err := c.CreateFrom(ctx, runOpts.Image, conf, hostconf, nil); err != nil { return fmt.Errorf("unable to create container %s: %w", c.Name, err) diff --git a/test/packetimpact/testbench/BUILD b/test/packetimpact/testbench/BUILD index 983c2c030..43b4c7ca1 100644 --- a/test/packetimpact/testbench/BUILD +++ b/test/packetimpact/testbench/BUILD @@ -1,7 +1,6 @@ load("//tools:defs.bzl", "go_library", "go_test") package( - default_visibility = ["//test/packetimpact:__subpackages__"], licenses = ["notice"], ) @@ -15,6 +14,7 @@ go_library( "rawsockets.go", "testbench.go", ], + visibility = ["//test/packetimpact:__subpackages__"], deps = [ "//pkg/tcpip", "//pkg/tcpip/buffer", diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go index 15e1a51de..8ad9040ff 100644 --- a/test/packetimpact/testbench/connections.go +++ b/test/packetimpact/testbench/connections.go @@ -677,17 +677,17 @@ func (conn *TCPIPv4) Connect(t *testing.T) { t.Helper() // Send the SYN. - conn.Send(t, TCP{Flags: Uint8(header.TCPFlagSyn)}) + conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagSyn)}) // Wait for the SYN-ACK. - synAck, err := conn.Expect(t, TCP{Flags: Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) + synAck, err := conn.Expect(t, TCP{Flags: TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("didn't get synack during handshake: %s", err) } conn.layerStates[len(conn.layerStates)-1].(*tcpState).synAck = synAck // Send an ACK. - conn.Send(t, TCP{Flags: Uint8(header.TCPFlagAck)}) + conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagAck)}) } // ConnectWithOptions performs a TCP 3-way handshake with given TCP options. @@ -696,17 +696,17 @@ func (conn *TCPIPv4) ConnectWithOptions(t *testing.T, options []byte) { t.Helper() // Send the SYN. - conn.Send(t, TCP{Flags: Uint8(header.TCPFlagSyn), Options: options}) + conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagSyn), Options: options}) // Wait for the SYN-ACK. - synAck, err := conn.Expect(t, TCP{Flags: Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) + synAck, err := conn.Expect(t, TCP{Flags: TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("didn't get synack during handshake: %s", err) } conn.layerStates[len(conn.layerStates)-1].(*tcpState).synAck = synAck // Send an ACK. - conn.Send(t, TCP{Flags: Uint8(header.TCPFlagAck)}) + conn.Send(t, TCP{Flags: TCPFlags(header.TCPFlagAck)}) } // ExpectData is a convenient method that expects a Layer and the Layer after @@ -823,6 +823,27 @@ func (conn *TCPIPv4) LocalAddr(t *testing.T) *unix.SockaddrInet4 { return sa } +// GenerateOTWSeqSegment generates a segment with +// seqnum = RCV.NXT + RCV.WND + seqNumOffset, the generated segment is only +// acceptable when seqNumOffset is 0, otherwise an ACK is expected from the +// receiver. +func GenerateOTWSeqSegment(t *testing.T, conn *TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) TCP { + t.Helper() + lastAcceptable := conn.LocalSeqNum(t).Add(windowSize) + otwSeq := uint32(lastAcceptable.Add(seqNumOffset)) + return TCP{SeqNum: Uint32(otwSeq), Flags: TCPFlags(header.TCPFlagAck)} +} + +// GenerateUnaccACKSegment generates a segment with +// acknum = SND.NXT + seqNumOffset, the generated segment is only acceptable +// when seqNumOffset is 0, otherwise an ACK is expected from the receiver. +func GenerateUnaccACKSegment(t *testing.T, conn *TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) TCP { + t.Helper() + lastAcceptable := conn.RemoteSeqNum(t) + unaccAck := uint32(lastAcceptable.Add(seqNumOffset)) + return TCP{AckNum: Uint32(unaccAck), Flags: TCPFlags(header.TCPFlagAck)} +} + // IPv4Conn maintains the state for all the layers in a IPv4 connection. type IPv4Conn struct { Connection diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go index be5121d98..eabdc8cb3 100644 --- a/test/packetimpact/testbench/dut.go +++ b/test/packetimpact/testbench/dut.go @@ -19,7 +19,6 @@ import ( "encoding/binary" "fmt" "net" - "syscall" "testing" "time" @@ -35,24 +34,26 @@ type DUT struct { conn *grpc.ClientConn posixServer POSIXClient Net *DUTTestNet + Uname *DUTUname } // NewDUT creates a new connection with the DUT over gRPC. func NewDUT(t *testing.T) DUT { t.Helper() - n := GetDUTTestNet() - dut := n.ConnectToDUT(t) + info := getDUTInfo() + dut := info.ConnectToDUT(t) t.Cleanup(func() { dut.TearDownConnection() - dut.Net.Release() + info.release() }) return dut } // ConnectToDUT connects to DUT through gRPC. -func (n *DUTTestNet) ConnectToDUT(t *testing.T) DUT { +func (info *DUTInfo) ConnectToDUT(t *testing.T) DUT { t.Helper() + n := info.Net posixServerAddress := net.JoinHostPort(n.POSIXServerIP.String(), fmt.Sprintf("%d", n.POSIXServerPort)) conn, err := grpc.Dial(posixServerAddress, grpc.WithInsecure(), grpc.WithKeepaliveParams(keepalive.ClientParameters{Timeout: RPCKeepalive})) if err != nil { @@ -63,6 +64,7 @@ func (n *DUTTestNet) ConnectToDUT(t *testing.T) DUT { conn: conn, posixServer: posixServer, Net: n, + Uname: info.Uname, } } @@ -196,7 +198,7 @@ func (dut *DUT) AcceptWithErrno(ctx context.Context, t *testing.T, sockfd int32) if err != nil { t.Fatalf("failed to call Accept: %s", err) } - return resp.GetFd(), dut.protoToSockaddr(t, resp.GetAddr()), syscall.Errno(resp.GetErrno_()) + return resp.GetFd(), dut.protoToSockaddr(t, resp.GetAddr()), unix.Errno(resp.GetErrno_()) } // Bind calls bind on the DUT and causes a fatal test failure if it doesn't @@ -225,7 +227,7 @@ func (dut *DUT) BindWithErrno(ctx context.Context, t *testing.T, fd int32, sa un if err != nil { t.Fatalf("failed to call Bind: %s", err) } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), unix.Errno(resp.GetErrno_()) } // Close calls close on the DUT and causes a fatal test failure if it doesn't @@ -253,7 +255,7 @@ func (dut *DUT) CloseWithErrno(ctx context.Context, t *testing.T, fd int32) (int if err != nil { t.Fatalf("failed to call Close: %s", err) } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), unix.Errno(resp.GetErrno_()) } // Connect calls connect on the DUT and causes a fatal test failure if it @@ -267,7 +269,7 @@ func (dut *DUT) Connect(t *testing.T, fd int32, sa unix.Sockaddr) { ret, err := dut.ConnectWithErrno(ctx, t, fd, sa) // Ignore 'operation in progress' error that can be returned when the socket // is non-blocking. - if err != syscall.Errno(unix.EINPROGRESS) && ret != 0 { + if err != unix.EINPROGRESS && ret != 0 { t.Fatalf("failed to connect socket: %s", err) } } @@ -284,7 +286,7 @@ func (dut *DUT) ConnectWithErrno(ctx context.Context, t *testing.T, fd int32, sa if err != nil { t.Fatalf("failed to call Connect: %s", err) } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), unix.Errno(resp.GetErrno_()) } // GetSockName calls getsockname on the DUT and causes a fatal test failure if @@ -313,7 +315,7 @@ func (dut *DUT) GetSockNameWithErrno(ctx context.Context, t *testing.T, sockfd i if err != nil { t.Fatalf("failed to call Bind: %s", err) } - return resp.GetRet(), dut.protoToSockaddr(t, resp.GetAddr()), syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), dut.protoToSockaddr(t, resp.GetAddr()), unix.Errno(resp.GetErrno_()) } func (dut *DUT) getSockOpt(ctx context.Context, t *testing.T, sockfd, level, optname, optlen int32, typ pb.GetSockOptRequest_SockOptType) (int32, *pb.SockOptVal, error) { @@ -334,7 +336,7 @@ func (dut *DUT) getSockOpt(ctx context.Context, t *testing.T, sockfd, level, opt if optval == nil { t.Fatalf("GetSockOpt response does not contain a value") } - return resp.GetRet(), optval, syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), optval, unix.Errno(resp.GetErrno_()) } // GetSockOpt calls getsockopt on the DUT and causes a fatal test failure if it @@ -452,7 +454,7 @@ func (dut *DUT) ListenWithErrno(ctx context.Context, t *testing.T, sockfd, backl if err != nil { t.Fatalf("failed to call Listen: %s", err) } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), unix.Errno(resp.GetErrno_()) } // PollOne calls poll on the DUT and asserts that the expected event must be @@ -519,7 +521,7 @@ func (dut *DUT) PollWithErrno(ctx context.Context, t *testing.T, pfds []unix.Pol Revents: int16(protoPfd.GetEvents()), }) } - return resp.GetRet(), result, syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), result, unix.Errno(resp.GetErrno_()) } // Send calls send on the DUT and causes a fatal test failure if it doesn't @@ -550,7 +552,7 @@ func (dut *DUT) SendWithErrno(ctx context.Context, t *testing.T, sockfd int32, b if err != nil { t.Fatalf("failed to call Send: %s", err) } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), unix.Errno(resp.GetErrno_()) } // SendTo calls sendto on the DUT and causes a fatal test failure if it doesn't @@ -582,7 +584,7 @@ func (dut *DUT) SendToWithErrno(ctx context.Context, t *testing.T, sockfd int32, if err != nil { t.Fatalf("failed to call SendTo: %s", err) } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), unix.Errno(resp.GetErrno_()) } // SetNonBlocking will set O_NONBLOCK flag for fd if nonblocking @@ -602,7 +604,7 @@ func (dut *DUT) SetNonBlocking(t *testing.T, fd int32, nonblocking bool) { t.Fatalf("failed to call SetNonblocking: %s", err) } if resp.GetRet() == -1 { - t.Fatalf("fcntl(%d, %s) failed: %s", fd, resp.GetCmd(), syscall.Errno(resp.GetErrno_())) + t.Fatalf("fcntl(%d, %s) failed: %s", fd, resp.GetCmd(), unix.Errno(resp.GetErrno_())) } } @@ -619,7 +621,7 @@ func (dut *DUT) setSockOpt(ctx context.Context, t *testing.T, sockfd, level, opt if err != nil { t.Fatalf("failed to call SetSockOpt: %s", err) } - return resp.GetRet(), syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), unix.Errno(resp.GetErrno_()) } // SetSockOpt calls setsockopt on the DUT and causes a fatal test failure if it @@ -720,7 +722,7 @@ func (dut *DUT) SocketWithErrno(t *testing.T, domain, typ, proto int32) (int32, if err != nil { t.Fatalf("failed to call Socket: %s", err) } - return resp.GetFd(), syscall.Errno(resp.GetErrno_()) + return resp.GetFd(), unix.Errno(resp.GetErrno_()) } // Recv calls recv on the DUT and causes a fatal test failure if it doesn't @@ -751,7 +753,7 @@ func (dut *DUT) RecvWithErrno(ctx context.Context, t *testing.T, sockfd, len, fl if err != nil { t.Fatalf("failed to call Recv: %s", err) } - return resp.GetRet(), resp.GetBuf(), syscall.Errno(resp.GetErrno_()) + return resp.GetRet(), resp.GetBuf(), unix.Errno(resp.GetErrno_()) } // SetSockLingerOption sets SO_LINGER socket option on the DUT. @@ -771,16 +773,19 @@ func (dut *DUT) SetSockLingerOption(t *testing.T, sockfd int32, timeout time.Dur // Shutdown calls shutdown on the DUT and causes a fatal test failure if it // doesn't succeed. If more control over the timeout or error handling is // needed, use ShutdownWithErrno. -func (dut *DUT) Shutdown(t *testing.T, fd, how int32) error { +func (dut *DUT) Shutdown(t *testing.T, fd, how int32) { t.Helper() ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout) defer cancel() - return dut.ShutdownWithErrno(ctx, t, fd, how) + ret, err := dut.ShutdownWithErrno(ctx, t, fd, how) + if ret != 0 { + t.Fatalf("failed to shutdown(%d, %d): %s", fd, how, err) + } } // ShutdownWithErrno calls shutdown on the DUT. -func (dut *DUT) ShutdownWithErrno(ctx context.Context, t *testing.T, fd, how int32) error { +func (dut *DUT) ShutdownWithErrno(ctx context.Context, t *testing.T, fd, how int32) (int32, error) { t.Helper() req := &pb.ShutdownRequest{ @@ -791,5 +796,8 @@ func (dut *DUT) ShutdownWithErrno(ctx context.Context, t *testing.T, fd, how int if err != nil { t.Fatalf("failed to call Shutdown: %s", err) } - return syscall.Errno(resp.GetErrno_()) + if resp.GetErrno_() == 0 { + return resp.GetRet(), nil + } + return resp.GetRet(), unix.Errno(resp.GetErrno_()) } diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go index 64a7a171a..2311f7686 100644 --- a/test/packetimpact/testbench/layers.go +++ b/test/packetimpact/testbench/layers.go @@ -407,6 +407,12 @@ func Uint8(v uint8) *uint8 { return &v } +// TCPFlags is a helper routine that allocates a new +// header.TCPFlags value to store v and returns a pointer to it. +func TCPFlags(v header.TCPFlags) *header.TCPFlags { + return &v +} + // Address is a helper routine that allocates a new tcpip.Address value to // store v and returns a pointer to it. func Address(v tcpip.Address) *tcpip.Address { @@ -1030,7 +1036,7 @@ type TCP struct { SeqNum *uint32 AckNum *uint32 DataOffset *uint8 - Flags *uint8 + Flags *header.TCPFlags WindowSize *uint16 Checksum *uint16 UrgentPointer *uint16 @@ -1063,7 +1069,7 @@ func (l *TCP) ToBytes() ([]byte, error) { h.SetDataOffset(uint8(l.length())) } if l.Flags != nil { - h.SetFlags(*l.Flags) + h.SetFlags(uint8(*l.Flags)) } if l.WindowSize != nil { h.SetWindowSize(*l.WindowSize) @@ -1157,7 +1163,7 @@ func parseTCP(b []byte) (Layer, layerParser) { SeqNum: Uint32(h.SequenceNumber()), AckNum: Uint32(h.AckNumber()), DataOffset: Uint8(h.DataOffset()), - Flags: Uint8(h.Flags()), + Flags: TCPFlags(h.Flags()), WindowSize: Uint16(h.WindowSize()), Checksum: Uint16(h.Checksum()), UrgentPointer: Uint16(h.UrgentPointer()), diff --git a/test/packetimpact/testbench/layers_test.go b/test/packetimpact/testbench/layers_test.go index eca0780b5..614a5de1e 100644 --- a/test/packetimpact/testbench/layers_test.go +++ b/test/packetimpact/testbench/layers_test.go @@ -178,7 +178,7 @@ func TestLayerStringFormat(t *testing.T) { SeqNum: Uint32(3452155723), AckNum: Uint32(2596996163), DataOffset: Uint8(5), - Flags: Uint8(20), + Flags: TCPFlags(header.TCPFlagRst | header.TCPFlagAck), WindowSize: Uint16(64240), Checksum: Uint16(0x2e2b), }, @@ -188,7 +188,7 @@ func TestLayerStringFormat(t *testing.T) { "SeqNum:3452155723 " + "AckNum:2596996163 " + "DataOffset:5 " + - "Flags:20 " + + "Flags: R A " + "WindowSize:64240 " + "Checksum:11819" + "}", @@ -436,7 +436,7 @@ func TestTCPOptions(t *testing.T) { DstPort: Uint16(54321), SeqNum: Uint32(0), AckNum: Uint32(0), - Flags: Uint8(header.TCPFlagSyn), + Flags: TCPFlags(header.TCPFlagSyn), WindowSize: Uint16(8192), Checksum: Uint16(0xf51c), UrgentPointer: Uint16(0), @@ -480,7 +480,7 @@ func TestTCPOptions(t *testing.T) { DstPort: Uint16(54321), SeqNum: Uint32(0), AckNum: Uint32(0), - Flags: Uint8(header.TCPFlagSyn), + Flags: TCPFlags(header.TCPFlagSyn), WindowSize: Uint16(8192), Checksum: Uint16(0xe521), UrgentPointer: Uint16(0), diff --git a/test/packetimpact/testbench/testbench.go b/test/packetimpact/testbench/testbench.go index 891897d55..a73c07e64 100644 --- a/test/packetimpact/testbench/testbench.go +++ b/test/packetimpact/testbench/testbench.go @@ -34,14 +34,29 @@ var ( // RPCTimeout is the gRPC timeout. RPCTimeout = 100 * time.Millisecond - // dutTestNetsJSON is the json string that describes all the test networks to + // dutInfosJSON is the json string that describes information about all the // duts available to use. - dutTestNetsJSON string - // dutTestNets is the pool among which the testbench can choose a DUT to work + dutInfosJSON string + // dutInfo is the pool among which the testbench can choose a DUT to work // with. - dutTestNets chan *DUTTestNet + dutInfo chan *DUTInfo ) +// DUTInfo has both network and uname information about the DUT. +type DUTInfo struct { + Uname *DUTUname + Net *DUTTestNet +} + +// DUTUname contains information about the DUT from uname. +type DUTUname struct { + Machine string + KernelName string + KernelRelease string + KernelVersion string + OperatingSystem string +} + // DUTTestNet describes the test network setup on dut and how the testbench // should connect with an existing DUT. type DUTTestNet struct { @@ -86,7 +101,7 @@ func registerFlags(fs *flag.FlagSet) { fs.BoolVar(&Native, "native", Native, "whether the test is running natively") fs.DurationVar(&RPCTimeout, "rpc_timeout", RPCTimeout, "gRPC timeout") fs.DurationVar(&RPCKeepalive, "rpc_keepalive", RPCKeepalive, "gRPC keepalive") - fs.StringVar(&dutTestNetsJSON, "dut_test_nets_json", dutTestNetsJSON, "path to the dut test nets json file") + fs.StringVar(&dutInfosJSON, "dut_infos_json", dutInfosJSON, "json that describes the DUTs") } // Initialize initializes the testbench, it parse the flags and sets up the @@ -94,27 +109,27 @@ func registerFlags(fs *flag.FlagSet) { func Initialize(fs *flag.FlagSet) { registerFlags(fs) flag.Parse() - if err := loadDUTTestNets(); err != nil { + if err := loadDUTInfos(); err != nil { panic(err) } } -// loadDUTTestNets loads available DUT test networks from the json file, it +// loadDUTInfos loads available DUT test infos from the json file, it // must be called after flag.Parse(). -func loadDUTTestNets() error { - var parsedTestNets []DUTTestNet - if err := json.Unmarshal([]byte(dutTestNetsJSON), &parsedTestNets); err != nil { +func loadDUTInfos() error { + var dutInfos []DUTInfo + if err := json.Unmarshal([]byte(dutInfosJSON), &dutInfos); err != nil { return fmt.Errorf("failed to unmarshal JSON: %w", err) } - if got, want := len(parsedTestNets), 1; got < want { + if got, want := len(dutInfos), 1; got < want { return fmt.Errorf("got %d DUTs, the test requires at least %d DUTs", got, want) } // Using a buffered channel as semaphore - dutTestNets = make(chan *DUTTestNet, len(parsedTestNets)) - for i := range parsedTestNets { - parsedTestNets[i].LocalIPv4 = parsedTestNets[i].LocalIPv4.To4() - parsedTestNets[i].RemoteIPv4 = parsedTestNets[i].RemoteIPv4.To4() - dutTestNets <- &parsedTestNets[i] + dutInfo = make(chan *DUTInfo, len(dutInfos)) + for i := range dutInfos { + dutInfos[i].Net.LocalIPv4 = dutInfos[i].Net.LocalIPv4.To4() + dutInfos[i].Net.RemoteIPv4 = dutInfos[i].Net.RemoteIPv4.To4() + dutInfo <- &dutInfos[i] } return nil } @@ -130,14 +145,13 @@ func GenerateRandomPayload(t *testing.T, n int) []byte { return buf } -// GetDUTTestNet gets a usable DUTTestNet, the function will block until any -// becomes available. -func GetDUTTestNet() *DUTTestNet { - return <-dutTestNets +// getDUTInfo returns information about an available DUT from the pool. If no +// DUT is readily available, getDUTInfo blocks until one becomes available. +func getDUTInfo() *DUTInfo { + return <-dutInfo } -// Release releases the DUTTestNet back to the pool so that some other test -// can use. -func (n *DUTTestNet) Release() { - dutTestNets <- n +// release returns the DUTInfo back to the pool. +func (info *DUTInfo) release() { + dutInfo <- info } diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD index 301cf4980..d5cb0ae06 100644 --- a/test/packetimpact/tests/BUILD +++ b/test/packetimpact/tests/BUILD @@ -124,6 +124,17 @@ packetimpact_testbench( ) packetimpact_testbench( + name = "tcp_outside_the_window_closing", + srcs = ["tcp_outside_the_window_closing_test.go"], + deps = [ + "//pkg/tcpip/header", + "//pkg/tcpip/seqnum", + "//test/packetimpact/testbench", + "@org_golang_x_sys//unix:go_default_library", + ], +) + +packetimpact_testbench( name = "tcp_noaccept_close_rst", srcs = ["tcp_noaccept_close_rst_test.go"], deps = [ @@ -155,6 +166,17 @@ packetimpact_testbench( ) packetimpact_testbench( + name = "tcp_unacc_seq_ack_closing", + srcs = ["tcp_unacc_seq_ack_closing_test.go"], + deps = [ + "//pkg/tcpip/header", + "//pkg/tcpip/seqnum", + "//test/packetimpact/testbench", + "@org_golang_x_sys//unix:go_default_library", + ], +) + +packetimpact_testbench( name = "tcp_paws_mechanism", srcs = ["tcp_paws_mechanism_test.go"], deps = [ @@ -375,6 +397,16 @@ packetimpact_testbench( ], ) +packetimpact_testbench( + name = "tcp_fin_retransmission", + srcs = ["tcp_fin_retransmission_test.go"], + deps = [ + "//pkg/tcpip/header", + "//test/packetimpact/testbench", + "@org_golang_x_sys//unix:go_default_library", + ], +) + validate_all_tests() [packetimpact_go_test( diff --git a/test/packetimpact/tests/fin_wait2_timeout_test.go b/test/packetimpact/tests/fin_wait2_timeout_test.go index 11f0fcd1e..cff8ca51d 100644 --- a/test/packetimpact/tests/fin_wait2_timeout_test.go +++ b/test/packetimpact/tests/fin_wait2_timeout_test.go @@ -51,21 +51,21 @@ func TestFinWait2Timeout(t *testing.T) { } dut.Close(t, acceptFd) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { t.Fatalf("expected a FIN-ACK within 1 second but got none: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) time.Sleep(5 * time.Second) conn.Drain(t) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) if tt.linger2 { - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil { t.Fatalf("expected a RST packet within a second but got none: %s", err) } } else { - if got, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, 10*time.Second); got != nil || err == nil { + if got, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, 10*time.Second); got != nil || err == nil { t.Fatalf("expected no RST packets within ten seconds but got one: %s", got) } } diff --git a/test/packetimpact/tests/ipv4_id_uniqueness_test.go b/test/packetimpact/tests/ipv4_id_uniqueness_test.go index a63b41366..2b69ceecb 100644 --- a/test/packetimpact/tests/ipv4_id_uniqueness_test.go +++ b/test/packetimpact/tests/ipv4_id_uniqueness_test.go @@ -100,7 +100,7 @@ func TestIPv4RetransmitIdentificationUniqueness(t *testing.T) { // Let the DUT estimate RTO with RTT from the DATA-ACK. // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which // we can skip sending this ACK. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) dut.Send(t, remoteFD, tc.payload, 0) expectTCP := &testbench.TCP{SeqNum: testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))} diff --git a/test/packetimpact/tests/tcp_cork_mss_test.go b/test/packetimpact/tests/tcp_cork_mss_test.go index a7ba5035e..1db3c9883 100644 --- a/test/packetimpact/tests/tcp_cork_mss_test.go +++ b/test/packetimpact/tests/tcp_cork_mss_test.go @@ -60,24 +60,24 @@ func TestTCPCorkMSS(t *testing.T) { // Expect the segments to be coalesced and sent and capped to MSS. expectedPayload := testbench.Payload{Bytes: expectedData[:mss]} - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, &expectedPayload, time.Second); err != nil { + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, &expectedPayload, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) // Expect the coalesced segment to be split and transmitted. expectedPayload = testbench.Payload{Bytes: expectedData[mss:]} - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil { + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) } // Check for segments to *not* be held up because of TCP_CORK when // the current send window is less than MSS. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(2 * len(sampleData)))}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(2 * len(sampleData)))}) dut.Send(t, acceptFD, sampleData, 0) dut.Send(t, acceptFD, sampleData, 0) expectedPayload = testbench.Payload{Bytes: append(sampleData, sampleData...)} - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil { + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &expectedPayload, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) } diff --git a/test/packetimpact/tests/tcp_fin_retransmission_test.go b/test/packetimpact/tests/tcp_fin_retransmission_test.go new file mode 100644 index 000000000..500f7a783 --- /dev/null +++ b/test/packetimpact/tests/tcp_fin_retransmission_test.go @@ -0,0 +1,87 @@ +// Copyright 2020 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 tcp_fin_retransmission_test + +import ( + "flag" + "testing" + "time" + + "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/test/packetimpact/testbench" +) + +func init() { + testbench.Initialize(flag.CommandLine) +} + +// TestTCPClosingFinRetransmission tests that TCP implementation should retransmit +// FIN segment in CLOSING state. +func TestTCPClosingFinRetransmission(t *testing.T) { + for _, tt := range []struct { + description string + flags header.TCPFlags + }{ + {"CLOSING", header.TCPFlagAck | header.TCPFlagFin}, + {"FIN_WAIT_1", header.TCPFlagAck}, + } { + t.Run(tt.description, func(t *testing.T) { + dut := testbench.NewDUT(t) + listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) + defer dut.Close(t, listenFD) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + defer conn.Close(t) + conn.Connect(t) + acceptFD, _ := dut.Accept(t, listenFD) + defer dut.Close(t, acceptFD) + + // Give a chance for the dut to estimate RTO with RTT from the DATA-ACK. + // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which + // we can skip the next block of code. + sampleData := []byte("Sample Data") + if got, want := dut.Send(t, acceptFD, sampleData, 0), len(sampleData); int(got) != want { + t.Fatalf("got dut.Send(t, %d, %s, 0) = %d, want %d", acceptFD, sampleData, got, want) + } + if _, err := conn.ExpectData(t, &testbench.TCP{}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { + t.Fatalf("expected payload was not received: %s", err) + } + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) + + dut.Shutdown(t, acceptFD, unix.SHUT_WR) + + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { + t.Fatalf("expected FINACK from DUT, but got none: %s", err) + } + + // Do not ack the FIN from DUT so that we can test for retransmission. + seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) + conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(tt.flags)}) + + if tt.flags&header.TCPFlagFin != 0 { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { + t.Errorf("expected an ACK to our FIN, but got none: %s", err) + } + } + + if _, err := conn.Expect(t, testbench.TCP{ + SeqNum: seqNumForTheirFIN, + Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck), + }, time.Second); err != nil { + t.Errorf("expected retransmission of FIN from the DUT: %s", err) + } + }) + } +} diff --git a/test/packetimpact/tests/tcp_handshake_window_size_test.go b/test/packetimpact/tests/tcp_handshake_window_size_test.go index 5d1266f3c..668e0275c 100644 --- a/test/packetimpact/tests/tcp_handshake_window_size_test.go +++ b/test/packetimpact/tests/tcp_handshake_window_size_test.go @@ -38,8 +38,8 @@ func TestTCPHandshakeWindowSize(t *testing.T) { defer conn.Close(t) // Start handshake with zero window size. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn), WindowSize: testbench.Uint16(uint16(0))}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn), WindowSize: testbench.Uint16(uint16(0))}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { t.Fatalf("expected SYN-ACK: %s", err) } // Update the advertised window size to a non-zero value with the ACK that @@ -47,7 +47,7 @@ func TestTCPHandshakeWindowSize(t *testing.T) { // // Set the window size with MSB set and expect the dut to treat it as // an unsigned value. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(1 << 15))}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(uint16(1 << 15))}) acceptFd, _ := dut.Accept(t, listenFD) defer dut.Close(t, acceptFd) @@ -59,7 +59,7 @@ func TestTCPHandshakeWindowSize(t *testing.T) { // expect the dut to honor the recently advertised non-zero window // and actually send out the data instead of probing for zero window. dut.Send(t, acceptFd, sampleData, 0) - if _, err := conn.ExpectNextData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload, time.Second); err != nil { + if _, err := conn.ExpectNextData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) } } diff --git a/test/packetimpact/tests/tcp_info_test.go b/test/packetimpact/tests/tcp_info_test.go index 69275e54b..3fc2c7fe5 100644 --- a/test/packetimpact/tests/tcp_info_test.go +++ b/test/packetimpact/tests/tcp_info_test.go @@ -51,7 +51,7 @@ func TestTCPInfo(t *testing.T) { if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) info := linux.TCPInfo{} infoBytes := dut.GetSockOpt(t, acceptFD, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo)) diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go index bc4b64388..88942904d 100644 --- a/test/packetimpact/tests/tcp_linger_test.go +++ b/test/packetimpact/tests/tcp_linger_test.go @@ -17,7 +17,6 @@ package tcp_linger_test import ( "context" "flag" - "syscall" "testing" "time" @@ -58,10 +57,10 @@ func TestTCPLingerZeroTimeout(t *testing.T) { dut.Close(t, acceptFD) // If the linger timeout is set to zero, the DUT should send a RST. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil { t.Errorf("expected RST-ACK packet within a second but got none: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) } // TestTCPLingerOff tests when SO_LINGER is not set. DUT should send FIN-ACK @@ -75,10 +74,10 @@ func TestTCPLingerOff(t *testing.T) { dut.Close(t, acceptFD) // If SO_LINGER is not set, DUT should send a FIN-ACK. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) } // TestTCPLingerNonZeroTimeout tests when SO_LINGER is set with non-zero timeout. @@ -115,10 +114,10 @@ func TestTCPLingerNonZeroTimeout(t *testing.T) { t.Errorf("expected close to return within a second, but returned later") } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) }) } } @@ -166,10 +165,10 @@ func TestTCPLingerSendNonZeroTimeout(t *testing.T) { t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) }) } } @@ -183,19 +182,19 @@ func TestTCPLingerShutdownZeroTimeout(t *testing.T) { defer closeAll(t, dut, listenFD, conn) dut.SetSockLingerOption(t, acceptFD, 0, true) - dut.Shutdown(t, acceptFD, syscall.SHUT_RDWR) + dut.Shutdown(t, acceptFD, unix.SHUT_RDWR) dut.Close(t, acceptFD) // Shutdown will send FIN-ACK with read/write option. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) } // If the linger timeout is set to zero, the DUT should send a RST. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, time.Second); err != nil { t.Errorf("expected RST-ACK packet within a second but got none: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) } // TestTCPLingerShutdownSendNonZeroTimeout tests SO_LINGER with shutdown() and @@ -220,7 +219,7 @@ func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) { sampleData := []byte("Sample Data") dut.Send(t, acceptFD, sampleData, 0) - dut.Shutdown(t, acceptFD, syscall.SHUT_RDWR) + dut.Shutdown(t, acceptFD, unix.SHUT_RDWR) // Increase timeout as Close will take longer time to // return when SO_LINGER is set with non-zero timeout. @@ -243,10 +242,10 @@ func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) { t.Fatalf("expected a packet with payload %v: %s", samplePayload, err) } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { t.Errorf("expected FIN-ACK packet within a second but got none: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) }) } } diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go index 53dc903e4..5168450ad 100644 --- a/test/packetimpact/tests/tcp_network_unreachable_test.go +++ b/test/packetimpact/tests/tcp_network_unreachable_test.go @@ -17,7 +17,6 @@ package tcp_synsent_reset_test import ( "context" "flag" - "syscall" "testing" "time" @@ -46,12 +45,12 @@ func TestTCPSynSentUnreachable(t *testing.T) { defer cancel() sa := unix.SockaddrInet4{Port: int(port)} copy(sa.Addr[:], dut.Net.LocalIPv4) - if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != syscall.Errno(unix.EINPROGRESS) { + if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != unix.EINPROGRESS { t.Errorf("got connect() = %v, want EINPROGRESS", err) } // Get the SYN. - tcpLayers, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, nil, time.Second) + tcpLayers, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, nil, time.Second) if err != nil { t.Fatalf("expected SYN: %s", err) } @@ -100,12 +99,12 @@ func TestTCPSynSentUnreachable6(t *testing.T) { ZoneId: dut.Net.RemoteDevID, } copy(sa.Addr[:], dut.Net.LocalIPv6) - if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != syscall.Errno(unix.EINPROGRESS) { + if _, err := dut.ConnectWithErrno(ctx, t, clientFD, &sa); err != unix.EINPROGRESS { t.Errorf("got connect() = %v, want EINPROGRESS", err) } // Get the SYN. - tcpLayers, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, nil, time.Second) + tcpLayers, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, nil, time.Second) if err != nil { t.Fatalf("expected SYN: %s", err) } @@ -156,7 +155,7 @@ func getConnectError(t *testing.T, dut *testbench.DUT, fd int32) error { // failure). dut.PollOne(t, fd, unix.POLLOUT, 10*time.Second) if errno := dut.GetSockOptInt(t, fd, unix.SOL_SOCKET, unix.SO_ERROR); errno != 0 { - return syscall.Errno(errno) + return unix.Errno(errno) } return nil } diff --git a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go index d2871df08..14eb7d93b 100644 --- a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go +++ b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go @@ -40,7 +40,7 @@ func TestTcpNoAcceptCloseReset(t *testing.T) { // it will only respond RST instead of RST+ACK. dut.PollOne(t, listenFd, unix.POLLIN, time.Second) dut.Close(t, listenFd) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}, 1*time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}, 1*time.Second); err != nil { t.Fatalf("expected a RST-ACK packet but got none: %s", err) } } diff --git a/test/packetimpact/tests/tcp_outside_the_window_closing_test.go b/test/packetimpact/tests/tcp_outside_the_window_closing_test.go new file mode 100644 index 000000000..1097746c7 --- /dev/null +++ b/test/packetimpact/tests/tcp_outside_the_window_closing_test.go @@ -0,0 +1,86 @@ +// Copyright 2021 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 tcp_outside_the_window_closing_test + +import ( + "flag" + "fmt" + "testing" + "time" + + "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/seqnum" + "gvisor.dev/gvisor/test/packetimpact/testbench" +) + +func init() { + testbench.Initialize(flag.CommandLine) +} + +// TestAckOTWSeqInClosing tests that the DUT should send an ACK with +// the right ACK number when receiving a packet with OTW Seq number +// in CLOSING state. https://tools.ietf.org/html/rfc793#page-69 +func TestAckOTWSeqInClosing(t *testing.T) { + for seqNumOffset := seqnum.Size(0); seqNumOffset < 3; seqNumOffset++ { + for _, tt := range []struct { + description string + flags header.TCPFlags + payloads testbench.Layers + }{ + {"SYN", header.TCPFlagSyn, nil}, + {"SYNACK", header.TCPFlagSyn | header.TCPFlagAck, nil}, + {"ACK", header.TCPFlagAck, nil}, + {"FINACK", header.TCPFlagFin | header.TCPFlagAck, nil}, + {"Data", header.TCPFlagAck, []testbench.Layer{&testbench.Payload{Bytes: []byte("abc123")}}}, + } { + t.Run(fmt.Sprintf("%s%d", tt.description, seqNumOffset), func(t *testing.T) { + dut := testbench.NewDUT(t) + listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1) + defer dut.Close(t, listenFD) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + defer conn.Close(t) + conn.Connect(t) + acceptFD, _ := dut.Accept(t, listenFD) + defer dut.Close(t, acceptFD) + + dut.Shutdown(t, acceptFD, unix.SHUT_WR) + + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second); err != nil { + t.Fatalf("expected FINACK from DUT, but got none: %s", err) + } + + // Do not ack the FIN from DUT so that the TCP state on DUT is CLOSING instead of CLOSED. + seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) + conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) + + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { + t.Errorf("expected an ACK to our FIN, but got none: %s", err) + } + + windowSize := seqnum.Size(*conn.SynAck(t).WindowSize) + seqNumOffset + conn.SendFrameStateless(t, conn.CreateFrame(t, testbench.Layers{&testbench.TCP{ + SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), + AckNum: seqNumForTheirFIN, + Flags: testbench.TCPFlags(tt.flags), + }}, tt.payloads...)) + + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { + t.Errorf("expected an ACK but got none: %s", err) + } + }) + } + } +} diff --git a/test/packetimpact/tests/tcp_outside_the_window_test.go b/test/packetimpact/tests/tcp_outside_the_window_test.go index 8909a348e..7cd7ff703 100644 --- a/test/packetimpact/tests/tcp_outside_the_window_test.go +++ b/test/packetimpact/tests/tcp_outside_the_window_test.go @@ -37,7 +37,7 @@ func init() { func TestTCPOutsideTheWindow(t *testing.T) { for _, tt := range []struct { description string - tcpFlags uint8 + tcpFlags header.TCPFlags payload []testbench.Layer seqNumOffset seqnum.Size expectACK bool @@ -76,11 +76,11 @@ func TestTCPOutsideTheWindow(t *testing.T) { // to the AckNum. localSeqNum := testbench.Uint32(uint32(*conn.LocalSeqNum(t))) conn.Send(t, testbench.TCP{ - Flags: testbench.Uint8(tt.tcpFlags), + Flags: testbench.TCPFlags(tt.tcpFlags), SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), }, tt.payload...) - timeout := 3 * time.Second - gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: localSeqNum}, timeout) + timeout := time.Second + gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: localSeqNum}, timeout) if tt.expectACK && err != nil { t.Fatalf("expected an ACK packet within %s but got none: %s", timeout, err) } @@ -93,11 +93,11 @@ func TestTCPOutsideTheWindow(t *testing.T) { // has passed since the last ACK was sent. t.Logf("sending another segment") conn.Send(t, testbench.TCP{ - Flags: testbench.Uint8(tt.tcpFlags), + Flags: testbench.TCPFlags(tt.tcpFlags), SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))), }, tt.payload...) timeout := 3 * time.Second - gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: localSeqNum}, timeout) + gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: localSeqNum}, timeout) if err == nil { t.Fatalf("expected no ACK packet but got one: %s", gotACK) } diff --git a/test/packetimpact/tests/tcp_paws_mechanism_test.go b/test/packetimpact/tests/tcp_paws_mechanism_test.go index 24d9ef4ec..9054955ea 100644 --- a/test/packetimpact/tests/tcp_paws_mechanism_test.go +++ b/test/packetimpact/tests/tcp_paws_mechanism_test.go @@ -38,8 +38,8 @@ func TestPAWSMechanism(t *testing.T) { options := make([]byte, header.TCPOptionTSLength) header.EncodeTSOption(currentTS(), 0, options) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn), Options: options}) - synAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn), Options: options}) + synAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("didn't get synack during handshake: %s", err) } @@ -49,7 +49,7 @@ func TestPAWSMechanism(t *testing.T) { } tsecr := parsedSynOpts.TSVal header.EncodeTSOption(currentTS(), tsecr, options) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), Options: options}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}) acceptFD, _ := dut.Accept(t, listenFD) defer dut.Close(t, acceptFD) @@ -60,9 +60,9 @@ func TestPAWSMechanism(t *testing.T) { // every time we send one, it should not cause any flakiness because timestamps // only need to be non-decreasing. time.Sleep(3 * time.Millisecond) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData}) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("expected an ACK but got none: %s", err) } @@ -85,9 +85,9 @@ func TestPAWSMechanism(t *testing.T) { // 3ms here is chosen arbitrarily and this time.Sleep() should not cause flakiness // due to the exact same reasoning discussed above. time.Sleep(3 * time.Millisecond) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), Options: options}, &testbench.Payload{Bytes: sampleData}) - gotTCP, err = conn.Expect(t, testbench.TCP{AckNum: lastAckNum, Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) + gotTCP, err = conn.Expect(t, testbench.TCP{AckNum: lastAckNum, Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("expected segment with AckNum %d but got none: %s", lastAckNum, err) } diff --git a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go index 7dd1c326a..1c8b72ebe 100644 --- a/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go +++ b/test/packetimpact/tests/tcp_queue_send_recv_in_syn_sent_test.go @@ -21,7 +21,6 @@ import ( "errors" "flag" "sync" - "syscall" "testing" "time" @@ -45,10 +44,10 @@ func TestQueueSendInSynSentHandshake(t *testing.T) { sampleData := []byte("Sample Data") dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) { + if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { t.Fatalf("expected a SYN from DUT, but got none: %s", err) } @@ -85,19 +84,19 @@ func TestQueueSendInSynSentHandshake(t *testing.T) { time.Sleep(100 * time.Millisecond) // Bring the connection to Established. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}) // Expect the data from the DUT's enqueued send request. // // On Linux, this can be piggybacked with the ACK completing the // handshake. On gVisor, getting such a piggyback is a bit more // complicated because the actual data enqueuing occurs in the // callers of endpoint Write. - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { t.Fatalf("expected an ACK from DUT, but got none: %s", err) } } @@ -113,15 +112,15 @@ func TestQueueRecvInSynSentHandshake(t *testing.T) { sampleData := []byte("Sample Data") dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) { + if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { t.Fatalf("expected a SYN from DUT, but got none: %s", err) } - if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != syscall.Errno(unix.EWOULDBLOCK) { - t.Fatalf("expected error %s, got %s", syscall.Errno(unix.EWOULDBLOCK), err) + if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != unix.EWOULDBLOCK { + t.Fatalf("expected error %s, got %s", unix.EWOULDBLOCK, err) } // Test blocking read. @@ -160,14 +159,14 @@ func TestQueueRecvInSynSentHandshake(t *testing.T) { time.Sleep(100 * time.Millisecond) // Bring the connection to Established. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}) + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { t.Fatalf("expected an ACK from DUT, but got none: %s", err) } // Send sample payload so that DUT can recv. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagPsh | header.TCPFlagAck)}, &testbench.Payload{Bytes: sampleData}) + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { t.Fatalf("expected an ACK from DUT, but got none: %s", err) } } @@ -183,10 +182,10 @@ func TestQueueSendInSynSentRST(t *testing.T) { sampleData := []byte("Sample Data") dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) { + if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { t.Fatalf("expected a SYN from DUT, but got none: %s", err) } @@ -207,8 +206,8 @@ func TestQueueSendInSynSentRST(t *testing.T) { // Issue SEND call in SYN-SENT, this should be queued for // process until the connection is established. n, err := dut.SendWithErrno(ctx, t, socket, sampleData, 0) - if err != syscall.Errno(unix.ECONNREFUSED) { - t.Errorf("expected error %s, got %s", syscall.Errno(unix.ECONNREFUSED), err) + if err != unix.ECONNREFUSED { + t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err) } if n != -1 { t.Errorf("expected return value %d, got %d", -1, n) @@ -224,7 +223,7 @@ func TestQueueSendInSynSentRST(t *testing.T) { // request and the system actually being blocked. time.Sleep(100 * time.Millisecond) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) } // TestQueueRecvInSynSentRST tests recv behavior when the TCP state @@ -238,15 +237,15 @@ func TestQueueRecvInSynSentRST(t *testing.T) { sampleData := []byte("Sample Data") dut.SetNonBlocking(t, socket, true) - if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, syscall.EINPROGRESS) { + if _, err := dut.ConnectWithErrno(context.Background(), t, socket, conn.LocalAddr(t)); !errors.Is(err, unix.EINPROGRESS) { t.Fatalf("failed to bring DUT to SYN-SENT, got: %s, want EINPROGRESS", err) } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, time.Second); err != nil { t.Fatalf("expected a SYN from DUT, but got none: %s", err) } - if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != syscall.Errno(unix.EWOULDBLOCK) { - t.Fatalf("expected error %s, got %s", syscall.Errno(unix.EWOULDBLOCK), err) + if _, _, err := dut.RecvWithErrno(context.Background(), t, socket, int32(len(sampleData)), 0); err != unix.EWOULDBLOCK { + t.Fatalf("expected error %s, got %s", unix.EWOULDBLOCK, err) } // Test blocking read. @@ -266,8 +265,8 @@ func TestQueueRecvInSynSentRST(t *testing.T) { // Issue RECEIVE call in SYN-SENT, this should be queued for // process until the connection is established. n, _, err := dut.RecvWithErrno(ctx, t, socket, int32(len(sampleData)), 0) - if err != syscall.Errno(unix.ECONNREFUSED) { - t.Errorf("expected error %s, got %s", syscall.Errno(unix.ECONNREFUSED), err) + if err != unix.ECONNREFUSED { + t.Errorf("expected error %s, got %s", unix.ECONNREFUSED, err) } if n != -1 { t.Errorf("expected return value %d, got %d", -1, n) @@ -283,5 +282,5 @@ func TestQueueRecvInSynSentRST(t *testing.T) { // request and the system actually being blocked. time.Sleep(100 * time.Millisecond) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) } diff --git a/test/packetimpact/tests/tcp_rack_test.go b/test/packetimpact/tests/tcp_rack_test.go index ef902c54d..0a5b0f12b 100644 --- a/test/packetimpact/tests/tcp_rack_test.go +++ b/test/packetimpact/tests/tcp_rack_test.go @@ -97,7 +97,7 @@ func sendAndReceive(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, num if sendACK { time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(sn))}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(sn))}) } } return lastSent @@ -149,7 +149,7 @@ func TestRACKTLPLost(t *testing.T) { // Cumulative ACK for #[1-5] packets. ackNum := seqNum1.Add(seqnum.Size(6 * payloadSize)) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(ackNum))}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(ackNum))}) // Probe Timeout (PTO) should be two times RTT. Check that the last // packet is retransmitted after probe timeout. @@ -194,7 +194,7 @@ func TestRACKWithSACK(t *testing.T) { sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ start, end, }}, sackBlock[sbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) rtt, _ := getRTTAndRTO(t, dut, acceptFd) timeout := 2 * rtt @@ -206,7 +206,7 @@ func TestRACKWithSACK(t *testing.T) { time.Sleep(simulatedRTT) // ACK for #1 packet. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(end))}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(end))}) // RACK considers transmission times of the packets to mark them lost. // As the 3rd packet was sent before the retransmitted 1st packet, RACK @@ -243,7 +243,7 @@ func TestRACKWithoutReorder(t *testing.T) { start, end, }}, sackBlock[sbOff:]) time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) // RACK marks #1 and #2 packets as lost and retransmits both after // RTT + reorderWindow. The reorderWindow initially will be a small @@ -289,7 +289,7 @@ func TestRACKWithReorder(t *testing.T) { sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ start, end, }}, sackBlock[sbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) } // Send a DSACK block indicating both original and retransmitted @@ -304,7 +304,7 @@ func TestRACKWithReorder(t *testing.T) { dbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ start, end, }}, dsackBlock[dbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1 + numPkts*payloadSize)), Options: dsackBlock[:dbOff]}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1 + numPkts*payloadSize)), Options: dsackBlock[:dbOff]}) seqNum1.UpdateForward(seqnum.Size(numPkts * payloadSize)) sendTime := time.Now() @@ -321,7 +321,7 @@ func TestRACKWithReorder(t *testing.T) { sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{ start, end, }}, sackBlock[sbOff:]) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) // Expect the retransmission of #1 packet after RTT+ReorderWindow. if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, time.Second); err != nil { @@ -361,7 +361,7 @@ func TestRACKWithLostRetransmission(t *testing.T) { start, end, }}, sackBlock[sbOff:]) time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]}) // RACK marks #1 packet as lost and retransmits it after // RTT + reorderWindow. The reorderWindow is bounded between a small @@ -394,7 +394,7 @@ func TestRACKWithLostRetransmission(t *testing.T) { start, end, }}, sackBlock1[sbOff1:]) time.Sleep(simulatedRTT) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock1[:sbOff1]}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock1[:sbOff1]}) // Expect re-retransmission of #1 packet without entering an RTO. if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil { diff --git a/test/packetimpact/tests/tcp_rcv_buf_space_test.go b/test/packetimpact/tests/tcp_rcv_buf_space_test.go index d6ad5cda6..f121d44eb 100644 --- a/test/packetimpact/tests/tcp_rcv_buf_space_test.go +++ b/test/packetimpact/tests/tcp_rcv_buf_space_test.go @@ -17,7 +17,6 @@ package tcp_rcv_buf_space_test import ( "context" "flag" - "syscall" "testing" "golang.org/x/sys/unix" @@ -61,7 +60,7 @@ func TestReduceRecvBuf(t *testing.T) { payloadBytes = l } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, []testbench.Layer{&testbench.Payload{Bytes: payload[:payloadBytes]}}...) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, []testbench.Layer{&testbench.Payload{Bytes: payload[:payloadBytes]}}...) payload = payload[payloadBytes:] } @@ -73,7 +72,7 @@ func TestReduceRecvBuf(t *testing.T) { // Second read should return EAGAIN as the last segment should have been // dropped due to it exceeding the receive buffer space available in the // socket. - if ret, got, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(len(sampleData)), syscall.MSG_DONTWAIT); got != nil || ret != -1 || err != syscall.EAGAIN { + if ret, got, err := dut.RecvWithErrno(context.Background(), t, acceptFd, int32(len(sampleData)), unix.MSG_DONTWAIT); got != nil || ret != -1 || err != unix.EAGAIN { t.Fatalf("expected no packets but got: %s", got) } } diff --git a/test/packetimpact/tests/tcp_retransmits_test.go b/test/packetimpact/tests/tcp_retransmits_test.go index ba79fbf55..3dc8f63ab 100644 --- a/test/packetimpact/tests/tcp_retransmits_test.go +++ b/test/packetimpact/tests/tcp_retransmits_test.go @@ -15,6 +15,7 @@ package tcp_retransmits_test import ( + "bytes" "flag" "testing" "time" @@ -59,38 +60,43 @@ func TestRetransmits(t *testing.T) { sampleData := []byte("Sample Data") samplePayload := &testbench.Payload{Bytes: sampleData} + // Give a chance for the dut to estimate RTO with RTT from the DATA-ACK. + // This is to reduce the test run-time from the default initial RTO of 1s. + // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which + // we can skip this data send/recv which is solely to estimate RTO. dut.Send(t, acceptFd, sampleData, 0) if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) } - // Give a chance for the dut to estimate RTO with RTT from the DATA-ACK. - // TODO(gvisor.dev/issue/2685) Estimate RTO during handshake, after which - // we can skip sending this ACK. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { + t.Fatalf("expected packet was not received: %s", err) + } + // Wait for the DUT to receive the data, thus ensuring that the stack has + // estimated RTO before we query RTO via TCP_INFO. + if got := dut.Recv(t, acceptFd, int32(len(sampleData)), 0); !bytes.Equal(got, sampleData) { + t.Fatalf("got dut.Recv(t, %d, %d, 0) = %s, want %s", acceptFd, len(sampleData), got, sampleData) + } const timeoutCorrection = time.Second - const diffCorrection = time.Millisecond + const diffCorrection = 200 * time.Millisecond rto := getRTO(t, dut, acceptFd) - timeout := rto + timeoutCorrection - startTime := time.Now() dut.Send(t, acceptFd, sampleData, 0) seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, timeout); err != nil { + if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, rto+timeoutCorrection); err != nil { t.Fatalf("expected payload was not received: %s", err) } // Expect retransmits of the same segment. for i := 0; i < 5; i++ { - if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, timeout); err != nil { - t.Fatalf("expected payload was not received within %d loop %d err %s", timeout, i, err) + startTime := time.Now() + rto = getRTO(t, dut, acceptFd) + if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, rto+timeoutCorrection); err != nil { + t.Fatalf("expected payload was not received within %s loop %d err %s", rto+timeoutCorrection, i, err) } - if diff := time.Since(startTime); diff+diffCorrection < rto { - t.Fatalf("retransmit came sooner got: %d want: >= %d probe %d", diff, rto, i) + t.Fatalf("retransmit came sooner got: %s want: >= %s probe %d", diff+diffCorrection, rto, i) } - startTime = time.Now() - rto = getRTO(t, dut, acceptFd) - timeout = rto + timeoutCorrection } } diff --git a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go index 418393796..64b7288fb 100644 --- a/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go +++ b/test/packetimpact/tests/tcp_send_window_sizes_piggyback_test.go @@ -71,7 +71,7 @@ func TestSendWindowSizesPiggyback(t *testing.T) { dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1) - expectedTCP := testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)} + expectedTCP := testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)} dut.Send(t, acceptFd, sampleData, 0) expectedPayload := testbench.Payload{Bytes: tt.expectedPayload1} @@ -90,7 +90,7 @@ func TestSendWindowSizesPiggyback(t *testing.T) { // Send ACK for the previous segment along with data for the dut to // receive and ACK back. Sending this ACK would make room for the dut // to transmit any enqueued segment. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh), WindowSize: testbench.Uint16(tt.windowSize)}, &testbench.Payload{Bytes: sampleData}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh), WindowSize: testbench.Uint16(tt.windowSize)}, &testbench.Payload{Bytes: sampleData}) // Expect the dut to piggyback the ACK for received data along with // the segment enqueued for transmit. diff --git a/test/packetimpact/tests/tcp_synrcvd_reset_test.go b/test/packetimpact/tests/tcp_synrcvd_reset_test.go index 32271d7b2..3346d43c4 100644 --- a/test/packetimpact/tests/tcp_synrcvd_reset_test.go +++ b/test/packetimpact/tests/tcp_synrcvd_reset_test.go @@ -37,11 +37,11 @@ func TestTCPSynRcvdReset(t *testing.T) { defer conn.Close(t) // Expect dut connection to have transitioned to SYN-RCVD state. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { t.Fatalf("expected SYN-ACK %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}) // Expect the connection to have transitioned SYN-RCVD to CLOSED. // @@ -49,8 +49,8 @@ func TestTCPSynRcvdReset(t *testing.T) { // CLOSED. We cannot use TCP_INFO to lookup the state as this is a passive // DUT connection. for i := 0; i < 5; i++ { - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { t.Logf("retransmit%d ACK as we did not get the expected RST, %s", i, err) continue } diff --git a/test/packetimpact/tests/tcp_synsent_reset_test.go b/test/packetimpact/tests/tcp_synsent_reset_test.go index 2c8bb101b..cccb0abc6 100644 --- a/test/packetimpact/tests/tcp_synsent_reset_test.go +++ b/test/packetimpact/tests/tcp_synsent_reset_test.go @@ -42,7 +42,7 @@ func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, uint16, copy(sa.Addr[:], dut.Net.LocalIPv4) // Bring the dut to SYN-SENT state with a non-blocking connect. dut.Connect(t, clientFD, &sa) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}, nil, time.Second); err != nil { + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}, nil, time.Second); err != nil { t.Fatalf("expected SYN\n") } @@ -53,11 +53,11 @@ func dutSynSentState(t *testing.T) (*testbench.DUT, *testbench.TCPIPv4, uint16, func TestTCPSynSentReset(t *testing.T) { _, conn, _, _ := dutSynSentState(t) defer conn.Close(t) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst | header.TCPFlagAck)}) // Expect the connection to have closed. // TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { t.Fatalf("expected a TCP RST") } } @@ -73,15 +73,15 @@ func TestTCPSynSentRcvdReset(t *testing.T) { // Initiate new SYN connection with the same port pair // (simultaneous open case), expect the dut connection to move to // SYN-RCVD state - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn)}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagSyn | header.TCPFlagAck)}, nil, time.Second); err != nil { t.Fatalf("expected SYN-ACK %s\n", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}) // Expect the connection to have transitioned SYN-RCVD to CLOSED. // TODO(gvisor.dev/issue/478): Check for TCP_INFO on the dut side. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { t.Fatalf("expected a TCP RST") } } diff --git a/test/packetimpact/tests/tcp_timewait_reset_test.go b/test/packetimpact/tests/tcp_timewait_reset_test.go index d1d2fb83d..89037f0a4 100644 --- a/test/packetimpact/tests/tcp_timewait_reset_test.go +++ b/test/packetimpact/tests/tcp_timewait_reset_test.go @@ -42,26 +42,26 @@ func TestTimeWaitReset(t *testing.T) { // Trigger active close. dut.Close(t, acceptFD) - _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) + _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("expected a FIN: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) // Send a FIN, DUT should transition to TIME_WAIT from FIN_WAIT2. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { t.Fatalf("expected an ACK for our FIN: %s", err) } // Send a RST, the DUT should transition to CLOSED from TIME_WAIT. // This is the default Linux behavior, it can be changed to ignore RSTs via // sysctl net.ipv4.tcp_rfc1337. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) // The DUT should reply with RST to our ACK as the state should have // transitioned to CLOSED. - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil { t.Fatalf("expected a RST: %s", err) } } diff --git a/test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go b/test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go new file mode 100644 index 000000000..a208210ac --- /dev/null +++ b/test/packetimpact/tests/tcp_unacc_seq_ack_closing_test.go @@ -0,0 +1,94 @@ +// Copyright 2021 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 tcp_unacc_seq_ack_closing_test + +import ( + "flag" + "fmt" + "testing" + "time" + + "golang.org/x/sys/unix" + "gvisor.dev/gvisor/pkg/tcpip/header" + "gvisor.dev/gvisor/pkg/tcpip/seqnum" + "gvisor.dev/gvisor/test/packetimpact/testbench" +) + +func init() { + testbench.Initialize(flag.CommandLine) +} + +func TestSimultaneousCloseUnaccSeqAck(t *testing.T) { + for _, tt := range []struct { + description string + makeTestingTCP func(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset, windowSize seqnum.Size) testbench.TCP + seqNumOffset seqnum.Size + expectAck bool + }{ + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: false}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: true}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: true}, + } { + t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { + dut := testbench.NewDUT(t) + listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1 /*backlog*/) + defer dut.Close(t, listenFD) + conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort}) + defer conn.Close(t) + + conn.Connect(t) + acceptFD, _ := dut.Accept(t, listenFD) + + // Trigger active close. + dut.Shutdown(t, acceptFD, unix.SHUT_WR) + + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) + if err != nil { + t.Fatalf("expected a FIN: %s", err) + } + // Do not ack the FIN from DUT so that we get to CLOSING. + seqNumForTheirFIN := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)) - 1) + conn.Send(t, testbench.TCP{AckNum: seqNumForTheirFIN, Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) + + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { + t.Errorf("expected an ACK to our FIN, but got none: %s", err) + } + + sampleData := []byte("Sample Data") + samplePayload := &testbench.Payload{Bytes: sampleData} + + origSeq := uint32(*conn.LocalSeqNum(t)) + // Send a segment with OTW Seq / unacc ACK. + tcp := tt.makeTestingTCP(t, &conn, tt.seqNumOffset, seqnum.Size(*gotTCP.WindowSize)) + if tt.description == "OTWSeq" { + // If we generate an OTW Seq segment, make sure we don't acknowledge their FIN so that + // we stay in CLOSING. + tcp.AckNum = seqNumForTheirFIN + } + conn.Send(t, tcp, samplePayload) + + got, err := conn.Expect(t, testbench.TCP{AckNum: testbench.Uint32(origSeq), Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) + if tt.expectAck && err != nil { + t.Errorf("expected an ack in CLOSING state, but got none: %s", err) + } + if !tt.expectAck && got != nil { + t.Errorf("expected no ack in CLOSING state, but got one: %s", got) + } + }) + } +} diff --git a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go index ea962c818..ce0a26171 100644 --- a/test/packetimpact/tests/tcp_unacc_seq_ack_test.go +++ b/test/packetimpact/tests/tcp_unacc_seq_ack_test.go @@ -17,7 +17,6 @@ package tcp_unacc_seq_ack_test import ( "flag" "fmt" - "syscall" "testing" "time" @@ -39,12 +38,12 @@ func TestEstablishedUnaccSeqAck(t *testing.T) { expectAck bool restoreSeq bool }{ - {description: "OTWSeq", makeTestingTCP: generateOTWSeqSegment, seqNumOffset: 0, expectAck: true, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: generateOTWSeqSegment, seqNumOffset: 1, expectAck: true, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: generateOTWSeqSegment, seqNumOffset: 2, expectAck: true, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: generateUnaccACKSegment, seqNumOffset: 0, expectAck: true, restoreSeq: false}, - {description: "UnaccAck", makeTestingTCP: generateUnaccACKSegment, seqNumOffset: 1, expectAck: false, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: generateUnaccACKSegment, seqNumOffset: 2, expectAck: false, restoreSeq: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: true, restoreSeq: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true, restoreSeq: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true, restoreSeq: true}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: true, restoreSeq: false}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: false, restoreSeq: true}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: false, restoreSeq: true}, } { t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { dut := testbench.NewDUT(t) @@ -59,8 +58,8 @@ func TestEstablishedUnaccSeqAck(t *testing.T) { sampleData := []byte("Sample Data") samplePayload := &testbench.Payload{Bytes: sampleData} - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("expected ack %s", err) } @@ -74,7 +73,7 @@ func TestEstablishedUnaccSeqAck(t *testing.T) { // ACK matches the TCP layer state. *conn.LocalSeqNum(t) = origSeq } - gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) + gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) if tt.expectAck && err != nil { t.Fatalf("expected an ack but got none: %s", err) } @@ -92,12 +91,12 @@ func TestPassiveCloseUnaccSeqAck(t *testing.T) { seqNumOffset seqnum.Size expectAck bool }{ - {description: "OTWSeq", makeTestingTCP: generateOTWSeqSegment, seqNumOffset: 0, expectAck: false}, - {description: "OTWSeq", makeTestingTCP: generateOTWSeqSegment, seqNumOffset: 1, expectAck: true}, - {description: "OTWSeq", makeTestingTCP: generateOTWSeqSegment, seqNumOffset: 2, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: generateUnaccACKSegment, seqNumOffset: 0, expectAck: false}, - {description: "UnaccAck", makeTestingTCP: generateUnaccACKSegment, seqNumOffset: 1, expectAck: true}, - {description: "UnaccAck", makeTestingTCP: generateUnaccACKSegment, seqNumOffset: 2, expectAck: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, expectAck: false}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, expectAck: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, expectAck: true}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, expectAck: false}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, expectAck: true}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, expectAck: true}, } { t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { dut := testbench.NewDUT(t) @@ -110,8 +109,8 @@ func TestPassiveCloseUnaccSeqAck(t *testing.T) { acceptFD, _ := dut.Accept(t, listenFD) // Send a FIN to DUT to intiate the passive close. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagFin)}) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagFin)}) + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("expected an ACK for our fin and DUT should enter CLOSE_WAIT: %s", err) } @@ -122,7 +121,7 @@ func TestPassiveCloseUnaccSeqAck(t *testing.T) { // Send a segment with OTW Seq / unacc ACK. conn.Send(t, tt.makeTestingTCP(t, &conn, tt.seqNumOffset, windowSize), samplePayload) - gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) + gotAck, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) if tt.expectAck && err != nil { t.Errorf("expected an ack but got none: %s", err) } @@ -132,14 +131,14 @@ func TestPassiveCloseUnaccSeqAck(t *testing.T) { // Now let's verify DUT is indeed in CLOSE_WAIT dut.Close(t, acceptFD) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagFin)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagFin)}, time.Second); err != nil { t.Fatalf("expected DUT to send a FIN: %s", err) } // Ack the FIN from DUT - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) // Send some extra data to DUT - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, samplePayload) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, samplePayload) + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, time.Second); err != nil { t.Fatalf("expected DUT to send an RST: %s", err) } }) @@ -153,12 +152,12 @@ func TestActiveCloseUnaccpSeqAck(t *testing.T) { seqNumOffset seqnum.Size restoreSeq bool }{ - {description: "OTWSeq", makeTestingTCP: generateOTWSeqSegment, seqNumOffset: 0, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: generateOTWSeqSegment, seqNumOffset: 1, restoreSeq: true}, - {description: "OTWSeq", makeTestingTCP: generateOTWSeqSegment, seqNumOffset: 2, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: generateUnaccACKSegment, seqNumOffset: 0, restoreSeq: false}, - {description: "UnaccAck", makeTestingTCP: generateUnaccACKSegment, seqNumOffset: 1, restoreSeq: true}, - {description: "UnaccAck", makeTestingTCP: generateUnaccACKSegment, seqNumOffset: 2, restoreSeq: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 0, restoreSeq: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 1, restoreSeq: true}, + {description: "OTWSeq", makeTestingTCP: testbench.GenerateOTWSeqSegment, seqNumOffset: 2, restoreSeq: true}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 0, restoreSeq: false}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 1, restoreSeq: true}, + {description: "UnaccAck", makeTestingTCP: testbench.GenerateUnaccACKSegment, seqNumOffset: 2, restoreSeq: true}, } { t.Run(fmt.Sprintf("%s:offset=%d", tt.description, tt.seqNumOffset), func(t *testing.T) { dut := testbench.NewDUT(t) @@ -171,14 +170,14 @@ func TestActiveCloseUnaccpSeqAck(t *testing.T) { acceptFD, _ := dut.Accept(t, listenFD) // Trigger active close. - dut.Shutdown(t, acceptFD, syscall.SHUT_WR) + dut.Shutdown(t, acceptFD, unix.SHUT_WR) // Get to FIN_WAIT2 - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("expected a FIN: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) sendUnaccSeqAck := func(state string) { t.Helper() @@ -193,7 +192,7 @@ func TestActiveCloseUnaccpSeqAck(t *testing.T) { // incoming ACK matches the TCP layer state. *conn.LocalSeqNum(t) = origSeq } - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { t.Errorf("expected an ack in %s state, but got none: %s", state, err) } } @@ -201,8 +200,8 @@ func TestActiveCloseUnaccpSeqAck(t *testing.T) { sendUnaccSeqAck("FIN_WAIT2") // Send a FIN to DUT to get to TIME_WAIT - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagFin | header.TCPFlagAck)}) - if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagFin | header.TCPFlagAck)}) + if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second); err != nil { t.Fatalf("expected an ACK for our fin and DUT should enter TIME_WAIT: %s", err) } @@ -210,22 +209,3 @@ func TestActiveCloseUnaccpSeqAck(t *testing.T) { }) } } - -// generateOTWSeqSegment generates an segment with -// seqnum = RCV.NXT + RCV.WND + seqNumOffset, the generated segment is only -// acceptable when seqNumOffset is 0, otherwise an ACK is expected from the -// receiver. -func generateOTWSeqSegment(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) testbench.TCP { - lastAcceptable := conn.LocalSeqNum(t).Add(windowSize) - otwSeq := uint32(lastAcceptable.Add(seqNumOffset)) - return testbench.TCP{SeqNum: testbench.Uint32(otwSeq), Flags: testbench.Uint8(header.TCPFlagAck)} -} - -// generateUnaccACKSegment generates an segment with -// acknum = SND.NXT + seqNumOffset, the generated segment is only acceptable -// when seqNumOffset is 0, otherwise an ACK is expected from the receiver. -func generateUnaccACKSegment(t *testing.T, conn *testbench.TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) testbench.TCP { - lastAcceptable := conn.RemoteSeqNum(t) - unaccAck := uint32(lastAcceptable.Add(seqNumOffset)) - return testbench.TCP{AckNum: testbench.Uint32(unaccAck), Flags: testbench.Uint8(header.TCPFlagAck)} -} diff --git a/test/packetimpact/tests/tcp_user_timeout_test.go b/test/packetimpact/tests/tcp_user_timeout_test.go index b16e65366..ef38bd738 100644 --- a/test/packetimpact/tests/tcp_user_timeout_test.go +++ b/test/packetimpact/tests/tcp_user_timeout_test.go @@ -35,7 +35,7 @@ func sendPayload(t *testing.T, conn *testbench.TCPIPv4, dut *testbench.DUT, fd i } conn.Drain(t) dut.Send(t, fd, sampleData, 0) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, &testbench.Payload{Bytes: sampleData}, time.Second); err != nil { t.Fatalf("expected data but got none: %w", err) } } @@ -79,14 +79,14 @@ func TestTCPUserTimeout(t *testing.T) { time.Sleep(tt.sendDelay) conn.Drain(t) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) // If TCP_USER_TIMEOUT was set and the above delay was longer than the // TCP_USER_TIMEOUT then the DUT should send a RST in response to the // testbench's packet. expectRST := tt.userTimeout != 0 && tt.sendDelay > tt.userTimeout expectTimeout := 5 * time.Second - got, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, expectTimeout) + got, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, expectTimeout) if expectRST && err != nil { t.Errorf("expected RST packet within %s but got none: %s", expectTimeout, err) } diff --git a/test/packetimpact/tests/tcp_window_shrink_test.go b/test/packetimpact/tests/tcp_window_shrink_test.go index 093484721..0d65a2ea2 100644 --- a/test/packetimpact/tests/tcp_window_shrink_test.go +++ b/test/packetimpact/tests/tcp_window_shrink_test.go @@ -48,7 +48,7 @@ func TestWindowShrink(t *testing.T) { if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) dut.Send(t, acceptFd, sampleData, 0) dut.Send(t, acceptFd, sampleData, 0) @@ -59,7 +59,7 @@ func TestWindowShrink(t *testing.T) { t.Fatalf("expected payload was not received: %s", err) } // We close our receiving window here - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) dut.Send(t, acceptFd, []byte("Sample Data"), 0) // Note: There is another kind of zero-window probing which Windows uses (by sending one diff --git a/test/packetimpact/tests/tcp_zero_receive_window_test.go b/test/packetimpact/tests/tcp_zero_receive_window_test.go index d06690705..d73495454 100644 --- a/test/packetimpact/tests/tcp_zero_receive_window_test.go +++ b/test/packetimpact/tests/tcp_zero_receive_window_test.go @@ -49,8 +49,8 @@ func TestZeroReceiveWindow(t *testing.T) { // Expect the DUT to eventually advertise zero receive window. // The test would timeout otherwise. for readOnce := false; ; { - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("expected packet was not received: %s", err) } @@ -100,8 +100,8 @@ func TestNonZeroReceiveWindow(t *testing.T) { // we sent. Once we have received ACKs with non-zero receive windows, we break // the loop. for { - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, time.Second) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + gotTCP, err := conn.Expect(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, time.Second) if err != nil { t.Fatalf("expected packet was not received: %s", err) } diff --git a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go index d094c10eb..22b17a39e 100644 --- a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go +++ b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go @@ -15,6 +15,7 @@ package tcp_zero_window_probe_retransmit_test import ( + "bytes" "flag" "testing" "time" @@ -51,18 +52,23 @@ func TestZeroWindowProbeRetransmit(t *testing.T) { if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil { - t.Fatalf("expected packet was not received: %s", err) - } // Check for the dut to keep the connection alive as long as the zero window // probes are acknowledged. Check if the zero window probes are sent at // exponentially increasing intervals. The timeout intervals are function // of the recorded first zero probe transmission duration. // - // Advertize zero receive window again. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) + // Advertize zero receive window along with a payload. + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh), WindowSize: testbench.Uint16(0)}, samplePayload) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { + t.Fatalf("expected packet was not received: %s", err) + } + // Wait for the payload to be received by the DUT, which is also an + // indication of receive of the peer window advertisement. + if got := dut.Recv(t, acceptFd, int32(len(sampleData)), 0); !bytes.Equal(got, sampleData) { + t.Fatalf("got dut.Recv(t, %d, %d, 0) = %s, want %s", acceptFd, len(sampleData), got, sampleData) + } + probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)) ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t))) @@ -98,10 +104,10 @@ func TestZeroWindowProbeRetransmit(t *testing.T) { } prev = got // Acknowledge the zero-window probes from the dut. - conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) + conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) } // Advertize non-zero window. - conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck)}) // Expect the dut to recover and transmit data. if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) diff --git a/test/packetimpact/tests/tcp_zero_window_probe_test.go b/test/packetimpact/tests/tcp_zero_window_probe_test.go index 650a569cc..8b90fcbe9 100644 --- a/test/packetimpact/tests/tcp_zero_window_probe_test.go +++ b/test/packetimpact/tests/tcp_zero_window_probe_test.go @@ -53,8 +53,8 @@ func TestZeroWindowProbe(t *testing.T) { t.Fatalf("expected payload was not received: %s", err) } sendTime := time.Now().Sub(start) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { t.Fatalf("expected packet was not received: %s", err) } @@ -62,7 +62,7 @@ func TestZeroWindowProbe(t *testing.T) { // probe to be sent. // // Advertize zero window to the dut. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) // Expected sequence number of the zero window probe. probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)) @@ -93,7 +93,7 @@ func TestZeroWindowProbe(t *testing.T) { // and sends out the sample payload after the send window opens. // // Advertize non-zero window to the dut and ack the zero window probe. - conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.Uint8(header.TCPFlagAck)}) + conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.TCPFlags(header.TCPFlagAck)}) // Expect the dut to recover and transmit data. if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) @@ -104,8 +104,8 @@ func TestZeroWindowProbe(t *testing.T) { // Basically with sequence number to one byte behind the unacknowledged // sequence number. p := testbench.Uint32(uint32(*conn.LocalSeqNum(t))) - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), SeqNum: testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1))}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: p}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), SeqNum: testbench.Uint32(uint32(*conn.LocalSeqNum(t) - 1))}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), AckNum: p}, nil, time.Second); err != nil { t.Fatalf("expected a packet with ack number: %d: %s", p, err) } } diff --git a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go index 079fea68c..1ce4d22b7 100644 --- a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go +++ b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go @@ -51,8 +51,8 @@ func TestZeroWindowProbeUserTimeout(t *testing.T) { if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil { t.Fatalf("expected payload was not received: %s", err) } - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}, nil, time.Second); err != nil { t.Fatalf("expected packet was not received: %s", err) } @@ -60,7 +60,7 @@ func TestZeroWindowProbeUserTimeout(t *testing.T) { // probe to be sent. // // Advertize zero window to the dut. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) // Expected sequence number of the zero window probe. probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1)) @@ -81,7 +81,7 @@ func TestZeroWindowProbeUserTimeout(t *testing.T) { // Reduce the retransmit timeout. dut.SetSockOptInt(t, acceptFd, unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int32(startProbeDuration.Milliseconds())) // Advertize zero window again. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck), WindowSize: testbench.Uint16(0)}) // Ask the dut to send out data that would trigger zero window probe retransmissions. dut.Send(t, acceptFd, sampleData, 0) @@ -90,8 +90,8 @@ func TestZeroWindowProbeUserTimeout(t *testing.T) { // Expect the connection to have timed out and closed which would cause the dut // to reply with a RST to the ACK we send. - conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)}) - if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil { + conn.Send(t, testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagAck)}) + if _, err := conn.ExpectData(t, &testbench.TCP{Flags: testbench.TCPFlags(header.TCPFlagRst)}, nil, time.Second); err != nil { t.Fatalf("expected a TCP RST") } } diff --git a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go index 52c6f9d91..f63cfcc9a 100644 --- a/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go +++ b/test/packetimpact/tests/udp_discard_mcast_source_addr_test.go @@ -19,7 +19,6 @@ import ( "flag" "fmt" "net" - "syscall" "testing" "golang.org/x/sys/unix" @@ -56,7 +55,7 @@ func TestDiscardsUDPPacketsWithMcastSourceAddressV4(t *testing.T) { ) ret, payload, errno := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0) - if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK { + if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno) } }) @@ -86,7 +85,7 @@ func TestDiscardsUDPPacketsWithMcastSourceAddressV6(t *testing.T) { &testbench.Payload{Bytes: []byte("test payload")}, ) ret, payload, errno := dut.RecvWithErrno(context.Background(), t, remoteFD, 100, 0) - if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK { + if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno) } }) diff --git a/test/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go index 3fca8c7a3..3159d5b89 100644 --- a/test/packetimpact/tests/udp_icmp_error_propagation_test.go +++ b/test/packetimpact/tests/udp_icmp_error_propagation_test.go @@ -20,7 +20,6 @@ import ( "fmt" "net" "sync" - "syscall" "testing" "time" @@ -86,16 +85,16 @@ type testData struct { remotePort uint16 cleanFD int32 cleanPort uint16 - wantErrno syscall.Errno + wantErrno unix.Errno } // wantErrno computes the errno to expect given the connection mode of a UDP // socket and the ICMP error it will receive. -func wantErrno(c connectionMode, icmpErr icmpError) syscall.Errno { +func wantErrno(c connectionMode, icmpErr icmpError) unix.Errno { if c && icmpErr == portUnreachable { - return syscall.Errno(unix.ECONNREFUSED) + return unix.ECONNREFUSED } - return syscall.Errno(0) + return unix.Errno(0) } // sendICMPError sends an ICMP error message in response to a UDP datagram. @@ -123,7 +122,7 @@ func sendICMPError(t *testing.T, conn *testbench.UDPIPv4, icmpErr icmpError, udp conn.SendFrameStateless(t, layers) } -// testRecv tests observing the ICMP error through the recv syscall. A packet +// testRecv tests observing the ICMP error through the recv unix. A packet // is sent to the DUT, and if wantErrno is non-zero, then the first recv should // fail and the second should succeed. Otherwise if wantErrno is zero then the // first recv should succeed immediately. @@ -136,7 +135,7 @@ func testRecv(ctx context.Context, t *testing.T, d testData) { d.conn.Send(t, testbench.UDP{}) - if d.wantErrno != syscall.Errno(0) { + if d.wantErrno != unix.Errno(0) { ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() ret, _, err := d.dut.RecvWithErrno(ctx, t, d.remoteFD, 100, 0) @@ -162,7 +161,7 @@ func testSendTo(ctx context.Context, t *testing.T, d testData) { t.Fatalf("did not receive UDP packet from clean socket on DUT: %s", err) } - if d.wantErrno != syscall.Errno(0) { + if d.wantErrno != unix.Errno(0) { ctx, cancel := context.WithTimeout(ctx, time.Second) defer cancel() ret, err := d.dut.SendToWithErrno(ctx, t, d.remoteFD, nil, 0, d.conn.LocalAddr(t)) @@ -183,11 +182,11 @@ func testSendTo(ctx context.Context, t *testing.T, d testData) { func testSockOpt(_ context.Context, t *testing.T, d testData) { // Check that there's no pending error on the clean socket. - if errno := syscall.Errno(d.dut.GetSockOptInt(t, d.cleanFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != syscall.Errno(0) { + if errno := unix.Errno(d.dut.GetSockOptInt(t, d.cleanFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != unix.Errno(0) { t.Fatalf("unexpected error (%[1]d) %[1]v on clean socket", errno) } - if errno := syscall.Errno(d.dut.GetSockOptInt(t, d.remoteFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != d.wantErrno { + if errno := unix.Errno(d.dut.GetSockOptInt(t, d.remoteFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != d.wantErrno { t.Fatalf("SO_ERROR sockopt after ICMP error is (%[1]d) %[1]v, expected (%[2]d) %[2]v", errno, d.wantErrno) } @@ -310,7 +309,7 @@ func TestICMPErrorDuringUDPRecv(t *testing.T) { go func() { defer wg.Done() - if wantErrno != syscall.Errno(0) { + if wantErrno != unix.Errno(0) { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() diff --git a/test/packetimpact/tests/udp_send_recv_dgram_test.go b/test/packetimpact/tests/udp_send_recv_dgram_test.go index 894d156cf..230b012c7 100644 --- a/test/packetimpact/tests/udp_send_recv_dgram_test.go +++ b/test/packetimpact/tests/udp_send_recv_dgram_test.go @@ -19,7 +19,6 @@ import ( "flag" "fmt" "net" - "syscall" "testing" "time" @@ -241,7 +240,7 @@ func TestUDP(t *testing.T) { }, ) ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, socketFD, 100, 0) - if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK { + if errno != unix.EAGAIN || errno != unix.EWOULDBLOCK { t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno) } } diff --git a/test/perf/BUILD b/test/perf/BUILD index e25f090ae..ed899ac22 100644 --- a/test/perf/BUILD +++ b/test/perf/BUILD @@ -1,4 +1,3 @@ -load("//tools:defs.bzl", "more_shards") load("//test/runner:defs.bzl", "syscall_test") package(licenses = ["notice"]) @@ -38,7 +37,6 @@ syscall_test( syscall_test( size = "enormous", debug = False, - shard_count = more_shards, tags = ["nogotsan"], test = "//test/perf/linux:getdents_benchmark", ) diff --git a/test/runner/gtest/gtest.go b/test/runner/gtest/gtest.go index 38e57d62f..2ad5f58ef 100644 --- a/test/runner/gtest/gtest.go +++ b/test/runner/gtest/gtest.go @@ -35,6 +35,39 @@ var ( filterBenchmarkFlag = "--benchmark_filter" ) +// BuildTestArgs builds arguments to be passed to the test binary to execute +// only the test cases in `indices`. +func BuildTestArgs(indices []int, testCases []TestCase) []string { + var testFilter, benchFilter string + for _, tci := range indices { + tc := testCases[tci] + if tc.all { + // No argument will make all tests run. + return nil + } + if tc.benchmark { + if len(benchFilter) > 0 { + benchFilter += "|" + } + benchFilter += "^" + tc.Name + "$" + } else { + if len(testFilter) > 0 { + testFilter += ":" + } + testFilter += tc.FullName() + } + } + + var args []string + if len(testFilter) > 0 { + args = append(args, fmt.Sprintf("%s=%s", filterTestFlag, testFilter)) + } + if len(benchFilter) > 0 { + args = append(args, fmt.Sprintf("%s=%s", filterBenchmarkFlag, benchFilter)) + } + return args +} + // TestCase is a single gtest test case. type TestCase struct { // Suite is the suite for this test. @@ -59,22 +92,6 @@ func (tc TestCase) FullName() string { return fmt.Sprintf("%s.%s", tc.Suite, tc.Name) } -// Args returns arguments to be passed when invoking the test. -func (tc TestCase) Args() []string { - if tc.all { - return []string{} // No arguments. - } - if tc.benchmark { - return []string{ - fmt.Sprintf("%s=^%s$", filterBenchmarkFlag, tc.Name), - fmt.Sprintf("%s=", filterTestFlag), - } - } - return []string{ - fmt.Sprintf("%s=%s", filterTestFlag, tc.FullName()), - } -} - // ParseTestCases calls a gtest test binary to list its test and returns a // slice with the name and suite of each test. // @@ -90,6 +107,7 @@ func ParseTestCases(testBin string, benchmarks bool, extraArgs ...string) ([]Tes // We failed to list tests with the given flags. Just // return something that will run the binary with no // flags, which should execute all tests. + fmt.Printf("failed to get test list: %v\n", err) return []TestCase{ { Suite: "Default", diff --git a/test/runner/runner.go b/test/runner/runner.go index e72c59200..a8a134fe2 100644 --- a/test/runner/runner.go +++ b/test/runner/runner.go @@ -26,7 +26,6 @@ import ( "path/filepath" "strings" "syscall" - "testing" "time" specs "github.com/opencontainers/runtime-spec/specs-go" @@ -57,13 +56,82 @@ var ( leakCheck = flag.Bool("leak-check", false, "check for reference leaks") ) +func main() { + flag.Parse() + if flag.NArg() != 1 { + fatalf("test must be provided") + } + + log.SetLevel(log.Info) + if *debug { + log.SetLevel(log.Debug) + } + + if *platform != "native" && *runscPath == "" { + if err := testutil.ConfigureExePath(); err != nil { + panic(err.Error()) + } + *runscPath = specutils.ExePath + } + + // Make sure stdout and stderr are opened with O_APPEND, otherwise logs + // from outside the sandbox can (and will) stomp on logs from inside + // the sandbox. + for _, f := range []*os.File{os.Stdout, os.Stderr} { + flags, err := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0) + if err != nil { + fatalf("error getting file flags for %v: %v", f, err) + } + if flags&unix.O_APPEND == 0 { + flags |= unix.O_APPEND + if _, err := unix.FcntlInt(f.Fd(), unix.F_SETFL, flags); err != nil { + fatalf("error setting file flags for %v: %v", f, err) + } + } + } + + // Resolve the absolute path for the binary. + testBin, err := filepath.Abs(flag.Args()[0]) + if err != nil { + fatalf("Abs(%q) failed: %v", flag.Args()[0], err) + } + + // Get all test cases in each binary. + testCases, err := gtest.ParseTestCases(testBin, true) + if err != nil { + fatalf("ParseTestCases(%q) failed: %v", testBin, err) + } + + // Get subset of tests corresponding to shard. + indices, err := testutil.TestIndicesForShard(len(testCases)) + if err != nil { + fatalf("TestsForShard() failed: %v", err) + } + if len(indices) == 0 { + log.Warningf("No tests to run in this shard") + return + } + args := gtest.BuildTestArgs(indices, testCases) + + switch *platform { + case "native": + if err := runTestCaseNative(testBin, args); err != nil { + fatalf(err.Error()) + } + default: + if err := runTestCaseRunsc(testBin, args); err != nil { + fatalf(err.Error()) + } + } +} + // runTestCaseNative runs the test case directly on the host machine. -func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) { +func runTestCaseNative(testBin string, args []string) error { // These tests might be running in parallel, so make sure they have a // unique test temp dir. tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "") if err != nil { - t.Fatalf("could not create temp dir: %v", err) + return fmt.Errorf("could not create temp dir: %v", err) } defer os.RemoveAll(tmpDir) @@ -84,12 +152,12 @@ func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) { } // Remove shard env variables so that the gunit binary does not try to // interpret them. - env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"}) + env = filterEnv(env, "TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS") if *addUDSTree { socketDir, cleanup, err := uds.CreateSocketTree("/tmp") if err != nil { - t.Fatalf("failed to create socket tree: %v", err) + return fmt.Errorf("failed to create socket tree: %v", err) } defer cleanup() @@ -99,24 +167,25 @@ func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) { env = append(env, "TEST_UDS_ATTACH_TREE="+socketDir) } - cmd := exec.Command(testBin, tc.Args()...) + cmd := exec.Command(testBin, args...) cmd.Env = env cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr - cmd.SysProcAttr = &syscall.SysProcAttr{} + cmd.SysProcAttr = &unix.SysProcAttr{} if specutils.HasCapabilities(capability.CAP_SYS_ADMIN) { - cmd.SysProcAttr.Cloneflags |= syscall.CLONE_NEWUTS + cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWUTS } if specutils.HasCapabilities(capability.CAP_NET_ADMIN) { - cmd.SysProcAttr.Cloneflags |= syscall.CLONE_NEWNET + cmd.SysProcAttr.Cloneflags |= unix.CLONE_NEWNET } if err := cmd.Run(); err != nil { ws := err.(*exec.ExitError).Sys().(syscall.WaitStatus) - t.Errorf("test %q exited with status %d, want 0", tc.FullName(), ws.ExitStatus()) + return fmt.Errorf("test exited with status %d, want 0", ws.ExitStatus()) } + return nil } // runRunsc runs spec in runsc in a standard test configuration. @@ -124,7 +193,7 @@ func runTestCaseNative(testBin string, tc gtest.TestCase, t *testing.T) { // runsc logs will be saved to a path in TEST_UNDECLARED_OUTPUTS_DIR. // // Returns an error if the sandboxed application exits non-zero. -func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { +func runRunsc(spec *specs.Spec) error { bundleDir, cleanup, err := testutil.SetupBundleDir(spec) if err != nil { return fmt.Errorf("SetupBundleDir failed: %v", err) @@ -137,9 +206,8 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { } defer cleanup() - name := tc.FullName() id := testutil.RandomContainerID() - log.Infof("Running test %q in container %q", name, id) + log.Infof("Running test in container %q", id) specutils.LogSpec(spec) args := []string{ @@ -148,7 +216,7 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { "-log-format=text", "-TESTONLY-unsafe-nonroot=true", "-net-raw=true", - fmt.Sprintf("-panic-signal=%d", syscall.SIGTERM), + fmt.Sprintf("-panic-signal=%d", unix.SIGTERM), "-watchdog-action=panic", "-platform", *platform, "-file-access", *fileAccess, @@ -175,13 +243,8 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { args = append(args, "-ref-leak-mode=log-names") } - testLogDir := "" - if undeclaredOutputsDir, ok := syscall.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); ok { - // Create log directory dedicated for this test. - testLogDir = filepath.Join(undeclaredOutputsDir, strings.Replace(name, "/", "_", -1)) - if err := os.MkdirAll(testLogDir, 0755); err != nil { - return fmt.Errorf("could not create test dir: %v", err) - } + testLogDir := os.Getenv("TEST_UNDECLARED_OUTPUTS_DIR") + if len(testLogDir) > 0 { debugLogDir, err := ioutil.TempDir(testLogDir, "runsc") if err != nil { return fmt.Errorf("could not create temp dir: %v", err) @@ -200,8 +263,8 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { // as root inside that namespace to get it. rArgs := append(args, "run", "--bundle", bundleDir, id) cmd := exec.Command(*runscPath, rArgs...) - cmd.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS, + cmd.SysProcAttr = &unix.SysProcAttr{ + Cloneflags: unix.CLONE_NEWUSER | unix.CLONE_NEWNS, // Set current user/group as root inside the namespace. UidMappings: []syscall.SysProcIDMap{ {ContainerID: 0, HostID: os.Getuid(), Size: 1}, @@ -219,14 +282,14 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { cmd.Stderr = os.Stderr sig := make(chan os.Signal, 1) defer close(sig) - signal.Notify(sig, syscall.SIGTERM) + signal.Notify(sig, unix.SIGTERM) defer signal.Stop(sig) go func() { s, ok := <-sig if !ok { return } - log.Warningf("%s: Got signal: %v", name, s) + log.Warningf("Got signal: %v", s) done := make(chan bool, 1) dArgs := append([]string{}, args...) dArgs = append(dArgs, "-alsologtostderr=true", "debug", "--stacks", id) @@ -247,7 +310,7 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { log.Warningf("Send SIGTERM to the sandbox process") dArgs = append(args, "debug", - fmt.Sprintf("--signal=%d", syscall.SIGTERM), + fmt.Sprintf("--signal=%d", unix.SIGTERM), id) signal := exec.Command(*runscPath, dArgs...) signal.Stdout = os.Stdout @@ -259,7 +322,7 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error { if err == nil && len(testLogDir) > 0 { // If the test passed, then we erase the log directory. This speeds up // uploading logs in continuous integration & saves on disk space. - os.RemoveAll(testLogDir) + _ = os.RemoveAll(testLogDir) } return err @@ -314,10 +377,10 @@ func setupUDSTree(spec *specs.Spec) (cleanup func(), err error) { } // runsTestCaseRunsc runs the test case in runsc. -func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) { +func runTestCaseRunsc(testBin string, args []string) error { // Run a new container with the test executable and filter for the // given test suite and name. - spec := testutil.NewSpecWithArgs(append([]string{testBin}, tc.Args()...)...) + spec := testutil.NewSpecWithArgs(append([]string{testBin}, args...)...) // Mark the root as writeable, as some tests attempt to // write to the rootfs, and expect EACCES, not EROFS. @@ -343,12 +406,12 @@ func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) { // users, so make sure it is world-accessible. tmpDir, err := ioutil.TempDir(testutil.TmpDir(), "") if err != nil { - t.Fatalf("could not create temp dir: %v", err) + return fmt.Errorf("could not create temp dir: %v", err) } defer os.RemoveAll(tmpDir) if err := os.Chmod(tmpDir, 0777); err != nil { - t.Fatalf("could not chmod temp dir: %v", err) + return fmt.Errorf("could not chmod temp dir: %v", err) } // "/tmp" is not replaced with a tmpfs mount inside the sandbox @@ -368,13 +431,12 @@ func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) { // Set environment variables that indicate we are running in gVisor with // the given platform, network, and filesystem stack. - platformVar := "TEST_ON_GVISOR" - networkVar := "GVISOR_NETWORK" - env := append(os.Environ(), platformVar+"="+*platform, networkVar+"="+*network) - vfsVar := "GVISOR_VFS" + env := []string{"TEST_ON_GVISOR=" + *platform, "GVISOR_NETWORK=" + *network} + env = append(env, os.Environ()...) + const vfsVar = "GVISOR_VFS" if *vfs2 { env = append(env, vfsVar+"=VFS2") - fuseVar := "FUSE_ENABLED" + const fuseVar = "FUSE_ENABLED" if *fuse { env = append(env, fuseVar+"=TRUE") } else { @@ -386,11 +448,11 @@ func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) { // Remove shard env variables so that the gunit binary does not try to // interpret them. - env = filterEnv(env, []string{"TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS"}) + env = filterEnv(env, "TEST_SHARD_INDEX", "TEST_TOTAL_SHARDS", "GTEST_SHARD_INDEX", "GTEST_TOTAL_SHARDS") // Set TEST_TMPDIR to /tmp, as some of the syscall tests require it to // be backed by tmpfs. - env = filterEnv(env, []string{"TEST_TMPDIR"}) + env = filterEnv(env, "TEST_TMPDIR") env = append(env, fmt.Sprintf("TEST_TMPDIR=%s", testTmpDir)) spec.Process.Env = env @@ -398,18 +460,19 @@ func runTestCaseRunsc(testBin string, tc gtest.TestCase, t *testing.T) { if *addUDSTree { cleanup, err := setupUDSTree(spec) if err != nil { - t.Fatalf("error creating UDS tree: %v", err) + return fmt.Errorf("error creating UDS tree: %v", err) } defer cleanup() } - if err := runRunsc(tc, spec); err != nil { - t.Errorf("test %q failed with error %v, want nil", tc.FullName(), err) + if err := runRunsc(spec); err != nil { + return fmt.Errorf("test failed with error %v, want nil", err) } + return nil } // filterEnv returns an environment with the excluded variables removed. -func filterEnv(env, exclude []string) []string { +func filterEnv(env []string, exclude ...string) []string { var out []string for _, kv := range env { ok := true @@ -430,82 +493,3 @@ func fatalf(s string, args ...interface{}) { fmt.Fprintf(os.Stderr, s+"\n", args...) os.Exit(1) } - -func matchString(a, b string) (bool, error) { - return a == b, nil -} - -func main() { - flag.Parse() - if flag.NArg() != 1 { - fatalf("test must be provided") - } - testBin := flag.Args()[0] // Only argument. - - log.SetLevel(log.Info) - if *debug { - log.SetLevel(log.Debug) - } - - if *platform != "native" && *runscPath == "" { - if err := testutil.ConfigureExePath(); err != nil { - panic(err.Error()) - } - *runscPath = specutils.ExePath - } - - // Make sure stdout and stderr are opened with O_APPEND, otherwise logs - // from outside the sandbox can (and will) stomp on logs from inside - // the sandbox. - for _, f := range []*os.File{os.Stdout, os.Stderr} { - flags, err := unix.FcntlInt(f.Fd(), unix.F_GETFL, 0) - if err != nil { - fatalf("error getting file flags for %v: %v", f, err) - } - if flags&unix.O_APPEND == 0 { - flags |= unix.O_APPEND - if _, err := unix.FcntlInt(f.Fd(), unix.F_SETFL, flags); err != nil { - fatalf("error setting file flags for %v: %v", f, err) - } - } - } - - // Get all test cases in each binary. - testCases, err := gtest.ParseTestCases(testBin, true) - if err != nil { - fatalf("ParseTestCases(%q) failed: %v", testBin, err) - } - - // Get subset of tests corresponding to shard. - indices, err := testutil.TestIndicesForShard(len(testCases)) - if err != nil { - fatalf("TestsForShard() failed: %v", err) - } - - // Resolve the absolute path for the binary. - testBin, err = filepath.Abs(testBin) - if err != nil { - fatalf("Abs() failed: %v", err) - } - - // Run the tests. - var tests []testing.InternalTest - for _, tci := range indices { - // Capture tc. - tc := testCases[tci] - tests = append(tests, testing.InternalTest{ - Name: fmt.Sprintf("%s_%s", tc.Suite, tc.Name), - F: func(t *testing.T) { - if *platform == "native" { - // Run the test case on host. - runTestCaseNative(testBin, tc, t) - } else { - // Run the test case in runsc. - runTestCaseRunsc(testBin, tc, t) - } - }, - }) - } - - testing.Main(matchString, tests, nil, nil) -} diff --git a/test/runtimes/proctor/BUILD b/test/runtimes/proctor/BUILD index fdc6d3173..b4a9b12de 100644 --- a/test/runtimes/proctor/BUILD +++ b/test/runtimes/proctor/BUILD @@ -7,5 +7,8 @@ go_binary( srcs = ["main.go"], pure = True, visibility = ["//test/runtimes:__pkg__"], - deps = ["//test/runtimes/proctor/lib"], + deps = [ + "//test/runtimes/proctor/lib", + "@org_golang_x_sys//unix:go_default_library", + ], ) diff --git a/test/runtimes/proctor/lib/BUILD b/test/runtimes/proctor/lib/BUILD index 0c8367dfe..f834f1b5a 100644 --- a/test/runtimes/proctor/lib/BUILD +++ b/test/runtimes/proctor/lib/BUILD @@ -13,6 +13,7 @@ go_library( "python.go", ], visibility = ["//test/runtimes/proctor:__pkg__"], + deps = ["@org_golang_x_sys//unix:go_default_library"], ) go_test( diff --git a/test/runtimes/proctor/lib/lib.go b/test/runtimes/proctor/lib/lib.go index f2ba82498..36c60088a 100644 --- a/test/runtimes/proctor/lib/lib.go +++ b/test/runtimes/proctor/lib/lib.go @@ -22,7 +22,8 @@ import ( "os/signal" "path/filepath" "regexp" - "syscall" + + "golang.org/x/sys/unix" ) // TestRunner is an interface that must be implemented for each runtime @@ -59,7 +60,7 @@ func TestRunnerForRuntime(runtime string) (TestRunner, error) { func PauseAndReap() { // Get notified of any new children. ch := make(chan os.Signal, 1) - signal.Notify(ch, syscall.SIGCHLD) + signal.Notify(ch, unix.SIGCHLD) for { if _, ok := <-ch; !ok { @@ -69,7 +70,7 @@ func PauseAndReap() { // Reap the child. for { - if cpid, _ := syscall.Wait4(-1, nil, syscall.WNOHANG, nil); cpid < 1 { + if cpid, _ := unix.Wait4(-1, nil, unix.WNOHANG, nil); cpid < 1 { break } } diff --git a/test/runtimes/proctor/main.go b/test/runtimes/proctor/main.go index 81cb68381..8c076a499 100644 --- a/test/runtimes/proctor/main.go +++ b/test/runtimes/proctor/main.go @@ -22,8 +22,8 @@ import ( "log" "os" "strings" - "syscall" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/test/runtimes/proctor/lib" ) @@ -42,14 +42,14 @@ func setNumFilesLimit() error { // timeout if the NOFILE limit is too high. On gVisor, syscalls are // slower so these tests will need even more time to pass. const nofile = 32768 - rLimit := syscall.Rlimit{} - err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) + rLimit := unix.Rlimit{} + err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rLimit) if err != nil { return fmt.Errorf("failed to get RLIMIT_NOFILE: %v", err) } if rLimit.Cur > nofile { rLimit.Cur = nofile - err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) + err := unix.Setrlimit(unix.RLIMIT_NOFILE, &rLimit) if err != nil { return fmt.Errorf("failed to set RLIMIT_NOFILE: %v", err) } diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD index 9adb1cea3..ef299799e 100644 --- a/test/syscalls/BUILD +++ b/test/syscalls/BUILD @@ -65,14 +65,8 @@ syscall_test( syscall_test( size = "large", - # Produce too many logs in the debug mode. - debug = False, shard_count = most_shards, - # Takes too long for TSAN. Since this is kind of a stress test that doesn't - # involve much concurrency, TSAN's usefulness here is limited anyway. - tags = ["nogotsan"], test = "//test/syscalls/linux:socket_stress_test", - vfs2 = False, ) syscall_test( diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD index 5371f825c..5399d8106 100644 --- a/test/syscalls/linux/BUILD +++ b/test/syscalls/linux/BUILD @@ -2330,13 +2330,15 @@ cc_binary( ], linkstatic = 1, deps = [ + gtest, ":ip_socket_test_util", ":socket_test_util", - "@com_google_absl//absl/strings", - gtest, + "//test/util:file_descriptor", "//test/util:test_main", "//test/util:test_util", "//test/util:thread_util", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", ], ) diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc index e508ce27f..61a421788 100644 --- a/test/syscalls/linux/proc.cc +++ b/test/syscalls/linux/proc.cc @@ -2162,7 +2162,13 @@ class BlockingChild { return tid_; } - void Join() { Stop(); } + void Join() { + { + absl::MutexLock ml(&mu_); + stop_ = true; + } + thread_.Join(); + } private: void Start() { @@ -2172,11 +2178,6 @@ class BlockingChild { mu_.Await(absl::Condition(&stop_)); } - void Stop() { - absl::MutexLock ml(&mu_); - stop_ = true; - } - mutable absl::Mutex mu_; bool stop_ ABSL_GUARDED_BY(mu_) = false; pid_t tid_; @@ -2190,16 +2191,18 @@ class BlockingChild { TEST(ProcTask, NewThreadAppears) { auto initial = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc/self/task", false)); BlockingChild child1; - EXPECT_NO_ERRNO(DirContainsExactly("/proc/self/task", - TaskFiles(initial, {child1.Tid()}))); + // Use Eventually* in case a proc from ealier test is still tearing down. + EXPECT_NO_ERRNO(EventuallyDirContainsExactly( + "/proc/self/task", TaskFiles(initial, {child1.Tid()}))); } TEST(ProcTask, KilledThreadsDisappear) { auto initial = ASSERT_NO_ERRNO_AND_VALUE(ListDir("/proc/self/task/", false)); BlockingChild child1; - EXPECT_NO_ERRNO(DirContainsExactly("/proc/self/task", - TaskFiles(initial, {child1.Tid()}))); + // Use Eventually* in case a proc from ealier test is still tearing down. + EXPECT_NO_ERRNO(EventuallyDirContainsExactly( + "/proc/self/task", TaskFiles(initial, {child1.Tid()}))); // Stat child1's task file. Regression test for b/32097707. struct stat statbuf; diff --git a/test/syscalls/linux/proc_net.cc b/test/syscalls/linux/proc_net.cc index 73140b2e9..20f1dc305 100644 --- a/test/syscalls/linux/proc_net.cc +++ b/test/syscalls/linux/proc_net.cc @@ -40,6 +40,7 @@ namespace { constexpr const char kProcNet[] = "/proc/net"; constexpr const char kIpForward[] = "/proc/sys/net/ipv4/ip_forward"; +constexpr const char kRangeFile[] = "/proc/sys/net/ipv4/ip_local_port_range"; TEST(ProcNetSymlinkTarget, FileMode) { struct stat s; @@ -562,6 +563,42 @@ TEST(ProcSysNetIpv4IpForward, CanReadAndWrite) { EXPECT_EQ(buf, to_write); } +TEST(ProcSysNetPortRange, CanReadAndWrite) { + int min; + int max; + std::string rangefile = ASSERT_NO_ERRNO_AND_VALUE(GetContents(kRangeFile)); + ASSERT_EQ(rangefile.back(), '\n'); + rangefile.pop_back(); + std::vector<std::string> range = + absl::StrSplit(rangefile, absl::ByAnyChar("\t ")); + ASSERT_GT(range.size(), 1); + ASSERT_TRUE(absl::SimpleAtoi(range.front(), &min)); + ASSERT_TRUE(absl::SimpleAtoi(range.back(), &max)); + EXPECT_LE(min, max); + + // If the file isn't writable, there's nothing else to do here. + if (access(kRangeFile, W_OK)) { + return; + } + + constexpr int kSize = 77; + FileDescriptor fd = + ASSERT_NO_ERRNO_AND_VALUE(Open(kRangeFile, O_WRONLY | O_TRUNC, 0)); + max = min + kSize; + const std::string small_range = absl::StrFormat("%d %d", min, max); + ASSERT_THAT(write(fd.get(), small_range.c_str(), small_range.size()), + SyscallSucceedsWithValue(small_range.size())); + + rangefile = ASSERT_NO_ERRNO_AND_VALUE(GetContents(kRangeFile)); + ASSERT_EQ(rangefile.back(), '\n'); + rangefile.pop_back(); + range = absl::StrSplit(rangefile, absl::ByAnyChar("\t ")); + ASSERT_GT(range.size(), 1); + ASSERT_TRUE(absl::SimpleAtoi(range.front(), &min)); + ASSERT_TRUE(absl::SimpleAtoi(range.back(), &max)); + EXPECT_EQ(min + kSize, max); +} + } // namespace } // namespace testing } // namespace gvisor diff --git a/test/syscalls/linux/pty.cc b/test/syscalls/linux/pty.cc index 294b9f6fd..8d15c491e 100644 --- a/test/syscalls/linux/pty.cc +++ b/test/syscalls/linux/pty.cc @@ -1255,8 +1255,11 @@ TEST_F(PtyTest, PartialBadBuffer) { // Read from the replica into bad_buffer. ASSERT_NO_ERRNO(WaitUntilReceived(replica_.get(), size)); - EXPECT_THAT(ReadFd(replica_.get(), bad_buffer, size), - SyscallFailsWithErrno(EFAULT)); + // Before Linux 3b830a9c this returned EFAULT, but after that commit it + // returns EAGAIN. + EXPECT_THAT( + ReadFd(replica_.get(), bad_buffer, size), + AnyOf(SyscallFailsWithErrno(EFAULT), SyscallFailsWithErrno(EAGAIN))); EXPECT_THAT(munmap(addr, 2 * kPageSize), SyscallSucceeds()) << addr; } diff --git a/test/syscalls/linux/socket_generic_stress.cc b/test/syscalls/linux/socket_generic_stress.cc index 679586530..c35aa2183 100644 --- a/test/syscalls/linux/socket_generic_stress.cc +++ b/test/syscalls/linux/socket_generic_stress.cc @@ -17,29 +17,72 @@ #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/un.h> +#include <unistd.h> #include <array> #include <string> #include "gtest/gtest.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" #include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" #include "test/syscalls/linux/ip_socket_test_util.h" #include "test/syscalls/linux/socket_test_util.h" +#include "test/util/file_descriptor.h" #include "test/util/test_util.h" #include "test/util/thread_util.h" namespace gvisor { namespace testing { +constexpr char kRangeFile[] = "/proc/sys/net/ipv4/ip_local_port_range"; + +PosixErrorOr<int> NumPorts() { + int min = 0; + int max = 1 << 16; + + // Read the ephemeral range from /proc. + ASSIGN_OR_RETURN_ERRNO(std::string rangefile, GetContents(kRangeFile)); + const std::string err_msg = + absl::StrFormat("%s has invalid content: %s", kRangeFile, rangefile); + if (rangefile.back() != '\n') { + return PosixError(EINVAL, err_msg); + } + rangefile.pop_back(); + std::vector<std::string> range = + absl::StrSplit(rangefile, absl::ByAnyChar("\t ")); + if (range.size() < 2 || !absl::SimpleAtoi(range.front(), &min) || + !absl::SimpleAtoi(range.back(), &max)) { + return PosixError(EINVAL, err_msg); + } + + // If we can open as writable, limit the range. + if (!access(kRangeFile, W_OK)) { + ASSIGN_OR_RETURN_ERRNO(FileDescriptor fd, + Open(kRangeFile, O_WRONLY | O_TRUNC, 0)); + max = min + 50; + const std::string small_range = absl::StrFormat("%d %d", min, max); + int n = write(fd.get(), small_range.c_str(), small_range.size()); + if (n < 0) { + return PosixError( + errno, + absl::StrFormat("write(%d [%s], \"%s\", %d)", fd.get(), kRangeFile, + small_range.c_str(), small_range.size())); + } + } + return max - min; +} + // Test fixture for tests that apply to pairs of connected sockets. using ConnectStressTest = SocketPairTest; -TEST_P(ConnectStressTest, Reset65kTimes) { - // TODO(b/165912341): These are too slow on KVM platform with nested virt. - SKIP_IF(GvisorPlatform() == Platform::kKVM); - - for (int i = 0; i < 1 << 16; ++i) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); +TEST_P(ConnectStressTest, Reset) { + const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); + for (int i = 0; i < nports * 2; i++) { + const std::unique_ptr<SocketPair> sockets = + ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); // Send some data to ensure that the connection gets reset and the port gets // released immediately. This avoids either end entering TIME-WAIT. @@ -57,6 +100,24 @@ TEST_P(ConnectStressTest, Reset65kTimes) { } } +// Tests that opening too many connections -- without closing them -- does lead +// to port exhaustion. +TEST_P(ConnectStressTest, TooManyOpen) { + const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); + int err_num = 0; + std::vector<std::unique_ptr<SocketPair>> sockets = + std::vector<std::unique_ptr<SocketPair>>(nports); + for (int i = 0; i < nports * 2; i++) { + PosixErrorOr<std::unique_ptr<SocketPair>> socks = NewSocketPair(); + if (!socks.ok()) { + err_num = socks.error().errno_value(); + break; + } + sockets.push_back(std::move(socks).ValueOrDie()); + } + ASSERT_EQ(err_num, EADDRINUSE); +} + INSTANTIATE_TEST_SUITE_P( AllConnectedSockets, ConnectStressTest, ::testing::Values(IPv6UDPBidirectionalBindSocketPair(0), @@ -73,14 +134,40 @@ INSTANTIATE_TEST_SUITE_P( // Test fixture for tests that apply to pairs of connected sockets created with // a persistent listener (if applicable). -using PersistentListenerConnectStressTest = SocketPairTest; +class PersistentListenerConnectStressTest : public SocketPairTest { + protected: + PersistentListenerConnectStressTest() : slept_{false} {} -TEST_P(PersistentListenerConnectStressTest, 65kTimesShutdownCloseFirst) { - // TODO(b/165912341): These are too slow on KVM platform with nested virt. - SKIP_IF(GvisorPlatform() == Platform::kKVM); + // NewSocketSleep is the same as NewSocketPair, but will sleep once (over the + // lifetime of the fixture) and retry if creation fails due to EADDRNOTAVAIL. + PosixErrorOr<std::unique_ptr<SocketPair>> NewSocketSleep() { + // We can't reuse a connection too close in time to its last use, as TCP + // uses the timestamp difference to disambiguate connections. With a + // sufficiently small port range, we'll cycle through too quickly, and TCP + // won't allow for connection reuse. Thus, we sleep the first time + // encountering EADDRINUSE to allow for that difference (1 second in + // gVisor). + PosixErrorOr<std::unique_ptr<SocketPair>> socks = NewSocketPair(); + if (socks.ok()) { + return socks; + } + if (!slept_ && socks.error().errno_value() == EADDRNOTAVAIL) { + absl::SleepFor(absl::Milliseconds(1500)); + slept_ = true; + return NewSocketPair(); + } + return socks; + } - for (int i = 0; i < 1 << 16; ++i) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + private: + bool slept_; +}; + +TEST_P(PersistentListenerConnectStressTest, ShutdownCloseFirst) { + const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); + for (int i = 0; i < nports * 2; i++) { + std::unique_ptr<SocketPair> sockets = + ASSERT_NO_ERRNO_AND_VALUE(NewSocketSleep()); ASSERT_THAT(shutdown(sockets->first_fd(), SHUT_RDWR), SyscallSucceeds()); if (GetParam().type == SOCK_STREAM) { // Poll the other FD to make sure that we see the FIN from the other @@ -97,12 +184,11 @@ TEST_P(PersistentListenerConnectStressTest, 65kTimesShutdownCloseFirst) { } } -TEST_P(PersistentListenerConnectStressTest, 65kTimesShutdownCloseSecond) { - // TODO(b/165912341): These are too slow on KVM platform with nested virt. - SKIP_IF(GvisorPlatform() == Platform::kKVM); - - for (int i = 0; i < 1 << 16; ++i) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); +TEST_P(PersistentListenerConnectStressTest, ShutdownCloseSecond) { + const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); + for (int i = 0; i < nports * 2; i++) { + const std::unique_ptr<SocketPair> sockets = + ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); ASSERT_THAT(shutdown(sockets->second_fd(), SHUT_RDWR), SyscallSucceeds()); if (GetParam().type == SOCK_STREAM) { // Poll the other FD to make sure that we see the FIN from the other @@ -119,12 +205,11 @@ TEST_P(PersistentListenerConnectStressTest, 65kTimesShutdownCloseSecond) { } } -TEST_P(PersistentListenerConnectStressTest, 65kTimesClose) { - // TODO(b/165912341): These are too slow on KVM platform with nested virt. - SKIP_IF(GvisorPlatform() == Platform::kKVM); - - for (int i = 0; i < 1 << 16; ++i) { - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); +TEST_P(PersistentListenerConnectStressTest, Close) { + const int nports = ASSERT_NO_ERRNO_AND_VALUE(NumPorts()); + for (int i = 0; i < nports * 2; i++) { + std::unique_ptr<SocketPair> sockets = + ASSERT_NO_ERRNO_AND_VALUE(NewSocketSleep()); } } @@ -149,7 +234,8 @@ TEST_P(DataTransferStressTest, BigDataTransfer) { // TODO(b/165912341): These are too slow on KVM platform with nested virt. SKIP_IF(GvisorPlatform() == Platform::kKVM); - auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); + const std::unique_ptr<SocketPair> sockets = + ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair()); int client_fd = sockets->first_fd(); int server_fd = sockets->second_fd(); diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc index 344a5a22c..54b45b075 100644 --- a/test/syscalls/linux/socket_inet_loopback.cc +++ b/test/syscalls/linux/socket_inet_loopback.cc @@ -705,12 +705,6 @@ TEST_P(SocketInetLoopbackTest, TCPFinWait2Test_NoRandomSave) { ds.reset(); - if (!IsRunningOnGvisor()) { - ASSERT_THAT( - bind(conn_fd2.get(), reinterpret_cast<sockaddr*>(&conn_bound_addr), - conn_addrlen), - SyscallSucceeds()); - } ASSERT_THAT(RetryEINTR(connect)(conn_fd2.get(), reinterpret_cast<sockaddr*>(&conn_addr), conn_addrlen), diff --git a/test/uds/BUILD b/test/uds/BUILD index 51e2c7ce8..a8f49b50c 100644 --- a/test/uds/BUILD +++ b/test/uds/BUILD @@ -12,5 +12,6 @@ go_library( deps = [ "//pkg/log", "//pkg/unet", + "@org_golang_x_sys//unix:go_default_library", ], ) diff --git a/test/uds/uds.go b/test/uds/uds.go index b714c61b0..02a4a7dee 100644 --- a/test/uds/uds.go +++ b/test/uds/uds.go @@ -21,8 +21,8 @@ import ( "io/ioutil" "os" "path/filepath" - "syscall" + "golang.org/x/sys/unix" "gvisor.dev/gvisor/pkg/log" "gvisor.dev/gvisor/pkg/unet" ) @@ -31,16 +31,16 @@ import ( // // Only works for stream, seqpacket sockets. func createEchoSocket(path string, protocol int) (cleanup func(), err error) { - fd, err := syscall.Socket(syscall.AF_UNIX, protocol, 0) + fd, err := unix.Socket(unix.AF_UNIX, protocol, 0) if err != nil { return nil, fmt.Errorf("error creating echo(%d) socket: %v", protocol, err) } - if err := syscall.Bind(fd, &syscall.SockaddrUnix{Name: path}); err != nil { + if err := unix.Bind(fd, &unix.SockaddrUnix{Name: path}); err != nil { return nil, fmt.Errorf("error binding echo(%d) socket: %v", protocol, err) } - if err := syscall.Listen(fd, 0); err != nil { + if err := unix.Listen(fd, 0); err != nil { return nil, fmt.Errorf("error listening echo(%d) socket: %v", protocol, err) } @@ -97,17 +97,17 @@ func createEchoSocket(path string, protocol int) (cleanup func(), err error) { // // Only relevant for stream, seqpacket sockets. func createNonListeningSocket(path string, protocol int) (cleanup func(), err error) { - fd, err := syscall.Socket(syscall.AF_UNIX, protocol, 0) + fd, err := unix.Socket(unix.AF_UNIX, protocol, 0) if err != nil { return nil, fmt.Errorf("error creating nonlistening(%d) socket: %v", protocol, err) } - if err := syscall.Bind(fd, &syscall.SockaddrUnix{Name: path}); err != nil { + if err := unix.Bind(fd, &unix.SockaddrUnix{Name: path}); err != nil { return nil, fmt.Errorf("error binding nonlistening(%d) socket: %v", protocol, err) } cleanup = func() { - if err := syscall.Close(fd); err != nil { + if err := unix.Close(fd); err != nil { log.Warningf("Failed to close nonlistening(%d) socket: %v", protocol, err) } } @@ -119,12 +119,12 @@ func createNonListeningSocket(path string, protocol int) (cleanup func(), err er // // Only works for dgram sockets. func createNullSocket(path string, protocol int) (cleanup func(), err error) { - fd, err := syscall.Socket(syscall.AF_UNIX, protocol, 0) + fd, err := unix.Socket(unix.AF_UNIX, protocol, 0) if err != nil { return nil, fmt.Errorf("error creating null(%d) socket: %v", protocol, err) } - if err := syscall.Bind(fd, &syscall.SockaddrUnix{Name: path}); err != nil { + if err := unix.Bind(fd, &unix.SockaddrUnix{Name: path}); err != nil { return nil, fmt.Errorf("error binding null(%d) socket: %v", protocol, err) } @@ -174,7 +174,7 @@ func CreateSocketTree(baseDir string) (dir string, cleanup func(), err error) { sockets map[string]socketCreator }{ { - protocol: syscall.SOCK_STREAM, + protocol: unix.SOCK_STREAM, name: "stream", sockets: map[string]socketCreator{ "echo": createEchoSocket, @@ -182,7 +182,7 @@ func CreateSocketTree(baseDir string) (dir string, cleanup func(), err error) { }, }, { - protocol: syscall.SOCK_SEQPACKET, + protocol: unix.SOCK_SEQPACKET, name: "seqpacket", sockets: map[string]socketCreator{ "echo": createEchoSocket, @@ -190,7 +190,7 @@ func CreateSocketTree(baseDir string) (dir string, cleanup func(), err error) { }, }, { - protocol: syscall.SOCK_DGRAM, + protocol: unix.SOCK_DGRAM, name: "dgram", sockets: map[string]socketCreator{ "null": createNullSocket, |