summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/benchmarks/database/redis_test.go4
-rw-r--r--test/benchmarks/fs/bazel_test.go2
-rw-r--r--test/benchmarks/network/node_test.go4
-rw-r--r--test/benchmarks/network/ruby_test.go4
-rw-r--r--test/packetimpact/dut/posix_server.cc28
-rw-r--r--test/packetimpact/proto/posix_server.proto11
-rw-r--r--test/packetimpact/runner/defs.bzl1
-rw-r--r--test/packetimpact/testbench/dut.go42
-rw-r--r--test/packetimpact/tests/BUILD10
-rw-r--r--test/packetimpact/tests/tcp_linger_test.go253
-rw-r--r--test/root/crictl_test.go2
-rw-r--r--test/runtimes/exclude_nodejs12.4.0.csv1
-rw-r--r--test/runtimes/exclude_php7.3.6.csv7
-rw-r--r--test/runtimes/proctor/BUILD1
-rw-r--r--test/syscalls/BUILD12
-rw-r--r--test/syscalls/linux/BUILD97
-rw-r--r--test/syscalls/linux/iptables.cc37
-rw-r--r--test/syscalls/linux/kcov.cc70
-rw-r--r--test/syscalls/linux/memfd.cc17
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc11
-rw-r--r--test/syscalls/linux/socket_ip_tcp_generic.cc150
-rw-r--r--test/syscalls/linux/socket_ip_udp_generic.cc36
-rw-r--r--test/syscalls/linux/socket_ip_udp_unbound_netlink_util.cc58
-rw-r--r--test/syscalls/linux/socket_ip_udp_unbound_netlink_util.h34
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_loopback_netlink.cc32
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc60
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h29
-rw-r--r--test/syscalls/linux/socket_ipv6_udp_unbound_loopback_netlink.cc32
-rw-r--r--test/syscalls/linux/socket_ipv6_udp_unbound_netlink.cc60
-rw-r--r--test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h29
-rw-r--r--test/syscalls/linux/xattr.cc74
-rw-r--r--test/util/fs_util.cc20
-rw-r--r--test/util/fs_util.h6
33 files changed, 1185 insertions, 49 deletions
diff --git a/test/benchmarks/database/redis_test.go b/test/benchmarks/database/redis_test.go
index 394fce820..6671a4969 100644
--- a/test/benchmarks/database/redis_test.go
+++ b/test/benchmarks/database/redis_test.go
@@ -84,12 +84,12 @@ func BenchmarkRedis(b *testing.B) {
ip, err := serverMachine.IPAddress()
if err != nil {
- b.Fatal("failed to get IP from server: %v", err)
+ b.Fatalf("failed to get IP from server: %v", err)
}
serverPort, err := server.FindPort(ctx, port)
if err != nil {
- b.Fatal("failed to get IP from server: %v", err)
+ b.Fatalf("failed to get IP from server: %v", err)
}
if err = harness.WaitUntilServing(ctx, clientMachine, ip, serverPort); err != nil {
diff --git a/test/benchmarks/fs/bazel_test.go b/test/benchmarks/fs/bazel_test.go
index f4236ba37..fdbbfe280 100644
--- a/test/benchmarks/fs/bazel_test.go
+++ b/test/benchmarks/fs/bazel_test.go
@@ -73,7 +73,7 @@ func runBuildBenchmark(b *testing.B, image, workdir, target string) {
if bm.tmpfs {
if out, err := container.Exec(ctx, dockerutil.ExecOpts{},
"cp", "-r", workdir, "/tmp/."); err != nil {
- b.Fatal("failed to copy directory: %v %s", err, out)
+ b.Fatalf("failed to copy directory: %v (%s)", err, out)
}
workdir = "/tmp" + workdir
}
diff --git a/test/benchmarks/network/node_test.go b/test/benchmarks/network/node_test.go
index 52eb794c4..0f4a205b6 100644
--- a/test/benchmarks/network/node_test.go
+++ b/test/benchmarks/network/node_test.go
@@ -48,14 +48,14 @@ func runNode(b *testing.B, hey *tools.Hey) {
// The machine to hold Redis and the Node Server.
serverMachine, err := h.GetMachine()
if err != nil {
- b.Fatal("failed to get machine with: %v", err)
+ b.Fatalf("failed to get machine with: %v", err)
}
defer serverMachine.CleanUp()
// The machine to run 'hey'.
clientMachine, err := h.GetMachine()
if err != nil {
- b.Fatal("failed to get machine with: %v", err)
+ b.Fatalf("failed to get machine with: %v", err)
}
defer clientMachine.CleanUp()
diff --git a/test/benchmarks/network/ruby_test.go b/test/benchmarks/network/ruby_test.go
index 5e0b2b724..67f63f76a 100644
--- a/test/benchmarks/network/ruby_test.go
+++ b/test/benchmarks/network/ruby_test.go
@@ -47,14 +47,14 @@ func runRuby(b *testing.B, hey *tools.Hey) {
// The machine to hold Redis and the Ruby Server.
serverMachine, err := h.GetMachine()
if err != nil {
- b.Fatal("failed to get machine with: %v", err)
+ b.Fatalf("failed to get machine with: %v", err)
}
defer serverMachine.CleanUp()
// The machine to run 'hey'.
clientMachine, err := h.GetMachine()
if err != nil {
- b.Fatal("failed to get machine with: %v", err)
+ b.Fatalf("failed to get machine with: %v", err)
}
defer clientMachine.CleanUp()
ctx := context.Background()
diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc
index 29d4cc6fe..0f8e279f8 100644
--- a/test/packetimpact/dut/posix_server.cc
+++ b/test/packetimpact/dut/posix_server.cc
@@ -28,6 +28,7 @@
#include "include/grpcpp/security/server_credentials.h"
#include "include/grpcpp/server_builder.h"
+#include "include/grpcpp/server_context.h"
#include "test/packetimpact/proto/posix_server.grpc.pb.h"
#include "test/packetimpact/proto/posix_server.pb.h"
@@ -108,7 +109,7 @@
}
class PosixImpl final : public posix_server::Posix::Service {
- ::grpc::Status Accept(grpc_impl::ServerContext *context,
+ ::grpc::Status Accept(grpc::ServerContext *context,
const ::posix_server::AcceptRequest *request,
::posix_server::AcceptResponse *response) override {
sockaddr_storage addr;
@@ -119,7 +120,7 @@ class PosixImpl final : public posix_server::Posix::Service {
return sockaddr_to_proto(addr, addrlen, response->mutable_addr());
}
- ::grpc::Status Bind(grpc_impl::ServerContext *context,
+ ::grpc::Status Bind(grpc::ServerContext *context,
const ::posix_server::BindRequest *request,
::posix_server::BindResponse *response) override {
if (!request->has_addr()) {
@@ -140,7 +141,7 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
- ::grpc::Status Close(grpc_impl::ServerContext *context,
+ ::grpc::Status Close(grpc::ServerContext *context,
const ::posix_server::CloseRequest *request,
::posix_server::CloseResponse *response) override {
response->set_ret(close(request->fd()));
@@ -148,7 +149,7 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
- ::grpc::Status Connect(grpc_impl::ServerContext *context,
+ ::grpc::Status Connect(grpc::ServerContext *context,
const ::posix_server::ConnectRequest *request,
::posix_server::ConnectResponse *response) override {
if (!request->has_addr()) {
@@ -168,7 +169,7 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
- ::grpc::Status Fcntl(grpc_impl::ServerContext *context,
+ ::grpc::Status Fcntl(grpc::ServerContext *context,
const ::posix_server::FcntlRequest *request,
::posix_server::FcntlResponse *response) override {
response->set_ret(::fcntl(request->fd(), request->cmd(), request->arg()));
@@ -177,7 +178,7 @@ class PosixImpl final : public posix_server::Posix::Service {
}
::grpc::Status GetSockName(
- grpc_impl::ServerContext *context,
+ grpc::ServerContext *context,
const ::posix_server::GetSockNameRequest *request,
::posix_server::GetSockNameResponse *response) override {
sockaddr_storage addr;
@@ -189,7 +190,7 @@ class PosixImpl final : public posix_server::Posix::Service {
}
::grpc::Status GetSockOpt(
- grpc_impl::ServerContext *context,
+ grpc::ServerContext *context,
const ::posix_server::GetSockOptRequest *request,
::posix_server::GetSockOptResponse *response) override {
switch (request->type()) {
@@ -230,7 +231,7 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
- ::grpc::Status Listen(grpc_impl::ServerContext *context,
+ ::grpc::Status Listen(grpc::ServerContext *context,
const ::posix_server::ListenRequest *request,
::posix_server::ListenResponse *response) override {
response->set_ret(listen(request->sockfd(), request->backlog()));
@@ -269,7 +270,7 @@ class PosixImpl final : public posix_server::Posix::Service {
}
::grpc::Status SetSockOpt(
- grpc_impl::ServerContext *context,
+ grpc::ServerContext *context,
const ::posix_server::SetSockOptRequest *request,
::posix_server::SetSockOptResponse *response) override {
switch (request->optval().val_case()) {
@@ -302,7 +303,7 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
- ::grpc::Status Socket(grpc_impl::ServerContext *context,
+ ::grpc::Status Socket(grpc::ServerContext *context,
const ::posix_server::SocketRequest *request,
::posix_server::SocketResponse *response) override {
response->set_fd(
@@ -311,6 +312,13 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
+ ::grpc::Status Shutdown(grpc_impl::ServerContext *context,
+ const ::posix_server::ShutdownRequest *request,
+ ::posix_server::ShutdownResponse *response) override {
+ response->set_errno_(shutdown(request->fd(), request->how()));
+ return ::grpc::Status::OK;
+ }
+
::grpc::Status Recv(::grpc::ServerContext *context,
const ::posix_server::RecvRequest *request,
::posix_server::RecvResponse *response) override {
diff --git a/test/packetimpact/proto/posix_server.proto b/test/packetimpact/proto/posix_server.proto
index ccd20b10d..f32ed54ef 100644
--- a/test/packetimpact/proto/posix_server.proto
+++ b/test/packetimpact/proto/posix_server.proto
@@ -188,6 +188,15 @@ message SocketResponse {
int32 errno_ = 2; // "errno" may fail to compile in c++.
}
+message ShutdownRequest {
+ int32 fd = 1;
+ int32 how = 2;
+}
+
+message ShutdownResponse {
+ int32 errno_ = 1; // "errno" may fail to compile in c++.
+}
+
message RecvRequest {
int32 sockfd = 1;
int32 len = 2;
@@ -225,6 +234,8 @@ service Posix {
rpc SetSockOpt(SetSockOptRequest) returns (SetSockOptResponse);
// Call socket() on the DUT.
rpc Socket(SocketRequest) returns (SocketResponse);
+ // Call shutdown() on the DUT.
+ rpc Shutdown(ShutdownRequest) returns (ShutdownResponse);
// Call recv() on the DUT.
rpc Recv(RecvRequest) returns (RecvResponse);
}
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index 93a36c6c2..d72c63fe6 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -125,6 +125,7 @@ def packetimpact_go_test(name, size = "small", pure = True, expect_native_failur
name = testbench_binary,
size = size,
pure = pure,
+ nogo = False, # FIXME(gvisor.dev/issue/3374): Not working with all build systems.
tags = [
"local",
"manual",
diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go
index 73c532e75..6165ab293 100644
--- a/test/packetimpact/testbench/dut.go
+++ b/test/packetimpact/testbench/dut.go
@@ -16,11 +16,13 @@ package testbench
import (
"context"
+ "encoding/binary"
"flag"
"net"
"strconv"
"syscall"
"testing"
+ "time"
pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto"
@@ -700,3 +702,43 @@ func (dut *DUT) RecvWithErrno(ctx context.Context, t *testing.T, sockfd, len, fl
}
return resp.GetRet(), resp.GetBuf(), syscall.Errno(resp.GetErrno_())
}
+
+// SetSockLingerOption sets SO_LINGER socket option on the DUT.
+func (dut *DUT) SetSockLingerOption(t *testing.T, sockfd int32, timeout time.Duration, enable bool) {
+ var linger unix.Linger
+ if enable {
+ linger.Onoff = 1
+ }
+ linger.Linger = int32(timeout / time.Second)
+
+ buf := make([]byte, 8)
+ binary.LittleEndian.PutUint32(buf, uint32(linger.Onoff))
+ binary.LittleEndian.PutUint32(buf[4:], uint32(linger.Linger))
+ dut.SetSockOpt(t, sockfd, unix.SOL_SOCKET, unix.SO_LINGER, buf)
+}
+
+// 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 {
+ t.Helper()
+
+ ctx, cancel := context.WithTimeout(context.Background(), RPCTimeout)
+ defer cancel()
+ return dut.ShutdownWithErrno(ctx, t, fd, how)
+}
+
+// ShutdownWithErrno calls shutdown on the DUT.
+func (dut *DUT) ShutdownWithErrno(ctx context.Context, t *testing.T, fd, how int32) error {
+ t.Helper()
+
+ req := pb.ShutdownRequest{
+ Fd: fd,
+ How: how,
+ }
+ resp, err := dut.posixServer.Shutdown(ctx, &req)
+ if err != nil {
+ t.Fatalf("failed to call Shutdown: %s", err)
+ }
+ return syscall.Errno(resp.GetErrno_())
+}
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index 74658fea0..7a7152fa5 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -308,3 +308,13 @@ packetimpact_go_test(
"@org_golang_x_sys//unix:go_default_library",
],
)
+
+packetimpact_go_test(
+ name = "tcp_linger",
+ srcs = ["tcp_linger_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
diff --git a/test/packetimpact/tests/tcp_linger_test.go b/test/packetimpact/tests/tcp_linger_test.go
new file mode 100644
index 000000000..913e49e06
--- /dev/null
+++ b/test/packetimpact/tests/tcp_linger_test.go
@@ -0,0 +1,253 @@
+// 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_linger_test
+
+import (
+ "context"
+ "flag"
+ "syscall"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+func createSocket(t *testing.T, dut testbench.DUT) (int32, int32, testbench.TCPIPv4) {
+ listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ conn := testbench.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ conn.Connect(t)
+ acceptFD, _ := dut.Accept(t, listenFD)
+ return acceptFD, listenFD, conn
+}
+
+func closeAll(t *testing.T, dut testbench.DUT, listenFD int32, conn testbench.TCPIPv4) {
+ conn.Close(t)
+ dut.Close(t, listenFD)
+ dut.TearDown()
+}
+
+// lingerDuration is the timeout value used with SO_LINGER socket option.
+const lingerDuration = 3 * time.Second
+
+// TestTCPLingerZeroTimeout tests when SO_LINGER is set with zero timeout. DUT
+// should send RST-ACK when socket is closed.
+func TestTCPLingerZeroTimeout(t *testing.T) {
+ // Create a socket, listen, TCP connect, and accept.
+ dut := testbench.NewDUT(t)
+ acceptFD, listenFD, conn := createSocket(t, dut)
+ defer closeAll(t, dut, listenFD, conn)
+
+ dut.SetSockLingerOption(t, acceptFD, 0, true)
+ 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 {
+ t.Errorf("expected RST-ACK packet within a second but got none: %s", err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+}
+
+// TestTCPLingerOff tests when SO_LINGER is not set. DUT should send FIN-ACK
+// when socket is closed.
+func TestTCPLingerOff(t *testing.T) {
+ // Create a socket, listen, TCP connect, and accept.
+ dut := testbench.NewDUT(t)
+ acceptFD, listenFD, conn := createSocket(t, dut)
+ defer closeAll(t, dut, listenFD, conn)
+
+ 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 {
+ t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+}
+
+// TestTCPLingerNonZeroTimeout tests when SO_LINGER is set with non-zero timeout.
+// DUT should close the socket after timeout.
+func TestTCPLingerNonZeroTimeout(t *testing.T) {
+ for _, tt := range []struct {
+ description string
+ lingerOn bool
+ }{
+ {"WithNonZeroLinger", true},
+ {"WithoutLinger", false},
+ } {
+ t.Run(tt.description, func(t *testing.T) {
+ // Create a socket, listen, TCP connect, and accept.
+ dut := testbench.NewDUT(t)
+ acceptFD, listenFD, conn := createSocket(t, dut)
+ defer closeAll(t, dut, listenFD, conn)
+
+ dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn)
+
+ // Increase timeout as Close will take longer time to
+ // return when SO_LINGER is set with non-zero timeout.
+ timeout := lingerDuration + 1*time.Second
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ defer cancel()
+ start := time.Now()
+ dut.CloseWithErrno(ctx, t, acceptFD)
+ end := time.Now()
+ diff := end.Sub(start)
+
+ if tt.lingerOn && diff < lingerDuration {
+ t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration)
+ } else if !tt.lingerOn && diff > 1*time.Second {
+ 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 {
+ t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+ })
+ }
+}
+
+// TestTCPLingerSendNonZeroTimeout tests when SO_LINGER is set with non-zero
+// timeout and send a packet. DUT should close the socket after timeout.
+func TestTCPLingerSendNonZeroTimeout(t *testing.T) {
+ for _, tt := range []struct {
+ description string
+ lingerOn bool
+ }{
+ {"WithSendNonZeroLinger", true},
+ {"WithoutLinger", false},
+ } {
+ t.Run(tt.description, func(t *testing.T) {
+ // Create a socket, listen, TCP connect, and accept.
+ dut := testbench.NewDUT(t)
+ acceptFD, listenFD, conn := createSocket(t, dut)
+ defer closeAll(t, dut, listenFD, conn)
+
+ dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn)
+
+ // Send data.
+ sampleData := []byte("Sample Data")
+ dut.Send(t, acceptFD, sampleData, 0)
+
+ // Increase timeout as Close will take longer time to
+ // return when SO_LINGER is set with non-zero timeout.
+ timeout := lingerDuration + 1*time.Second
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ defer cancel()
+ start := time.Now()
+ dut.CloseWithErrno(ctx, t, acceptFD)
+ end := time.Now()
+ diff := end.Sub(start)
+
+ if tt.lingerOn && diff < lingerDuration {
+ t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration)
+ } else if !tt.lingerOn && diff > 1*time.Second {
+ t.Errorf("expected close to return within a second, but returned later")
+ }
+
+ samplePayload := &testbench.Payload{Bytes: sampleData}
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ 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 {
+ t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+ })
+ }
+}
+
+// TestTCPLingerShutdownZeroTimeout tests SO_LINGER with shutdown() and zero
+// timeout. DUT should send RST-ACK when socket is closed.
+func TestTCPLingerShutdownZeroTimeout(t *testing.T) {
+ // Create a socket, listen, TCP connect, and accept.
+ dut := testbench.NewDUT(t)
+ acceptFD, listenFD, conn := createSocket(t, dut)
+ defer closeAll(t, dut, listenFD, conn)
+
+ dut.SetSockLingerOption(t, acceptFD, 0, true)
+ dut.Shutdown(t, acceptFD, syscall.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 {
+ 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 {
+ t.Errorf("expected RST-ACK packet within a second but got none: %s", err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+}
+
+// TestTCPLingerShutdownSendNonZeroTimeout tests SO_LINGER with shutdown() and
+// non-zero timeout. DUT should close the socket after timeout.
+func TestTCPLingerShutdownSendNonZeroTimeout(t *testing.T) {
+ for _, tt := range []struct {
+ description string
+ lingerOn bool
+ }{
+ {"shutdownRDWR", true},
+ {"shutdownRDWR", false},
+ } {
+ t.Run(tt.description, func(t *testing.T) {
+ // Create a socket, listen, TCP connect, and accept.
+ dut := testbench.NewDUT(t)
+ acceptFD, listenFD, conn := createSocket(t, dut)
+ defer closeAll(t, dut, listenFD, conn)
+
+ dut.SetSockLingerOption(t, acceptFD, lingerDuration, tt.lingerOn)
+
+ // Send data.
+ sampleData := []byte("Sample Data")
+ dut.Send(t, acceptFD, sampleData, 0)
+
+ dut.Shutdown(t, acceptFD, syscall.SHUT_RDWR)
+
+ // Increase timeout as Close will take longer time to
+ // return when SO_LINGER is set with non-zero timeout.
+ timeout := lingerDuration + 1*time.Second
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ defer cancel()
+ start := time.Now()
+ dut.CloseWithErrno(ctx, t, acceptFD)
+ end := time.Now()
+ diff := end.Sub(start)
+
+ if tt.lingerOn && diff < lingerDuration {
+ t.Errorf("expected close to return after %v seconds, but returned sooner", lingerDuration)
+ } else if !tt.lingerOn && diff > 1*time.Second {
+ t.Errorf("expected close to return within a second, but returned later")
+ }
+
+ samplePayload := &testbench.Payload{Bytes: sampleData}
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ 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 {
+ t.Errorf("expected FIN-ACK packet within a second but got none: %s", err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+ })
+ }
+}
diff --git a/test/root/crictl_test.go b/test/root/crictl_test.go
index df91fa0fe..11ac5cb52 100644
--- a/test/root/crictl_test.go
+++ b/test/root/crictl_test.go
@@ -418,7 +418,7 @@ func setup(t *testing.T, version string) (*criutil.Crictl, func(), error) {
// care about the docker runtime name.
config = v2Template
default:
- t.Fatalf("unknown version: %d", version)
+ t.Fatalf("unknown version: %s", version)
}
t.Logf("Using config: %s", config)
diff --git a/test/runtimes/exclude_nodejs12.4.0.csv b/test/runtimes/exclude_nodejs12.4.0.csv
index 1d8e65fd0..5866ee56d 100644
--- a/test/runtimes/exclude_nodejs12.4.0.csv
+++ b/test/runtimes/exclude_nodejs12.4.0.csv
@@ -49,6 +49,7 @@ pseudo-tty/test-tty-wrap.js,b/162801321,
pummel/test-heapdump-http2.js,,Flaky
pummel/test-net-pingpong.js,,
pummel/test-vm-memleak.js,b/162799436,
+pummel/test-watch-file.js,,Flaky - Timeout
sequential/test-child-process-pass-fd.js,b/63926391,Flaky
sequential/test-https-connect-localport.js,,Flaky - EADDRINUSE
sequential/test-net-bytes-per-incoming-chunk-overhead.js,,flaky - timeout
diff --git a/test/runtimes/exclude_php7.3.6.csv b/test/runtimes/exclude_php7.3.6.csv
index 2ce979dc8..815a137c5 100644
--- a/test/runtimes/exclude_php7.3.6.csv
+++ b/test/runtimes/exclude_php7.3.6.csv
@@ -13,6 +13,13 @@ ext/session/tests/session_set_save_handler_class_018.phpt,,
ext/session/tests/session_set_save_handler_iface_003.phpt,,
ext/session/tests/session_set_save_handler_sid_001.phpt,,
ext/session/tests/session_set_save_handler_variation4.phpt,,
+ext/standard/tests/file/disk.phpt,https://bugs.php.net/bug.php?id=80018,
+ext/standard/tests/file/disk_free_space_basic.phpt,https://bugs.php.net/bug.php?id=80018,
+ext/standard/tests/file/disk_free_space_error.phpt,https://bugs.php.net/bug.php?id=80018,
+ext/standard/tests/file/disk_free_space_variation.phpt,https://bugs.php.net/bug.php?id=80018,
+ext/standard/tests/file/disk_total_space_basic.phpt,https://bugs.php.net/bug.php?id=80018,
+ext/standard/tests/file/disk_total_space_error.phpt,https://bugs.php.net/bug.php?id=80018,
+ext/standard/tests/file/disk_total_space_variation.phpt,https://bugs.php.net/bug.php?id=80018,
ext/standard/tests/file/fopen_variation19.phpt,b/162894964,
ext/standard/tests/file/lstat_stat_variation14.phpt,,Flaky
ext/standard/tests/file/php_fd_wrapper_01.phpt,,
diff --git a/test/runtimes/proctor/BUILD b/test/runtimes/proctor/BUILD
index f76e2ddc0..d1935cbe8 100644
--- a/test/runtimes/proctor/BUILD
+++ b/test/runtimes/proctor/BUILD
@@ -21,6 +21,7 @@ go_test(
size = "small",
srcs = ["proctor_test.go"],
library = ":proctor",
+ nogo = False, # FIXME(gvisor.dev/issue/3374): Not working with all build systems.
pure = True,
deps = [
"//pkg/test/testutil",
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
index ad53e92e5..65e8299c3 100644
--- a/test/syscalls/BUILD
+++ b/test/syscalls/BUILD
@@ -263,6 +263,10 @@ syscall_test(
)
syscall_test(
+ test = "//test/syscalls/linux:kcov_test",
+)
+
+syscall_test(
test = "//test/syscalls/linux:kill_test",
)
@@ -675,6 +679,14 @@ syscall_test(
)
syscall_test(
+ test = "//test/syscalls/linux:socket_ipv4_udp_unbound_loopback_netlink_test",
+)
+
+syscall_test(
+ test = "//test/syscalls/linux:socket_ipv6_udp_unbound_loopback_netlink_test",
+)
+
+syscall_test(
test = "//test/syscalls/linux:socket_ip_unbound_test",
)
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index ecd2d8d2a..5a323d331 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -1068,6 +1068,20 @@ cc_binary(
)
cc_binary(
+ name = "kcov_test",
+ testonly = 1,
+ srcs = ["kcov.cc"],
+ linkstatic = 1,
+ deps = [
+ "//test/util:capability_util",
+ "//test/util:file_descriptor",
+ gtest,
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
+cc_binary(
name = "kill_test",
testonly = 1,
srcs = ["kill.cc"],
@@ -2403,6 +2417,57 @@ cc_library(
)
cc_library(
+ name = "socket_ip_udp_unbound_netlink_test_utils",
+ testonly = 1,
+ srcs = [
+ "socket_ip_udp_unbound_netlink_util.cc",
+ ],
+ hdrs = [
+ "socket_ip_udp_unbound_netlink_util.h",
+ ],
+ deps = [
+ ":socket_test_util",
+ ],
+ alwayslink = 1,
+)
+
+cc_library(
+ name = "socket_ipv4_udp_unbound_netlink_test_cases",
+ testonly = 1,
+ srcs = [
+ "socket_ipv4_udp_unbound_netlink.cc",
+ ],
+ hdrs = [
+ "socket_ipv4_udp_unbound_netlink.h",
+ ],
+ deps = [
+ ":socket_ip_udp_unbound_netlink_test_utils",
+ ":socket_netlink_route_util",
+ "//test/util:capability_util",
+ gtest,
+ ],
+ alwayslink = 1,
+)
+
+cc_library(
+ name = "socket_ipv6_udp_unbound_netlink_test_cases",
+ testonly = 1,
+ srcs = [
+ "socket_ipv6_udp_unbound_netlink.cc",
+ ],
+ hdrs = [
+ "socket_ipv6_udp_unbound_netlink.h",
+ ],
+ deps = [
+ ":socket_ip_udp_unbound_netlink_test_utils",
+ ":socket_netlink_route_util",
+ "//test/util:capability_util",
+ gtest,
+ ],
+ alwayslink = 1,
+)
+
+cc_library(
name = "socket_ipv4_udp_unbound_external_networking_test_cases",
testonly = 1,
srcs = [
@@ -2756,6 +2821,38 @@ cc_binary(
)
cc_binary(
+ name = "socket_ipv4_udp_unbound_loopback_netlink_test",
+ testonly = 1,
+ srcs = [
+ "socket_ipv4_udp_unbound_loopback_netlink.cc",
+ ],
+ linkstatic = 1,
+ deps = [
+ ":ip_socket_test_util",
+ ":socket_ipv4_udp_unbound_netlink_test_cases",
+ ":socket_test_util",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
+cc_binary(
+ name = "socket_ipv6_udp_unbound_loopback_netlink_test",
+ testonly = 1,
+ srcs = [
+ "socket_ipv6_udp_unbound_loopback_netlink.cc",
+ ],
+ linkstatic = 1,
+ deps = [
+ ":ip_socket_test_util",
+ ":socket_ipv6_udp_unbound_netlink_test_cases",
+ ":socket_test_util",
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
+cc_binary(
name = "socket_ip_unbound_test",
testonly = 1,
srcs = [
diff --git a/test/syscalls/linux/iptables.cc b/test/syscalls/linux/iptables.cc
index 9b338d970..f1af8f097 100644
--- a/test/syscalls/linux/iptables.cc
+++ b/test/syscalls/linux/iptables.cc
@@ -67,12 +67,43 @@ TEST(IPTablesBasic, FailSockoptNonRaw) {
struct ipt_getinfo info = {};
snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
socklen_t info_size = sizeof(info);
- EXPECT_THAT(getsockopt(sock, IPPROTO_IP, IPT_SO_GET_INFO, &info, &info_size),
+ EXPECT_THAT(getsockopt(sock, SOL_IP, IPT_SO_GET_INFO, &info, &info_size),
SyscallFailsWithErrno(ENOPROTOOPT));
ASSERT_THAT(close(sock), SyscallSucceeds());
}
+TEST(IPTablesBasic, GetInfoErrorPrecedence) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int sock;
+ ASSERT_THAT(sock = socket(AF_INET, SOCK_DGRAM, 0), SyscallSucceeds());
+
+ // When using the wrong type of socket and a too-short optlen, we should get
+ // EINVAL.
+ struct ipt_getinfo info = {};
+ snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
+ socklen_t info_size = sizeof(info) - 1;
+ ASSERT_THAT(getsockopt(sock, SOL_IP, IPT_SO_GET_INFO, &info, &info_size),
+ SyscallFailsWithErrno(EINVAL));
+}
+
+TEST(IPTablesBasic, GetEntriesErrorPrecedence) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int sock;
+ ASSERT_THAT(sock = socket(AF_INET, SOCK_DGRAM, 0), SyscallSucceeds());
+
+ // When using the wrong type of socket and a too-short optlen, we should get
+ // EINVAL.
+ struct ipt_get_entries entries = {};
+ socklen_t entries_size = sizeof(struct ipt_get_entries) - 1;
+ snprintf(entries.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
+ ASSERT_THAT(
+ getsockopt(sock, SOL_IP, IPT_SO_GET_ENTRIES, &entries, &entries_size),
+ SyscallFailsWithErrno(EINVAL));
+}
+
// Fixture for iptables tests.
class IPTablesTest : public ::testing::Test {
protected:
@@ -112,7 +143,7 @@ TEST_F(IPTablesTest, InitialState) {
struct ipt_getinfo info = {};
snprintf(info.name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
socklen_t info_size = sizeof(info);
- ASSERT_THAT(getsockopt(s_, IPPROTO_IP, IPT_SO_GET_INFO, &info, &info_size),
+ ASSERT_THAT(getsockopt(s_, SOL_IP, IPT_SO_GET_INFO, &info, &info_size),
SyscallSucceeds());
// The nat table supports PREROUTING, and OUTPUT.
@@ -148,7 +179,7 @@ TEST_F(IPTablesTest, InitialState) {
snprintf(entries->name, XT_TABLE_MAXNAMELEN, "%s", kNatTablename);
entries->size = info.size;
ASSERT_THAT(
- getsockopt(s_, IPPROTO_IP, IPT_SO_GET_ENTRIES, entries, &entries_size),
+ getsockopt(s_, SOL_IP, IPT_SO_GET_ENTRIES, entries, &entries_size),
SyscallSucceeds());
// Verify the name and size.
diff --git a/test/syscalls/linux/kcov.cc b/test/syscalls/linux/kcov.cc
new file mode 100644
index 000000000..f3c30444e
--- /dev/null
+++ b/test/syscalls/linux/kcov.cc
@@ -0,0 +1,70 @@
+// Copyright 2018 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.
+
+#include <sys/errno.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+
+#include "gtest/gtest.h"
+#include "test/util/capability_util.h"
+#include "test/util/file_descriptor.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+namespace {
+
+// For this test to work properly, it must be run with coverage enabled. On
+// native Linux, this involves compiling the kernel with kcov enabled. For
+// gVisor, we need to enable the Go coverage tool, e.g.
+// bazel test --collect_coverage_data --instrumentation_filter=//pkg/... <test>.
+TEST(KcovTest, Kcov) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability((CAP_DAC_OVERRIDE))));
+
+ constexpr int kSize = 4096;
+ constexpr int KCOV_INIT_TRACE = 0x80086301;
+ constexpr int KCOV_ENABLE = 0x6364;
+
+ int fd;
+ ASSERT_THAT(fd = open("/sys/kernel/debug/kcov", O_RDWR),
+ AnyOf(SyscallSucceeds(), SyscallFailsWithErrno(ENOENT)));
+
+ // Kcov not enabled.
+ SKIP_IF(errno == ENOENT);
+
+ ASSERT_THAT(ioctl(fd, KCOV_INIT_TRACE, kSize), SyscallSucceeds());
+ uint64_t* area = (uint64_t*)mmap(nullptr, kSize * sizeof(uint64_t),
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+
+ ASSERT_TRUE(area != MAP_FAILED);
+ ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallSucceeds());
+
+ for (int i = 0; i < 10; i++) {
+ // Make some syscalls to generate coverage data.
+ ASSERT_THAT(ioctl(fd, KCOV_ENABLE, 0), SyscallFailsWithErrno(EINVAL));
+ }
+
+ uint64_t num_pcs = *(uint64_t*)(area);
+ EXPECT_GT(num_pcs, 0);
+ for (uint64_t i = 1; i <= num_pcs; i++) {
+ // Verify that PCs are in the standard kernel range.
+ EXPECT_GT(area[i], 0xffffffff7fffffffL);
+ }
+}
+
+} // namespace
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/memfd.cc b/test/syscalls/linux/memfd.cc
index f8b7f7938..4a450742b 100644
--- a/test/syscalls/linux/memfd.cc
+++ b/test/syscalls/linux/memfd.cc
@@ -14,12 +14,10 @@
#include <errno.h>
#include <fcntl.h>
-#include <linux/magic.h>
#include <linux/memfd.h>
#include <linux/unistd.h>
#include <string.h>
#include <sys/mman.h>
-#include <sys/statfs.h>
#include <sys/syscall.h>
#include <vector>
@@ -53,6 +51,7 @@ namespace {
#define F_SEAL_GROW 0x0004
#define F_SEAL_WRITE 0x0008
+using ::gvisor::testing::IsTmpfs;
using ::testing::StartsWith;
const std::string kMemfdName = "some-memfd";
@@ -444,20 +443,6 @@ TEST(MemfdTest, SealsAreInodeLevelProperties) {
EXPECT_THAT(ftruncate(memfd3.get(), kPageSize), SyscallFailsWithErrno(EPERM));
}
-PosixErrorOr<bool> IsTmpfs(const std::string& path) {
- struct statfs stat;
- if (statfs(path.c_str(), &stat)) {
- if (errno == ENOENT) {
- // Nothing at path, don't raise this as an error. Instead, just report no
- // tmpfs at path.
- return false;
- }
- return PosixError(errno,
- absl::StrFormat("statfs(\"%s\", %#p)", path, &stat));
- }
- return stat.f_type == TMPFS_MAGIC;
-}
-
// Tmpfs files also support seals, but are created with F_SEAL_SEAL.
TEST(MemfdTest, TmpfsFilesHaveSealSeal) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs("/tmp")));
diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc
index bd30fb86b..7c1d6a414 100644
--- a/test/syscalls/linux/socket_inet_loopback.cc
+++ b/test/syscalls/linux/socket_inet_loopback.cc
@@ -97,11 +97,13 @@ TEST(BadSocketPairArgs, ValidateErrForBadCallsToSocketPair) {
ASSERT_THAT(socketpair(AF_INET6, 0, 0, fd),
SyscallFailsWithErrno(ESOCKTNOSUPPORT));
- // Invalid AF will return ENOAFSUPPORT.
+ // Invalid AF will return ENOAFSUPPORT or EPERM.
ASSERT_THAT(socketpair(AF_MAX, 0, 0, fd),
- SyscallFailsWithErrno(EAFNOSUPPORT));
+ ::testing::AnyOf(SyscallFailsWithErrno(EAFNOSUPPORT),
+ SyscallFailsWithErrno(EPERM)));
ASSERT_THAT(socketpair(8675309, 0, 0, fd),
- SyscallFailsWithErrno(EAFNOSUPPORT));
+ ::testing::AnyOf(SyscallFailsWithErrno(EAFNOSUPPORT),
+ SyscallFailsWithErrno(EPERM)));
}
enum class Operation {
@@ -116,7 +118,8 @@ std::string OperationToString(Operation operation) {
return "Bind";
case Operation::Connect:
return "Connect";
- case Operation::SendTo:
+ // Operation::SendTo is the default.
+ default:
return "SendTo";
}
}
diff --git a/test/syscalls/linux/socket_ip_tcp_generic.cc b/test/syscalls/linux/socket_ip_tcp_generic.cc
index 53c076787..f4b69c46c 100644
--- a/test/syscalls/linux/socket_ip_tcp_generic.cc
+++ b/test/syscalls/linux/socket_ip_tcp_generic.cc
@@ -819,18 +819,37 @@ TEST_P(TCPSocketPairTest, TCPLingerTimeoutDefault) {
EXPECT_EQ(get, kDefaultTCPLingerTimeout);
}
-TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutZeroOrLess) {
+TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutLessThanZero) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
- constexpr int kZero = 0;
- EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &kZero,
- sizeof(kZero)),
- SyscallSucceedsWithValue(0));
-
constexpr int kNegative = -1234;
EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2,
&kNegative, sizeof(kNegative)),
SyscallSucceedsWithValue(0));
+ int get = INT_MAX;
+ socklen_t get_len = sizeof(get);
+ EXPECT_THAT(
+ getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get_len, sizeof(get));
+ EXPECT_EQ(get, -1);
+}
+
+TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutZero) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ constexpr int kZero = 0;
+ EXPECT_THAT(setsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &kZero,
+ sizeof(kZero)),
+ SyscallSucceedsWithValue(0));
+ int get = -1;
+ socklen_t get_len = sizeof(get);
+ EXPECT_THAT(
+ getsockopt(sockets->first_fd(), IPPROTO_TCP, TCP_LINGER2, &get, &get_len),
+ SyscallSucceedsWithValue(0));
+ EXPECT_EQ(get_len, sizeof(get));
+ EXPECT_THAT(get,
+ AnyOf(Eq(kMaxTCPLingerTimeout), Eq(kOldMaxTCPLingerTimeout)));
}
TEST_P(TCPSocketPairTest, SetTCPLingerTimeoutAboveMax) {
@@ -1061,5 +1080,124 @@ TEST_P(TCPSocketPairTest, TCPResetDuringClose_NoRandomSave) {
}
}
+// Test setsockopt and getsockopt for a socket with SO_LINGER option.
+TEST_P(TCPSocketPairTest, SetAndGetLingerOption) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ // Check getsockopt before SO_LINGER option is set.
+ struct linger got_linger = {-1, -1};
+ socklen_t got_len = sizeof(got_linger);
+
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER,
+ &got_linger, &got_len),
+ SyscallSucceeds());
+ ASSERT_THAT(got_len, sizeof(got_linger));
+ struct linger want_linger = {};
+ EXPECT_EQ(0, memcmp(&want_linger, &got_linger, got_len));
+
+ // Set and get SO_LINGER with negative values.
+ struct linger sl;
+ sl.l_onoff = 1;
+ sl.l_linger = -3;
+ ASSERT_THAT(
+ setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)),
+ SyscallSucceeds());
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER,
+ &got_linger, &got_len),
+ SyscallSucceeds());
+ ASSERT_EQ(got_len, sizeof(got_linger));
+ EXPECT_EQ(sl.l_onoff, got_linger.l_onoff);
+ // Linux returns a different value as it uses HZ to convert the seconds to
+ // jiffies which overflows for negative values. We want to be compatible with
+ // linux for getsockopt return value.
+ if (IsRunningOnGvisor()) {
+ EXPECT_EQ(sl.l_linger, got_linger.l_linger);
+ }
+
+ // Set and get SO_LINGER option with positive values.
+ sl.l_onoff = 1;
+ sl.l_linger = 5;
+ ASSERT_THAT(
+ setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)),
+ SyscallSucceeds());
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER,
+ &got_linger, &got_len),
+ SyscallSucceeds());
+ ASSERT_EQ(got_len, sizeof(got_linger));
+ EXPECT_EQ(0, memcmp(&sl, &got_linger, got_len));
+}
+
+// Test socket to disable SO_LINGER option.
+TEST_P(TCPSocketPairTest, SetOffLingerOption) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ // Set the SO_LINGER option.
+ struct linger sl;
+ sl.l_onoff = 1;
+ sl.l_linger = 5;
+ ASSERT_THAT(
+ setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)),
+ SyscallSucceeds());
+
+ // Check getsockopt after SO_LINGER option is set.
+ struct linger got_linger = {-1, -1};
+ socklen_t got_len = sizeof(got_linger);
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER,
+ &got_linger, &got_len),
+ SyscallSucceeds());
+ ASSERT_EQ(got_len, sizeof(got_linger));
+ EXPECT_EQ(0, memcmp(&sl, &got_linger, got_len));
+
+ sl.l_onoff = 0;
+ sl.l_linger = 5;
+ ASSERT_THAT(
+ setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)),
+ SyscallSucceeds());
+
+ // Check getsockopt after SO_LINGER option is set to zero.
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER,
+ &got_linger, &got_len),
+ SyscallSucceeds());
+ ASSERT_EQ(got_len, sizeof(got_linger));
+ EXPECT_EQ(0, memcmp(&sl, &got_linger, got_len));
+}
+
+// Test close on dup'd socket with SO_LINGER option set.
+TEST_P(TCPSocketPairTest, CloseWithLingerOption) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ // Set the SO_LINGER option.
+ struct linger sl;
+ sl.l_onoff = 1;
+ sl.l_linger = 5;
+ ASSERT_THAT(
+ setsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER, &sl, sizeof(sl)),
+ SyscallSucceeds());
+
+ // Check getsockopt after SO_LINGER option is set.
+ struct linger got_linger = {-1, -1};
+ socklen_t got_len = sizeof(got_linger);
+ ASSERT_THAT(getsockopt(sockets->first_fd(), SOL_SOCKET, SO_LINGER,
+ &got_linger, &got_len),
+ SyscallSucceeds());
+ ASSERT_EQ(got_len, sizeof(got_linger));
+ EXPECT_EQ(0, memcmp(&sl, &got_linger, got_len));
+
+ FileDescriptor dupFd = FileDescriptor(dup(sockets->first_fd()));
+ ASSERT_THAT(close(sockets->release_first_fd()), SyscallSucceeds());
+ char buf[10] = {};
+ // Write on dupFd should succeed as socket will not be closed until
+ // all references are removed.
+ ASSERT_THAT(RetryEINTR(write)(dupFd.get(), buf, sizeof(buf)),
+ SyscallSucceedsWithValue(sizeof(buf)));
+ ASSERT_THAT(RetryEINTR(write)(sockets->first_fd(), buf, sizeof(buf)),
+ SyscallFailsWithErrno(EBADF));
+
+ // Close the socket.
+ dupFd.reset();
+ // Write on dupFd should fail as all references for socket are removed.
+ ASSERT_THAT(RetryEINTR(write)(dupFd.get(), buf, sizeof(buf)),
+ SyscallFailsWithErrno(EBADF));
+}
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc
index edb86aded..6e4ecd680 100644
--- a/test/syscalls/linux/socket_ip_udp_generic.cc
+++ b/test/syscalls/linux/socket_ip_udp_generic.cc
@@ -435,8 +435,10 @@ TEST_P(UDPSocketPairTest, TOSRecvMismatch) {
// Test that an IPv4 socket does not support the IPv6 TClass option.
TEST_P(UDPSocketPairTest, TClassRecvMismatch) {
- // This should only test AF_INET sockets for the mismatch behavior.
- SKIP_IF(GetParam().domain != AF_INET);
+ // This should only test AF_INET6 sockets for the mismatch behavior.
+ SKIP_IF(GetParam().domain != AF_INET6);
+ // IPV6_RECVTCLASS is only valid for SOCK_DGRAM and SOCK_RAW.
+ SKIP_IF(GetParam().type != SOCK_DGRAM | GetParam().type != SOCK_RAW);
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
@@ -448,5 +450,35 @@ TEST_P(UDPSocketPairTest, TClassRecvMismatch) {
SyscallFailsWithErrno(EOPNOTSUPP));
}
+// Test the SO_LINGER option can be set/get on udp socket.
+TEST_P(UDPSocketPairTest, SoLingerFail) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+ int level = SOL_SOCKET;
+ int type = SO_LINGER;
+
+ struct linger sl;
+ sl.l_onoff = 1;
+ sl.l_linger = 5;
+ ASSERT_THAT(setsockopt(sockets->first_fd(), level, type, &sl, sizeof(sl)),
+ SyscallSucceedsWithValue(0));
+
+ struct linger got_linger = {};
+ socklen_t length = sizeof(sl);
+ ASSERT_THAT(
+ getsockopt(sockets->first_fd(), level, type, &got_linger, &length),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_EQ(length, sizeof(got_linger));
+ // Linux returns the values which are set in the SetSockOpt for SO_LINGER.
+ // In gVisor, we do not store the linger values for UDP as SO_LINGER for UDP
+ // is a no-op.
+ if (IsRunningOnGvisor()) {
+ struct linger want_linger = {};
+ EXPECT_EQ(0, memcmp(&want_linger, &got_linger, length));
+ } else {
+ EXPECT_EQ(0, memcmp(&sl, &got_linger, length));
+ }
+}
+
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_udp_unbound_netlink_util.cc b/test/syscalls/linux/socket_ip_udp_unbound_netlink_util.cc
new file mode 100644
index 000000000..13ffafde7
--- /dev/null
+++ b/test/syscalls/linux/socket_ip_udp_unbound_netlink_util.cc
@@ -0,0 +1,58 @@
+// 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.
+
+#include "test/syscalls/linux/socket_ip_udp_unbound_netlink_util.h"
+
+namespace gvisor {
+namespace testing {
+
+const size_t kSendBufSize = 200;
+
+void IPUDPUnboundSocketNetlinkTest::TestSendRecv(TestAddress sender_addr,
+ TestAddress receiver_addr) {
+ auto snd_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+ auto rcv_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+
+ EXPECT_THAT(
+ bind(snd_sock->get(), reinterpret_cast<sockaddr*>(&sender_addr.addr),
+ sender_addr.addr_len),
+ SyscallSucceeds());
+
+ EXPECT_THAT(
+ bind(rcv_sock->get(), reinterpret_cast<sockaddr*>(&receiver_addr.addr),
+ receiver_addr.addr_len),
+ SyscallSucceeds());
+ socklen_t receiver_addr_len = receiver_addr.addr_len;
+ ASSERT_THAT(getsockname(rcv_sock->get(),
+ reinterpret_cast<sockaddr*>(&receiver_addr.addr),
+ &receiver_addr_len),
+ SyscallSucceeds());
+ EXPECT_EQ(receiver_addr_len, receiver_addr.addr_len);
+ char send_buf[kSendBufSize];
+ RandomizeBuffer(send_buf, kSendBufSize);
+ EXPECT_THAT(
+ RetryEINTR(sendto)(snd_sock->get(), send_buf, kSendBufSize, 0,
+ reinterpret_cast<sockaddr*>(&receiver_addr.addr),
+ receiver_addr.addr_len),
+ SyscallSucceedsWithValue(kSendBufSize));
+
+ // Check that we received the packet.
+ char recv_buf[kSendBufSize] = {};
+ ASSERT_THAT(RetryEINTR(recv)(rcv_sock->get(), recv_buf, kSendBufSize, 0),
+ SyscallSucceedsWithValue(kSendBufSize));
+ EXPECT_EQ(0, memcmp(send_buf, recv_buf, kSendBufSize));
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_udp_unbound_netlink_util.h b/test/syscalls/linux/socket_ip_udp_unbound_netlink_util.h
new file mode 100644
index 000000000..157fb0939
--- /dev/null
+++ b/test/syscalls/linux/socket_ip_udp_unbound_netlink_util.h
@@ -0,0 +1,34 @@
+// 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.
+
+#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_NETLINK_UTIL_H_
+#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_NETLINK_UTIL_H_
+
+#include "test/syscalls/linux/socket_test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// Test fixture for tests that apply to IP UDP sockets.
+class IPUDPUnboundSocketNetlinkTest : public SimpleSocketTest {
+ public:
+ // TestSendRecv tests sending and receiving a UDP packet from |sender_addr| to
+ // |receiver_addr|.
+ void TestSendRecv(TestAddress sender_addr, TestAddress receiver_addr);
+};
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IP_UDP_UNBOUND_NETLINK_UTIL_H_
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_netlink.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_netlink.cc
new file mode 100644
index 000000000..8052bf404
--- /dev/null
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound_loopback_netlink.cc
@@ -0,0 +1,32 @@
+// 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.
+
+#include <vector>
+
+#include "test/syscalls/linux/ip_socket_test_util.h"
+#include "test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+INSTANTIATE_TEST_SUITE_P(
+ IPv4UDPSockets, IPv4UDPUnboundSocketNetlinkTest,
+ ::testing::ValuesIn(ApplyVec<SocketKind>(IPv4UDPUnboundSocket,
+ AllBitwiseCombinations(List<int>{
+ 0, SOCK_NONBLOCK}))));
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc
new file mode 100644
index 000000000..696fbb189
--- /dev/null
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc
@@ -0,0 +1,60 @@
+// 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.
+
+#include "test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h"
+
+#include <arpa/inet.h>
+
+#include "gtest/gtest.h"
+#include "test/syscalls/linux/socket_netlink_route_util.h"
+#include "test/util/capability_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// Checks that the loopback interface considers itself bound to all IPs in an
+// associated subnet.
+TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+
+ // Add an IP address to the loopback interface.
+ Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
+ struct in_addr addr;
+ EXPECT_EQ(1, inet_pton(AF_INET, "192.0.2.1", &addr));
+ EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr, sizeof(addr)));
+
+ // Send from an unassigned address but an address that is in the subnet
+ // associated with the loopback interface.
+ TestAddress sender_addr("V4NotAssignd1");
+ sender_addr.addr.ss_family = AF_INET;
+ sender_addr.addr_len = sizeof(sockaddr_in);
+ EXPECT_EQ(1, inet_pton(AF_INET, "192.0.2.2",
+ &(reinterpret_cast<sockaddr_in*>(&sender_addr.addr)
+ ->sin_addr.s_addr)));
+
+ // Send the packet to an unassigned address but an address that is in the
+ // subnet associated with the loopback interface.
+ TestAddress receiver_addr("V4NotAssigned2");
+ receiver_addr.addr.ss_family = AF_INET;
+ receiver_addr.addr_len = sizeof(sockaddr_in);
+ EXPECT_EQ(1, inet_pton(AF_INET, "192.0.2.254",
+ &(reinterpret_cast<sockaddr_in*>(&receiver_addr.addr)
+ ->sin_addr.s_addr)));
+
+ TestSendRecv(sender_addr, receiver_addr);
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h
new file mode 100644
index 000000000..fcfb3318e
--- /dev/null
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.h
@@ -0,0 +1,29 @@
+// 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.
+
+#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_NETLINK_UTIL_H_
+#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_NETLINK_UTIL_H_
+
+#include "test/syscalls/linux/socket_ip_udp_unbound_netlink_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// Test fixture for tests that apply to IPv4 UDP sockets.
+using IPv4UDPUnboundSocketNetlinkTest = IPUDPUnboundSocketNetlinkTest;
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV4_UDP_UNBOUND_NETLINK_UTIL_H_
diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_loopback_netlink.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback_netlink.cc
new file mode 100644
index 000000000..17021ff82
--- /dev/null
+++ b/test/syscalls/linux/socket_ipv6_udp_unbound_loopback_netlink.cc
@@ -0,0 +1,32 @@
+// 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.
+
+#include <vector>
+
+#include "test/syscalls/linux/ip_socket_test_util.h"
+#include "test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+INSTANTIATE_TEST_SUITE_P(
+ IPv6UDPSockets, IPv6UDPUnboundSocketNetlinkTest,
+ ::testing::ValuesIn(ApplyVec<SocketKind>(IPv6UDPUnboundSocket,
+ AllBitwiseCombinations(List<int>{
+ 0, SOCK_NONBLOCK}))));
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.cc b/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.cc
new file mode 100644
index 000000000..6275b5aed
--- /dev/null
+++ b/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.cc
@@ -0,0 +1,60 @@
+// 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.
+
+#include "test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h"
+
+#include <arpa/inet.h>
+
+#include "gtest/gtest.h"
+#include "test/syscalls/linux/socket_netlink_route_util.h"
+#include "test/util/capability_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// Checks that the loopback interface considers itself bound to all IPs in an
+// associated subnet.
+TEST_P(IPv6UDPUnboundSocketNetlinkTest, JoinSubnet) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+
+ // Add an IP address to the loopback interface.
+ Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
+ struct in6_addr addr;
+ EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::1", &addr));
+ EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET6,
+ /*prefixlen=*/64, &addr, sizeof(addr)));
+
+ // Send from an unassigned address but an address that is in the subnet
+ // associated with the loopback interface.
+ TestAddress sender_addr("V6NotAssignd1");
+ sender_addr.addr.ss_family = AF_INET6;
+ sender_addr.addr_len = sizeof(sockaddr_in6);
+ EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::2",
+ reinterpret_cast<sockaddr_in6*>(&sender_addr.addr)
+ ->sin6_addr.s6_addr));
+
+ // Send the packet to an unassigned address but an address that is in the
+ // subnet associated with the loopback interface.
+ TestAddress receiver_addr("V6NotAssigned2");
+ receiver_addr.addr.ss_family = AF_INET6;
+ receiver_addr.addr_len = sizeof(sockaddr_in6);
+ EXPECT_EQ(1, inet_pton(AF_INET6, "2001:db8::ffff:ffff:ffff:ffff",
+ reinterpret_cast<sockaddr_in6*>(&receiver_addr.addr)
+ ->sin6_addr.s6_addr));
+
+ TestSendRecv(sender_addr, receiver_addr);
+}
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h b/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h
new file mode 100644
index 000000000..6a2b0a5be
--- /dev/null
+++ b/test/syscalls/linux/socket_ipv6_udp_unbound_netlink.h
@@ -0,0 +1,29 @@
+// 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.
+
+#ifndef GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_NETLINK_UTIL_H_
+#define GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_NETLINK_UTIL_H_
+
+#include "test/syscalls/linux/socket_ip_udp_unbound_netlink_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// Test fixture for tests that apply to IPv6 UDP sockets.
+using IPv6UDPUnboundSocketNetlinkTest = IPUDPUnboundSocketNetlinkTest;
+
+} // namespace testing
+} // namespace gvisor
+
+#endif // GVISOR_TEST_SYSCALLS_LINUX_SOCKET_IPV6_UDP_UNBOUND_NETLINK_UTIL_H_
diff --git a/test/syscalls/linux/xattr.cc b/test/syscalls/linux/xattr.cc
index cbcf08451..5510a87a0 100644
--- a/test/syscalls/linux/xattr.cc
+++ b/test/syscalls/linux/xattr.cc
@@ -28,6 +28,7 @@
#include "test/syscalls/linux/file_base.h"
#include "test/util/capability_util.h"
#include "test/util/file_descriptor.h"
+#include "test/util/fs_util.h"
#include "test/util/posix_error.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
@@ -37,6 +38,8 @@ namespace testing {
namespace {
+using ::gvisor::testing::IsTmpfs;
+
class XattrTest : public FileTest {};
TEST_F(XattrTest, XattrNonexistentFile) {
@@ -604,6 +607,77 @@ TEST_F(XattrTest, XattrWithFD) {
EXPECT_THAT(fremovexattr(fd.get(), name), SyscallSucceeds());
}
+TEST_F(XattrTest, TrustedNamespaceWithCapSysAdmin) {
+ // Trusted namespace not supported in VFS1.
+ SKIP_IF(IsRunningWithVFS1());
+
+ // TODO(b/66162845): Only gVisor tmpfs currently supports trusted namespace.
+ SKIP_IF(IsRunningOnGvisor() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(test_file_name_)));
+
+ // Setting/Getting in the trusted namespace requires CAP_SYS_ADMIN.
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
+
+ const char* path = test_file_name_.c_str();
+ const char name[] = "trusted.test";
+
+ // Set.
+ char val = 'a';
+ size_t size = sizeof(val);
+ EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0), SyscallSucceeds());
+
+ // Get.
+ char got = '\0';
+ EXPECT_THAT(getxattr(path, name, &got, size), SyscallSucceedsWithValue(size));
+ EXPECT_EQ(val, got);
+
+ // List.
+ char list[sizeof(name)];
+ EXPECT_THAT(listxattr(path, list, sizeof(list)),
+ SyscallSucceedsWithValue(sizeof(name)));
+ EXPECT_STREQ(list, name);
+
+ // Remove.
+ EXPECT_THAT(removexattr(path, name), SyscallSucceeds());
+
+ // Get should now return ENODATA.
+ EXPECT_THAT(getxattr(path, name, &got, size), SyscallFailsWithErrno(ENODATA));
+}
+
+TEST_F(XattrTest, TrustedNamespaceWithoutCapSysAdmin) {
+ // Trusted namespace not supported in VFS1.
+ SKIP_IF(IsRunningWithVFS1());
+
+ // TODO(b/66162845): Only gVisor tmpfs currently supports trusted namespace.
+ SKIP_IF(IsRunningOnGvisor() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsTmpfs(test_file_name_)));
+
+ // Drop CAP_SYS_ADMIN if we have it.
+ if (ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN))) {
+ EXPECT_NO_ERRNO(SetCapability(CAP_SYS_ADMIN, false));
+ }
+
+ const char* path = test_file_name_.c_str();
+ const char name[] = "trusted.test";
+
+ // Set fails.
+ char val = 'a';
+ size_t size = sizeof(val);
+ EXPECT_THAT(setxattr(path, name, &val, size, /*flags=*/0),
+ SyscallFailsWithErrno(EPERM));
+
+ // Get fails.
+ char got = '\0';
+ EXPECT_THAT(getxattr(path, name, &got, size), SyscallFailsWithErrno(ENODATA));
+
+ // List still works, but returns no items.
+ char list[sizeof(name)];
+ EXPECT_THAT(listxattr(path, list, sizeof(list)), SyscallSucceedsWithValue(0));
+
+ // Remove fails.
+ EXPECT_THAT(removexattr(path, name), SyscallFailsWithErrno(EPERM));
+}
+
} // namespace
} // namespace testing
diff --git a/test/util/fs_util.cc b/test/util/fs_util.cc
index 5418948fe..dffa16183 100644
--- a/test/util/fs_util.cc
+++ b/test/util/fs_util.cc
@@ -15,7 +15,11 @@
#include "test/util/fs_util.h"
#include <dirent.h>
+#ifndef __fuchsia__
+#include <linux/magic.h>
+#endif // __fuchsia__
#include <sys/stat.h>
+#include <sys/statfs.h>
#include <sys/types.h>
#include <unistd.h>
@@ -629,5 +633,21 @@ PosixErrorOr<std::string> ProcessExePath(int pid) {
return ReadLink(absl::StrCat("/proc/", pid, "/exe"));
}
+#ifndef __fuchsia__
+PosixErrorOr<bool> IsTmpfs(const std::string& path) {
+ struct statfs stat;
+ if (statfs(path.c_str(), &stat)) {
+ if (errno == ENOENT) {
+ // Nothing at path, don't raise this as an error. Instead, just report no
+ // tmpfs at path.
+ return false;
+ }
+ return PosixError(errno,
+ absl::StrFormat("statfs(\"%s\", %#p)", path, &stat));
+ }
+ return stat.f_type == TMPFS_MAGIC;
+}
+#endif // __fuchsia__
+
} // namespace testing
} // namespace gvisor
diff --git a/test/util/fs_util.h b/test/util/fs_util.h
index 8cdac23a1..044190657 100644
--- a/test/util/fs_util.h
+++ b/test/util/fs_util.h
@@ -17,6 +17,7 @@
#include <dirent.h>
#include <sys/stat.h>
+#include <sys/statfs.h>
#include <sys/types.h>
#include <unistd.h>
@@ -178,6 +179,11 @@ std::string CleanPath(absl::string_view path);
// Returns the full path to the executable of the given pid or a PosixError.
PosixErrorOr<std::string> ProcessExePath(int pid);
+#ifndef __fuchsia__
+// IsTmpfs returns true if the file at path is backed by tmpfs.
+PosixErrorOr<bool> IsTmpfs(const std::string& path);
+#endif // __fucshia__
+
namespace internal {
// Not part of the public API.
std::string JoinPathImpl(std::initializer_list<absl::string_view> paths);