summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/benchmarks/base/BUILD2
-rw-r--r--test/iptables/filter_output.go17
-rw-r--r--test/iptables/nat.go15
-rw-r--r--test/packetimpact/netdevs/netdevs.go2
-rw-r--r--test/packetimpact/runner/defs.bzl3
-rw-r--r--test/packetimpact/testbench/connections.go55
-rw-r--r--test/packetimpact/testbench/dut_client.go2
-rw-r--r--test/packetimpact/testbench/layers.go40
-rw-r--r--test/packetimpact/tests/BUILD13
-rw-r--r--test/packetimpact/tests/ipv4_fragment_reassembly_test.go142
-rw-r--r--test/packetimpact/tests/ipv6_fragment_reassembly_test.go237
-rw-r--r--test/packetimpact/tests/tcp_network_unreachable_test.go4
-rw-r--r--test/root/oom_score_adj_test.go12
-rw-r--r--test/runner/defs.bzl2
-rw-r--r--test/runner/runner.go6
-rw-r--r--test/runtimes/exclude/java11.csv1
-rw-r--r--test/runtimes/exclude/nodejs12.4.0.csv41
-rw-r--r--test/runtimes/exclude/php7.3.6.csv6
-rw-r--r--test/runtimes/exclude/python3.7.3.csv2
-rw-r--r--test/runtimes/proctor/main.go28
-rw-r--r--test/runtimes/runner/lib/lib.go39
-rw-r--r--test/runtimes/runner/main.go14
-rw-r--r--test/syscalls/BUILD4
-rw-r--r--test/syscalls/linux/BUILD30
-rw-r--r--test/syscalls/linux/mknod.cc30
-rw-r--r--test/syscalls/linux/mmap.cc6
-rw-r--r--test/syscalls/linux/mount.cc38
-rw-r--r--test/syscalls/linux/packet_socket_raw.cc11
-rw-r--r--test/syscalls/linux/proc.cc409
-rw-r--r--test/syscalls/linux/proc_pid_smaps.cc2
-rw-r--r--test/syscalls/linux/ptrace.cc43
-rw-r--r--test/syscalls/linux/raw_socket_icmp.cc13
-rw-r--r--test/syscalls/linux/semaphore.cc287
-rw-r--r--test/syscalls/linux/sendfile.cc53
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc26
-rw-r--r--test/syscalls/linux/socket_ip_udp_generic.cc14
-rw-r--r--test/syscalls/linux/socket_ip_unbound.cc18
-rw-r--r--test/syscalls/linux/socket_ip_unbound_netlink.cc104
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound.cc82
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc28
-rw-r--r--test/syscalls/linux/socket_netlink_route.cc63
-rw-r--r--test/syscalls/linux/socket_netlink_route_util.cc22
-rw-r--r--test/syscalls/linux/socket_netlink_route_util.h11
-rw-r--r--test/syscalls/linux/socket_test_util.cc16
-rw-r--r--test/syscalls/linux/socket_test_util.h9
-rw-r--r--test/syscalls/linux/socket_unix_stream.cc13
-rw-r--r--test/syscalls/linux/splice.cc55
-rw-r--r--test/syscalls/linux/stat.cc34
-rw-r--r--test/syscalls/linux/tcp_socket.cc57
-rw-r--r--test/syscalls/linux/timers.cc5
-rw-r--r--test/syscalls/linux/udp_socket.cc72
-rw-r--r--test/util/BUILD4
-rw-r--r--test/util/posix_error.cc2
-rw-r--r--test/util/posix_error.h34
-rw-r--r--test/util/save_util.cc52
-rw-r--r--test/util/save_util.h20
-rw-r--r--test/util/save_util_linux.cc22
-rw-r--r--test/util/save_util_other.cc8
-rw-r--r--test/util/signal_util.h2
-rw-r--r--test/util/timer_util.h5
60 files changed, 1939 insertions, 448 deletions
diff --git a/test/benchmarks/base/BUILD b/test/benchmarks/base/BUILD
index 32c139204..7dfd4b693 100644
--- a/test/benchmarks/base/BUILD
+++ b/test/benchmarks/base/BUILD
@@ -13,7 +13,7 @@ go_library(
go_test(
name = "base_test",
- size = "large",
+ size = "enormous",
srcs = [
"size_test.go",
"startup_test.go",
diff --git a/test/iptables/filter_output.go b/test/iptables/filter_output.go
index 32bf2a992..d3e5efd4f 100644
--- a/test/iptables/filter_output.go
+++ b/test/iptables/filter_output.go
@@ -441,9 +441,20 @@ func (FilterOutputDestination) Name() string {
// ContainerAction implements TestCase.ContainerAction.
func (FilterOutputDestination) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
- rules := [][]string{
- {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"},
- {"-P", "OUTPUT", "DROP"},
+ var rules [][]string
+ if ipv6 {
+ rules = [][]string{
+ {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"},
+ // Allow solicited node multicast addresses so we can send neighbor
+ // solicitations.
+ {"-A", "OUTPUT", "-d", "ff02::1:ff00:0/104", "-j", "ACCEPT"},
+ {"-P", "OUTPUT", "DROP"},
+ }
+ } else {
+ rules = [][]string{
+ {"-A", "OUTPUT", "-d", ip.String(), "-j", "ACCEPT"},
+ {"-P", "OUTPUT", "DROP"},
+ }
}
if err := filterTableRules(ipv6, rules); err != nil {
return err
diff --git a/test/iptables/nat.go b/test/iptables/nat.go
index dd9a18339..b98d99fb8 100644
--- a/test/iptables/nat.go
+++ b/test/iptables/nat.go
@@ -577,11 +577,18 @@ func listenForRedirectedConn(ctx context.Context, ipv6 bool, originalDsts []net.
connCh := make(chan int)
errCh := make(chan error)
go func() {
- connFD, _, err := syscall.Accept(sockfd)
- if err != nil {
- errCh <- err
+ for {
+ connFD, _, err := syscall.Accept(sockfd)
+ if errors.Is(err, syscall.EINTR) {
+ continue
+ }
+ if err != nil {
+ errCh <- err
+ return
+ }
+ connCh <- connFD
+ return
}
- connCh <- connFD
}()
// Wait for accept() to return or for the context to finish.
diff --git a/test/packetimpact/netdevs/netdevs.go b/test/packetimpact/netdevs/netdevs.go
index eecfe0730..006988896 100644
--- a/test/packetimpact/netdevs/netdevs.go
+++ b/test/packetimpact/netdevs/netdevs.go
@@ -40,7 +40,7 @@ var (
deviceLine = regexp.MustCompile(`^\s*(\d+): (\w+)`)
linkLine = regexp.MustCompile(`^\s*link/\w+ ([0-9a-fA-F:]+)`)
inetLine = regexp.MustCompile(`^\s*inet ([0-9./]+)`)
- inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-Z:/]+)`)
+ inet6Line = regexp.MustCompile(`^\s*inet6 ([0-9a-fA-F:/]+)`)
)
// ParseDevices parses the output from `ip addr show` into a map from device
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index 1546d0d51..c03c2c62c 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -252,6 +252,9 @@ ALL_TESTS = [
expect_netstack_failure = True,
),
PacketimpactTestInfo(
+ name = "ipv4_fragment_reassembly",
+ ),
+ PacketimpactTestInfo(
name = "ipv6_fragment_reassembly",
),
PacketimpactTestInfo(
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
index a90046f69..8fa585804 100644
--- a/test/packetimpact/testbench/connections.go
+++ b/test/packetimpact/testbench/connections.go
@@ -839,6 +839,61 @@ func (conn *TCPIPv4) Drain(t *testing.T) {
conn.sniffer.Drain(t)
}
+// IPv4Conn maintains the state for all the layers in a IPv4 connection.
+type IPv4Conn Connection
+
+// NewIPv4Conn creates a new IPv4Conn connection with reasonable defaults.
+func NewIPv4Conn(t *testing.T, outgoingIPv4, incomingIPv4 IPv4) IPv4Conn {
+ t.Helper()
+
+ etherState, err := newEtherState(Ether{}, Ether{})
+ if err != nil {
+ t.Fatalf("can't make EtherState: %s", err)
+ }
+ ipv4State, err := newIPv4State(outgoingIPv4, incomingIPv4)
+ if err != nil {
+ t.Fatalf("can't make IPv4State: %s", err)
+ }
+
+ injector, err := NewInjector(t)
+ if err != nil {
+ t.Fatalf("can't make injector: %s", err)
+ }
+ sniffer, err := NewSniffer(t)
+ if err != nil {
+ t.Fatalf("can't make sniffer: %s", err)
+ }
+
+ return IPv4Conn{
+ layerStates: []layerState{etherState, ipv4State},
+ injector: injector,
+ sniffer: sniffer,
+ }
+}
+
+// Send sends a frame with ipv4 overriding the IPv4 layer defaults and
+// additionalLayers added after it.
+func (c *IPv4Conn) Send(t *testing.T, ipv4 IPv4, additionalLayers ...Layer) {
+ t.Helper()
+
+ (*Connection)(c).send(t, Layers{&ipv4}, additionalLayers...)
+}
+
+// Close cleans up any resources held.
+func (c *IPv4Conn) Close(t *testing.T) {
+ t.Helper()
+
+ (*Connection)(c).Close(t)
+}
+
+// ExpectFrame expects a frame that matches the provided Layers within the
+// timeout specified. If it doesn't arrive in time, an error is returned.
+func (c *IPv4Conn) ExpectFrame(t *testing.T, frame Layers, timeout time.Duration) (Layers, error) {
+ t.Helper()
+
+ return (*Connection)(c).ExpectFrame(t, frame, timeout)
+}
+
// IPv6Conn maintains the state for all the layers in a IPv6 connection.
type IPv6Conn Connection
diff --git a/test/packetimpact/testbench/dut_client.go b/test/packetimpact/testbench/dut_client.go
index d0e68c5da..0fc3d97b4 100644
--- a/test/packetimpact/testbench/dut_client.go
+++ b/test/packetimpact/testbench/dut_client.go
@@ -19,7 +19,7 @@ import (
pb "gvisor.dev/gvisor/test/packetimpact/proto/posix_server_go_proto"
)
-// PosixClient is a gRPC client for the Posix service.
+// POSIXClient is a gRPC client for the Posix service.
type POSIXClient pb.PosixClient
// NewPOSIXClient makes a new gRPC client for the POSIX service.
diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go
index a35562ca8..af7a2ba4e 100644
--- a/test/packetimpact/testbench/layers.go
+++ b/test/packetimpact/testbench/layers.go
@@ -879,6 +879,9 @@ type ICMPv4 struct {
Type *header.ICMPv4Type
Code *header.ICMPv4Code
Checksum *uint16
+ Ident *uint16
+ Sequence *uint16
+ Payload []byte
}
func (l *ICMPv4) String() string {
@@ -887,7 +890,7 @@ func (l *ICMPv4) String() string {
// ToBytes implements Layer.ToBytes.
func (l *ICMPv4) ToBytes() ([]byte, error) {
- b := make([]byte, header.ICMPv4MinimumSize)
+ b := make([]byte, header.ICMPv4MinimumSize+len(l.Payload))
h := header.ICMPv4(b)
if l.Type != nil {
h.SetType(*l.Type)
@@ -895,15 +898,33 @@ func (l *ICMPv4) ToBytes() ([]byte, error) {
if l.Code != nil {
h.SetCode(*l.Code)
}
+ if copied := copy(h.Payload(), l.Payload); copied != len(l.Payload) {
+ panic(fmt.Sprintf("wrong number of bytes copied into h.Payload(): got = %d, want = %d", len(h.Payload()), len(l.Payload)))
+ }
+ if l.Ident != nil {
+ h.SetIdent(*l.Ident)
+ }
+ if l.Sequence != nil {
+ h.SetSequence(*l.Sequence)
+ }
+
+ // The checksum must be handled last because the ICMPv4 header fields are
+ // included in the computation.
if l.Checksum != nil {
h.SetChecksum(*l.Checksum)
- return h, nil
- }
- payload, err := payload(l)
- if err != nil {
- return nil, err
+ } else {
+ // Compute the checksum based on the ICMPv4.Payload and also the subsequent
+ // layers.
+ payload, err := payload(l)
+ if err != nil {
+ return nil, err
+ }
+ var vv buffer.VectorisedView
+ vv.AppendView(buffer.View(l.Payload))
+ vv.Append(payload)
+ h.SetChecksum(header.ICMPv4Checksum(h, vv))
}
- h.SetChecksum(header.ICMPv4Checksum(h, payload))
+
return h, nil
}
@@ -915,8 +936,11 @@ func parseICMPv4(b []byte) (Layer, layerParser) {
Type: ICMPv4Type(h.Type()),
Code: ICMPv4Code(h.Code()),
Checksum: Uint16(h.Checksum()),
+ Ident: Uint16(h.Ident()),
+ Sequence: Uint16(h.Sequence()),
+ Payload: h.Payload(),
}
- return &icmpv4, parsePayload
+ return &icmpv4, nil
}
func (l *ICMPv4) match(other Layer) bool {
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index 8c2de5a9f..c30c77a17 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -298,6 +298,18 @@ packetimpact_testbench(
)
packetimpact_testbench(
+ name = "ipv4_fragment_reassembly",
+ srcs = ["ipv4_fragment_reassembly_test.go"],
+ deps = [
+ "//pkg/tcpip/buffer",
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@com_github_google_go_cmp//cmp:go_default_library",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_testbench(
name = "ipv6_fragment_reassembly",
srcs = ["ipv6_fragment_reassembly_test.go"],
deps = [
@@ -305,6 +317,7 @@ packetimpact_testbench(
"//pkg/tcpip/buffer",
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
+ "@com_github_google_go_cmp//cmp:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
],
)
diff --git a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go
new file mode 100644
index 000000000..65c0df140
--- /dev/null
+++ b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go
@@ -0,0 +1,142 @@
+// 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 ipv4_fragment_reassembly_test
+
+import (
+ "flag"
+ "math/rand"
+ "testing"
+ "time"
+
+ "github.com/google/go-cmp/cmp"
+ "gvisor.dev/gvisor/pkg/tcpip/buffer"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.RegisterFlags(flag.CommandLine)
+}
+
+type fragmentInfo struct {
+ offset uint16
+ size uint16
+ more uint8
+}
+
+func TestIPv4FragmentReassembly(t *testing.T) {
+ const fragmentID = 42
+ icmpv4ProtoNum := uint8(header.ICMPv4ProtocolNumber)
+
+ tests := []struct {
+ description string
+ ipPayloadLen int
+ fragments []fragmentInfo
+ expectReply bool
+ }{
+ {
+ description: "basic reassembly",
+ ipPayloadLen: 2000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1000, more: header.IPv4FlagMoreFragments},
+ {offset: 1000, size: 1000, more: 0},
+ },
+ expectReply: true,
+ },
+ {
+ description: "out of order fragments",
+ ipPayloadLen: 2000,
+ fragments: []fragmentInfo{
+ {offset: 1000, size: 1000, more: 0},
+ {offset: 0, size: 1000, more: header.IPv4FlagMoreFragments},
+ },
+ expectReply: true,
+ },
+ }
+
+ for _, test := range tests {
+ t.Run(test.description, func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ conn := testbench.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{})
+ defer conn.Close(t)
+
+ data := make([]byte, test.ipPayloadLen)
+ icmp := header.ICMPv4(data[:header.ICMPv4MinimumSize])
+ icmp.SetType(header.ICMPv4Echo)
+ icmp.SetCode(header.ICMPv4UnusedCode)
+ icmp.SetChecksum(0)
+ icmp.SetSequence(0)
+ icmp.SetIdent(0)
+ originalPayload := data[header.ICMPv4MinimumSize:]
+ if _, err := rand.Read(originalPayload); err != nil {
+ t.Fatalf("rand.Read: %s", err)
+ }
+ cksum := header.ICMPv4Checksum(
+ icmp,
+ buffer.NewVectorisedView(len(originalPayload), []buffer.View{originalPayload}),
+ )
+ icmp.SetChecksum(cksum)
+
+ for _, fragment := range test.fragments {
+ conn.Send(t,
+ testbench.IPv4{
+ Protocol: &icmpv4ProtoNum,
+ FragmentOffset: testbench.Uint16(fragment.offset),
+ Flags: testbench.Uint8(fragment.more),
+ ID: testbench.Uint16(fragmentID),
+ },
+ &testbench.Payload{
+ Bytes: data[fragment.offset:][:fragment.size],
+ })
+ }
+
+ var bytesReceived int
+ reassembledPayload := make([]byte, test.ipPayloadLen)
+ for {
+ incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv4{},
+ &testbench.ICMPv4{},
+ }, time.Second)
+ if err != nil {
+ // Either an unexpected frame was received, or none at all.
+ if bytesReceived < test.ipPayloadLen {
+ t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err)
+ }
+ break
+ }
+ if !test.expectReply {
+ t.Fatalf("unexpected reply received:\n%s", incomingFrame)
+ }
+ ipPayload, err := incomingFrame[2 /* ICMPv4 */].ToBytes()
+ if err != nil {
+ t.Fatalf("failed to parse ICMPv4 header: incomingPacket[2].ToBytes() = (_, %s)", err)
+ }
+ offset := *incomingFrame[1 /* IPv4 */].(*testbench.IPv4).FragmentOffset
+ if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) {
+ t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload))
+ }
+ bytesReceived += len(ipPayload)
+ }
+
+ if test.expectReply {
+ if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv4MinimumSize:]); diff != "" {
+ t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff)
+ }
+ }
+ })
+ }
+}
diff --git a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
index a24c85566..4a29de688 100644
--- a/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
+++ b/test/packetimpact/tests/ipv6_fragment_reassembly_test.go
@@ -15,154 +15,137 @@
package ipv6_fragment_reassembly_test
import (
- "bytes"
- "encoding/binary"
- "encoding/hex"
"flag"
+ "math/rand"
"net"
"testing"
"time"
+ "github.com/google/go-cmp/cmp"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/buffer"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
-const (
- // The payload length for the first fragment we send. This number
- // is a multiple of 8 near 750 (half of 1500).
- firstPayloadLength = 752
- // The ID field for our outgoing fragments.
- fragmentID = 1
- // A node must be able to accept a fragmented packet that,
- // after reassembly, is as large as 1500 octets.
- reassemblyCap = 1500
-)
-
func init() {
testbench.RegisterFlags(flag.CommandLine)
}
-func TestIPv6FragmentReassembly(t *testing.T) {
- dut := testbench.NewDUT(t)
- defer dut.TearDown()
- conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
- defer conn.Close(t)
-
- firstPayloadToSend := make([]byte, firstPayloadLength)
- for i := range firstPayloadToSend {
- firstPayloadToSend[i] = 'A'
- }
-
- secondPayloadLength := reassemblyCap - firstPayloadLength - header.ICMPv6EchoMinimumSize
- secondPayloadToSend := firstPayloadToSend[:secondPayloadLength]
-
- icmpv6EchoPayload := make([]byte, 4)
- binary.BigEndian.PutUint16(icmpv6EchoPayload[0:], 0)
- binary.BigEndian.PutUint16(icmpv6EchoPayload[2:], 0)
- icmpv6EchoPayload = append(icmpv6EchoPayload, firstPayloadToSend...)
-
- lIP := tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16())
- rIP := tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16())
- icmpv6 := testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
- Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode),
- Payload: icmpv6EchoPayload,
- }
- icmpv6Bytes, err := icmpv6.ToBytes()
- if err != nil {
- t.Fatalf("failed to serialize ICMPv6: %s", err)
- }
- cksum := header.ICMPv6Checksum(
- header.ICMPv6(icmpv6Bytes),
- lIP,
- rIP,
- buffer.NewVectorisedView(len(secondPayloadToSend), []buffer.View{secondPayloadToSend}),
- )
-
- conn.Send(t, testbench.IPv6{},
- &testbench.IPv6FragmentExtHdr{
- FragmentOffset: testbench.Uint16(0),
- MoreFragments: testbench.Bool(true),
- Identification: testbench.Uint32(fragmentID),
- },
- &testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6EchoRequest),
- Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode),
- Payload: icmpv6EchoPayload,
- Checksum: &cksum,
- })
+type fragmentInfo struct {
+ offset uint16
+ size uint16
+ more bool
+}
+func TestIPv6FragmentReassembly(t *testing.T) {
+ const fragmentID = 42
icmpv6ProtoNum := header.IPv6ExtensionHeaderIdentifier(header.ICMPv6ProtocolNumber)
- conn.Send(t, testbench.IPv6{},
- &testbench.IPv6FragmentExtHdr{
- NextHeader: &icmpv6ProtoNum,
- FragmentOffset: testbench.Uint16((firstPayloadLength + header.ICMPv6EchoMinimumSize) / 8),
- MoreFragments: testbench.Bool(false),
- Identification: testbench.Uint32(fragmentID),
+ tests := []struct {
+ description string
+ ipPayloadLen int
+ fragments []fragmentInfo
+ expectReply bool
+ }{
+ {
+ description: "basic reassembly",
+ ipPayloadLen: 1500,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 760, more: true},
+ {offset: 760, size: 740, more: false},
+ },
+ expectReply: true,
},
- &testbench.Payload{
- Bytes: secondPayloadToSend,
- })
-
- gotEchoReplyFirstPart, err := conn.ExpectFrame(t, testbench.Layers{
- &testbench.Ether{},
- &testbench.IPv6{},
- &testbench.IPv6FragmentExtHdr{
- FragmentOffset: testbench.Uint16(0),
- MoreFragments: testbench.Bool(true),
+ {
+ description: "out of order fragments",
+ ipPayloadLen: 3000,
+ fragments: []fragmentInfo{
+ {offset: 0, size: 1024, more: true},
+ {offset: 2048, size: 952, more: false},
+ {offset: 1024, size: 1024, more: true},
+ },
+ expectReply: true,
},
- &testbench.ICMPv6{
- Type: testbench.ICMPv6Type(header.ICMPv6EchoReply),
- Code: testbench.ICMPv6Code(header.ICMPv6UnusedCode),
- },
- }, time.Second)
- if err != nil {
- t.Fatalf("expected a fragmented ICMPv6 Echo Reply, but got none: %s", err)
}
- id := *gotEchoReplyFirstPart[2].(*testbench.IPv6FragmentExtHdr).Identification
- gotFirstPayload, err := gotEchoReplyFirstPart[len(gotEchoReplyFirstPart)-1].ToBytes()
- if err != nil {
- t.Fatalf("failed to serialize ICMPv6: %s", err)
- }
- icmpPayload := gotFirstPayload[header.ICMPv6EchoMinimumSize:]
- receivedLen := len(icmpPayload)
- wantSecondPayloadLen := reassemblyCap - header.ICMPv6EchoMinimumSize - receivedLen
- wantFirstPayload := make([]byte, receivedLen)
- for i := range wantFirstPayload {
- wantFirstPayload[i] = 'A'
- }
- wantSecondPayload := wantFirstPayload[:wantSecondPayloadLen]
- if !bytes.Equal(icmpPayload, wantFirstPayload) {
- t.Fatalf("received unexpected payload, got: %s, want: %s",
- hex.Dump(icmpPayload),
- hex.Dump(wantFirstPayload))
- }
-
- gotEchoReplySecondPart, err := conn.ExpectFrame(t, testbench.Layers{
- &testbench.Ether{},
- &testbench.IPv6{},
- &testbench.IPv6FragmentExtHdr{
- NextHeader: &icmpv6ProtoNum,
- FragmentOffset: testbench.Uint16(uint16((receivedLen + header.ICMPv6EchoMinimumSize) / 8)),
- MoreFragments: testbench.Bool(false),
- Identification: &id,
- },
- &testbench.ICMPv6{},
- }, time.Second)
- if err != nil {
- t.Fatalf("expected the rest of ICMPv6 Echo Reply, but got none: %s", err)
- }
- secondPayload, err := gotEchoReplySecondPart[len(gotEchoReplySecondPart)-1].ToBytes()
- if err != nil {
- t.Fatalf("failed to serialize ICMPv6 Echo Reply: %s", err)
- }
- if !bytes.Equal(secondPayload, wantSecondPayload) {
- t.Fatalf("received unexpected payload, got: %s, want: %s",
- hex.Dump(secondPayload),
- hex.Dump(wantSecondPayload))
+ for _, test := range tests {
+ t.Run(test.description, func(t *testing.T) {
+ dut := testbench.NewDUT(t)
+ defer dut.TearDown()
+ conn := testbench.NewIPv6Conn(t, testbench.IPv6{}, testbench.IPv6{})
+ defer conn.Close(t)
+
+ lIP := tcpip.Address(net.ParseIP(testbench.LocalIPv6).To16())
+ rIP := tcpip.Address(net.ParseIP(testbench.RemoteIPv6).To16())
+
+ data := make([]byte, test.ipPayloadLen)
+ icmp := header.ICMPv6(data[:header.ICMPv6HeaderSize])
+ icmp.SetType(header.ICMPv6EchoRequest)
+ icmp.SetCode(header.ICMPv6UnusedCode)
+ icmp.SetChecksum(0)
+ originalPayload := data[header.ICMPv6HeaderSize:]
+ if _, err := rand.Read(originalPayload); err != nil {
+ t.Fatalf("rand.Read: %s", err)
+ }
+
+ cksum := header.ICMPv6Checksum(
+ icmp,
+ lIP,
+ rIP,
+ buffer.NewVectorisedView(len(originalPayload), []buffer.View{originalPayload}),
+ )
+ icmp.SetChecksum(cksum)
+
+ for _, fragment := range test.fragments {
+ conn.Send(t, testbench.IPv6{},
+ &testbench.IPv6FragmentExtHdr{
+ NextHeader: &icmpv6ProtoNum,
+ FragmentOffset: testbench.Uint16(fragment.offset / header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit),
+ MoreFragments: testbench.Bool(fragment.more),
+ Identification: testbench.Uint32(fragmentID),
+ },
+ &testbench.Payload{
+ Bytes: data[fragment.offset:][:fragment.size],
+ })
+ }
+
+ var bytesReceived int
+ reassembledPayload := make([]byte, test.ipPayloadLen)
+ for {
+ incomingFrame, err := conn.ExpectFrame(t, testbench.Layers{
+ &testbench.Ether{},
+ &testbench.IPv6{},
+ &testbench.IPv6FragmentExtHdr{},
+ &testbench.ICMPv6{},
+ }, time.Second)
+ if err != nil {
+ // Either an unexpected frame was received, or none at all.
+ if bytesReceived < test.ipPayloadLen {
+ t.Fatalf("received %d bytes out of %d, then conn.ExpectFrame(_, _, time.Second) failed with %s", bytesReceived, test.ipPayloadLen, err)
+ }
+ break
+ }
+ if !test.expectReply {
+ t.Fatalf("unexpected reply received:\n%s", incomingFrame)
+ }
+ ipPayload, err := incomingFrame[3 /* ICMPv6 */].ToBytes()
+ if err != nil {
+ t.Fatalf("failed to parse ICMPv6 header: incomingPacket[3].ToBytes() = (_, %s)", err)
+ }
+ offset := *incomingFrame[2 /* IPv6FragmentExtHdr */].(*testbench.IPv6FragmentExtHdr).FragmentOffset
+ offset *= header.IPv6FragmentExtHdrFragmentOffsetBytesPerUnit
+ if copied := copy(reassembledPayload[offset:], ipPayload); copied != len(ipPayload) {
+ t.Fatalf("wrong number of bytes copied into reassembledPayload: got = %d, want = %d", copied, len(ipPayload))
+ }
+ bytesReceived += len(ipPayload)
+ }
+
+ if test.expectReply {
+ if diff := cmp.Diff(originalPayload, reassembledPayload[header.ICMPv6HeaderSize:]); diff != "" {
+ t.Fatalf("reassembledPayload mismatch (-want +got):\n%s", diff)
+ }
+ }
+ })
}
}
diff --git a/test/packetimpact/tests/tcp_network_unreachable_test.go b/test/packetimpact/tests/tcp_network_unreachable_test.go
index 2f57dff19..8a1fe1279 100644
--- a/test/packetimpact/tests/tcp_network_unreachable_test.go
+++ b/test/packetimpact/tests/tcp_network_unreachable_test.go
@@ -74,7 +74,9 @@ func TestTCPSynSentUnreachable(t *testing.T) {
}
var icmpv4 testbench.ICMPv4 = testbench.ICMPv4{
Type: testbench.ICMPv4Type(header.ICMPv4DstUnreachable),
- Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable)}
+ Code: testbench.ICMPv4Code(header.ICMPv4HostUnreachable),
+ }
+
layers = append(layers, &icmpv4, ip, tcp)
rawConn.SendFrameStateless(t, layers)
diff --git a/test/root/oom_score_adj_test.go b/test/root/oom_score_adj_test.go
index 4243eb59e..0dcc0fdea 100644
--- a/test/root/oom_score_adj_test.go
+++ b/test/root/oom_score_adj_test.go
@@ -40,11 +40,7 @@ var (
// TestOOMScoreAdjSingle tests that oom_score_adj is set properly in a
// single container sandbox.
func TestOOMScoreAdjSingle(t *testing.T) {
- ppid, err := specutils.GetParentPid(os.Getpid())
- if err != nil {
- t.Fatalf("getting parent pid: %v", err)
- }
- parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(ppid)
+ parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(os.Getppid())
if err != nil {
t.Fatalf("getting parent oom_score_adj: %v", err)
}
@@ -122,11 +118,7 @@ func TestOOMScoreAdjSingle(t *testing.T) {
// TestOOMScoreAdjMulti tests that oom_score_adj is set properly in a
// multi-container sandbox.
func TestOOMScoreAdjMulti(t *testing.T) {
- ppid, err := specutils.GetParentPid(os.Getpid())
- if err != nil {
- t.Fatalf("getting parent pid: %v", err)
- }
- parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(ppid)
+ parentOOMScoreAdj, err := specutils.GetOOMScoreAdj(os.Getppid())
if err != nil {
t.Fatalf("getting parent oom_score_adj: %v", err)
}
diff --git a/test/runner/defs.bzl b/test/runner/defs.bzl
index 9b5994d59..4992147d4 100644
--- a/test/runner/defs.bzl
+++ b/test/runner/defs.bzl
@@ -97,10 +97,10 @@ def _syscall_test(
# we figure out how to request ipv4 sockets on Guitar machines.
if network == "host":
tags.append("noguitar")
- tags.append("block-network")
# Disable off-host networking.
tags.append("requires-net:loopback")
+ tags.append("block-network")
# gotsan makes sense only if tests are running in gVisor.
if platform == "native":
diff --git a/test/runner/runner.go b/test/runner/runner.go
index 22d535f8d..7ab2c3edf 100644
--- a/test/runner/runner.go
+++ b/test/runner/runner.go
@@ -53,6 +53,9 @@ var (
runscPath = flag.String("runsc", "", "path to runsc binary")
addUDSTree = flag.Bool("add-uds-tree", false, "expose a tree of UDS utilities for use in tests")
+ // TODO(gvisor.dev/issue/4572): properly support leak checking for runsc, and
+ // set to true as the default for the test runner.
+ leakCheck = flag.Bool("leak-check", false, "check for reference leaks")
)
// runTestCaseNative runs the test case directly on the host machine.
@@ -174,6 +177,9 @@ func runRunsc(tc gtest.TestCase, spec *specs.Spec) error {
if *addUDSTree {
args = append(args, "-fsgofer-host-uds")
}
+ if *leakCheck {
+ args = append(args, "-ref-leak-mode=log-names")
+ }
testLogDir := ""
if undeclaredOutputsDir, ok := syscall.Getenv("TEST_UNDECLARED_OUTPUTS_DIR"); ok {
diff --git a/test/runtimes/exclude/java11.csv b/test/runtimes/exclude/java11.csv
index d978baca7..e41441374 100644
--- a/test/runtimes/exclude/java11.csv
+++ b/test/runtimes/exclude/java11.csv
@@ -144,6 +144,7 @@ jdk/jfr/cmd/TestSplit.java,,java.lang.RuntimeException: 'Missing file' missing f
jdk/jfr/cmd/TestSummary.java,,java.lang.RuntimeException: 'Missing file' missing from stdout/stderr
jdk/jfr/event/compiler/TestCompilerStats.java,,java.lang.RuntimeException: Field nmetodsSize not in event
jdk/jfr/event/metadata/TestDefaultConfigurations.java,,Setting 'threshold' in event 'jdk.SecurityPropertyModification' was not configured in the configuration 'default'
+jdk/jfr/event/oldobject/TestLargeRootSet.java,,Flaky - `main' threw exception: java.lang.RuntimeException: Could not find root object
jdk/jfr/event/runtime/TestActiveSettingEvent.java,,java.lang.Exception: Could not find setting with name jdk.X509Validation#threshold
jdk/jfr/event/runtime/TestModuleEvents.java,,java.lang.RuntimeException: assertEquals: expected jdk.proxy1 to equal java.base
jdk/jfr/event/runtime/TestNetworkUtilizationEvent.java,,
diff --git a/test/runtimes/exclude/nodejs12.4.0.csv b/test/runtimes/exclude/nodejs12.4.0.csv
index ba993814f..c4e7917ec 100644
--- a/test/runtimes/exclude/nodejs12.4.0.csv
+++ b/test/runtimes/exclude/nodejs12.4.0.csv
@@ -1,31 +1,22 @@
test name,bug id,comment
async-hooks/test-statwatcher.js,https://github.com/nodejs/node/issues/21425,Check for fix inclusion in nodejs releases after 2020-03-29
-benchmark/test-benchmark-fs.js,,
-benchmark/test-benchmark-napi.js,,
+benchmark/test-benchmark-fs.js,,Broken test
+benchmark/test-benchmark-napi.js,,Broken test
doctool/test-make-doc.js,b/68848110,Expected to fail.
internet/test-dgram-multicast-set-interface-lo.js,b/162798882,
-internet/test-doctool-versions.js,,
-internet/test-uv-threadpool-schedule.js,,
-parallel/test-cluster-dgram-reuse.js,b/64024294,
+internet/test-doctool-versions.js,,Broken test
+internet/test-uv-threadpool-schedule.js,,Broken test
parallel/test-dgram-bind-fd.js,b/132447356,
parallel/test-dgram-socket-buffer-size.js,b/68847921,
parallel/test-dns-channel-timeout.js,b/161893056,
-parallel/test-fs-access.js,,
-parallel/test-fs-watchfile.js,,Flaky - File already exists error
-parallel/test-fs-write-stream.js,b/166819807,Flaky
-parallel/test-fs-write-stream-double-close.js,b/166819807,Flaky
-parallel/test-fs-write-stream-throw-type-error.js,b/166819807,Flaky
-parallel/test-http-writable-true-after-close.js,,Flaky - Mismatched <anonymous> function calls. Expected exactly 1 actual 2
+parallel/test-fs-access.js,,Broken test
+parallel/test-fs-watchfile.js,b/166819807,Flaky - VFS1 only
+parallel/test-fs-write-stream.js,b/166819807,Flaky - VFS1 only
+parallel/test-fs-write-stream-double-close.js,b/166819807,Flaky - VFS1 only
+parallel/test-fs-write-stream-throw-type-error.js,b/166819807,Flaky - VFS1 only
+parallel/test-http-writable-true-after-close.js,b/171301436,Flaky - Mismatched <anonymous> function calls. Expected exactly 1 actual 2
parallel/test-os.js,b/63997097,
-parallel/test-net-server-listen-options.js,,Flaky - EADDRINUSE
-parallel/test-process-uid-gid.js,,
-parallel/test-tls-cli-min-version-1.0.js,,Flaky - EADDRINUSE
-parallel/test-tls-cli-min-version-1.1.js,,Flaky - EADDRINUSE
-parallel/test-tls-cli-min-version-1.2.js,,Flaky - EADDRINUSE
-parallel/test-tls-cli-min-version-1.3.js,,Flaky - EADDRINUSE
-parallel/test-tls-cli-max-version-1.2.js,,Flaky - EADDRINUSE
-parallel/test-tls-cli-max-version-1.3.js,,Flaky - EADDRINUSE
-parallel/test-tls-min-max-version.js,,Flaky - EADDRINUSE
+parallel/test-process-uid-gid.js,,Does not work inside Docker with gid nobody
pseudo-tty/test-assert-colors.js,b/162801321,
pseudo-tty/test-assert-no-color.js,b/162801321,
pseudo-tty/test-assert-position-indicator.js,b/162801321,
@@ -48,11 +39,7 @@ pseudo-tty/test-tty-stdout-resize.js,b/162801321,
pseudo-tty/test-tty-stream-constructors.js,b/162801321,
pseudo-tty/test-tty-window-size.js,b/162801321,
pseudo-tty/test-tty-wrap.js,b/162801321,
-pummel/test-heapdump-http2.js,,Flaky
-pummel/test-net-pingpong.js,,
+pummel/test-net-pingpong.js,,Broken test
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
-tick-processor/test-tick-processor-builtin.js,,
+pummel/test-watch-file.js,,Flaky - VFS1 only
+tick-processor/test-tick-processor-builtin.js,,Broken test
diff --git a/test/runtimes/exclude/php7.3.6.csv b/test/runtimes/exclude/php7.3.6.csv
index a73f3bcfb..c051fe571 100644
--- a/test/runtimes/exclude/php7.3.6.csv
+++ b/test/runtimes/exclude/php7.3.6.csv
@@ -8,6 +8,7 @@ ext/mbstring/tests/bug77165.phpt,,
ext/mbstring/tests/bug77454.phpt,,
ext/mbstring/tests/mb_convert_encoding_leak.phpt,,
ext/mbstring/tests/mb_strrpos_encoding_3rd_param.phpt,,
+ext/pcre/tests/cache_limit.phpt,,Broken test - Flaky
ext/session/tests/session_module_name_variation4.phpt,,Flaky
ext/session/tests/session_set_save_handler_class_018.phpt,,
ext/session/tests/session_set_save_handler_iface_003.phpt,,
@@ -26,13 +27,14 @@ ext/standard/tests/file/php_fd_wrapper_01.phpt,,
ext/standard/tests/file/php_fd_wrapper_02.phpt,,
ext/standard/tests/file/php_fd_wrapper_03.phpt,,
ext/standard/tests/file/php_fd_wrapper_04.phpt,,
-ext/standard/tests/file/realpath_bug77484.phpt,b/162894969,
+ext/standard/tests/file/realpath_bug77484.phpt,b/162894969,VFS1 only failure
ext/standard/tests/file/rename_variation.phpt,b/68717309,
ext/standard/tests/file/symlink_link_linkinfo_is_link_variation4.phpt,b/162895341,
-ext/standard/tests/file/symlink_link_linkinfo_is_link_variation8.phpt,b/162896223,
+ext/standard/tests/file/symlink_link_linkinfo_is_link_variation8.phpt,b/162896223,VFS1 only failure
ext/standard/tests/general_functions/escapeshellarg_bug71270.phpt,,
ext/standard/tests/general_functions/escapeshellcmd_bug71270.phpt,,
ext/standard/tests/streams/proc_open_bug60120.phpt,,Flaky until php-src 3852a35fdbcb
+ext/standard/tests/streams/proc_open_bug64438.phpt,,Flaky
ext/standard/tests/streams/proc_open_bug69900.phpt,,Flaky
ext/standard/tests/streams/stream_socket_sendto.phpt,,
ext/standard/tests/strings/007.phpt,,
diff --git a/test/runtimes/exclude/python3.7.3.csv b/test/runtimes/exclude/python3.7.3.csv
index 8760f8951..e9fef03b7 100644
--- a/test/runtimes/exclude/python3.7.3.csv
+++ b/test/runtimes/exclude/python3.7.3.csv
@@ -4,7 +4,6 @@ test_asyncore,b/162973328,
test_epoll,b/162983393,
test_fcntl,b/162978767,fcntl invalid argument -- artificial test to make sure something works in 64 bit mode.
test_httplib,b/163000009,OSError: [Errno 98] Address already in use
-test_imaplib,b/162979661,
test_logging,b/162980079,
test_multiprocessing_fork,,Flaky. Sometimes times out.
test_multiprocessing_forkserver,,Flaky. Sometimes times out.
@@ -18,4 +17,3 @@ test_selectors,b/76116849,OSError not raised with epoll
test_smtplib,b/162980434,unclosed sockets
test_signal,,Flaky - signal: alarm clock
test_socket,b/75983380,
-test_subprocess,b/162980831,
diff --git a/test/runtimes/proctor/main.go b/test/runtimes/proctor/main.go
index e5607ac92..81cb68381 100644
--- a/test/runtimes/proctor/main.go
+++ b/test/runtimes/proctor/main.go
@@ -22,6 +22,7 @@ import (
"log"
"os"
"strings"
+ "syscall"
"gvisor.dev/gvisor/test/runtimes/proctor/lib"
)
@@ -33,6 +34,29 @@ var (
pause = flag.Bool("pause", false, "cause container to pause indefinitely, reaping any zombie children")
)
+// setNumFilesLimit changes the NOFILE soft rlimit if it is too high.
+func setNumFilesLimit() error {
+ // In docker containers, the default value of the NOFILE limit is
+ // 1048576. A few runtime tests (e.g. python:test_subprocess)
+ // enumerates all possible file descriptors and these tests can fail by
+ // 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)
+ 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)
+ if err != nil {
+ return fmt.Errorf("failed to set RLIMIT_NOFILE: %v", err)
+ }
+ }
+ return nil
+}
+
func main() {
flag.Parse()
@@ -74,6 +98,10 @@ func main() {
tests = strings.Split(*testNames, ",")
}
+ if err := setNumFilesLimit(); err != nil {
+ log.Fatalf("%v", err)
+ }
+
// Run tests.
cmds := tr.TestCmds(tests)
for _, cmd := range cmds {
diff --git a/test/runtimes/runner/lib/lib.go b/test/runtimes/runner/lib/lib.go
index 78285cb0e..64e6e14db 100644
--- a/test/runtimes/runner/lib/lib.go
+++ b/test/runtimes/runner/lib/lib.go
@@ -34,8 +34,16 @@ import (
// RunTests is a helper that is called by main. It exists so that we can run
// defered functions before exiting. It returns an exit code that should be
// passed to os.Exit.
-func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Duration) int {
- // Get tests to exclude..
+func RunTests(lang, image, excludeFile string, partitionNum, totalPartitions, batchSize int, timeout time.Duration) int {
+ if partitionNum <= 0 || totalPartitions <= 0 || partitionNum > totalPartitions {
+ fmt.Fprintf(os.Stderr, "invalid partition %d of %d", partitionNum, totalPartitions)
+ return 1
+ }
+
+ // TODO(gvisor.dev/issue/1624): Remove those tests from all exclude lists
+ // that only fail with VFS1.
+
+ // Get tests to exclude.
excludes, err := getExcludes(excludeFile)
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting exclude list: %s\n", err.Error())
@@ -55,7 +63,7 @@ func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Durat
// Get a slice of tests to run. This will also start a single Docker
// container that will be used to run each test. The final test will
// stop the Docker container.
- tests, err := getTests(ctx, d, lang, image, batchSize, timeout, excludes)
+ tests, err := getTests(ctx, d, lang, image, partitionNum, totalPartitions, batchSize, timeout, excludes)
if err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err.Error())
return 1
@@ -66,7 +74,7 @@ func RunTests(lang, image, excludeFile string, batchSize int, timeout time.Durat
}
// getTests executes all tests as table tests.
-func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, batchSize int, timeout time.Duration, excludes map[string]struct{}) ([]testing.InternalTest, error) {
+func getTests(ctx context.Context, d *dockerutil.Container, lang, image string, partitionNum, totalPartitions, batchSize int, timeout time.Duration, excludes map[string]struct{}) ([]testing.InternalTest, error) {
// Start the container.
opts := dockerutil.RunOpts{
Image: fmt.Sprintf("runtimes/%s", image),
@@ -86,6 +94,14 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string,
// shard.
tests := strings.Fields(list)
sort.Strings(tests)
+
+ partitionSize := len(tests) / totalPartitions
+ if partitionNum == totalPartitions {
+ tests = tests[(partitionNum-1)*partitionSize:]
+ } else {
+ tests = tests[(partitionNum-1)*partitionSize : partitionNum*partitionSize]
+ }
+
indices, err := testutil.TestIndicesForShard(len(tests))
if err != nil {
return nil, fmt.Errorf("TestsForShard() failed: %v", err)
@@ -116,8 +132,15 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string,
err error
)
+ state, err := d.Status(ctx)
+ if err != nil {
+ t.Fatalf("Could not find container status: %v", err)
+ }
+ if !state.Running {
+ t.Fatalf("container is not running: state = %s", state.Status)
+ }
+
go func() {
- fmt.Printf("RUNNING the following in a batch\n%s\n", strings.Join(tcs, "\n"))
output, err = d.Exec(ctx, dockerutil.ExecOpts{}, "/proctor/proctor", "--runtime", lang, "--tests", strings.Join(tcs, ","))
close(done)
}()
@@ -125,12 +148,12 @@ func getTests(ctx context.Context, d *dockerutil.Container, lang, image string,
select {
case <-done:
if err == nil {
- fmt.Printf("PASS: (%v)\n\n", time.Since(now))
+ fmt.Printf("PASS: (%v) %d tests passed\n", time.Since(now), len(tcs))
return
}
- t.Errorf("FAIL: (%v):\n%s\n", time.Since(now), output)
+ t.Errorf("FAIL: (%v):\nBatch:\n%s\nOutput:\n%s\n", time.Since(now), strings.Join(tcs, "\n"), output)
case <-time.After(timeout):
- t.Errorf("TIMEOUT: (%v):\n%s\n", time.Since(now), output)
+ t.Errorf("TIMEOUT: (%v):\nBatch:\n%s\nOutput:\n%s\n", time.Since(now), strings.Join(tcs, "\n"), output)
}
},
})
diff --git a/test/runtimes/runner/main.go b/test/runtimes/runner/main.go
index ec79a22c2..5b3443e36 100644
--- a/test/runtimes/runner/main.go
+++ b/test/runtimes/runner/main.go
@@ -25,11 +25,13 @@ import (
)
var (
- lang = flag.String("lang", "", "language runtime to test")
- image = flag.String("image", "", "docker image with runtime tests")
- excludeFile = flag.String("exclude_file", "", "file containing list of tests to exclude, in CSV format with fields: test name, bug id, comment")
- batchSize = flag.Int("batch", 50, "number of test cases run in one command")
- timeout = flag.Duration("timeout", 90*time.Minute, "batch timeout")
+ lang = flag.String("lang", "", "language runtime to test")
+ image = flag.String("image", "", "docker image with runtime tests")
+ excludeFile = flag.String("exclude_file", "", "file containing list of tests to exclude, in CSV format with fields: test name, bug id, comment")
+ partition = flag.Int("partition", 1, "partition number, this is 1-indexed")
+ totalPartitions = flag.Int("total_partitions", 1, "total number of partitions")
+ batchSize = flag.Int("batch", 50, "number of test cases run in one command")
+ timeout = flag.Duration("timeout", 90*time.Minute, "batch timeout")
)
func main() {
@@ -38,5 +40,5 @@ func main() {
fmt.Fprintf(os.Stderr, "lang and image flags must not be empty\n")
os.Exit(1)
}
- os.Exit(lib.RunTests(*lang, *image, *excludeFile, *batchSize, *timeout))
+ os.Exit(lib.RunTests(*lang, *image, *excludeFile, *partition, *totalPartitions, *batchSize, *timeout))
}
diff --git a/test/syscalls/BUILD b/test/syscalls/BUILD
index f66a9ceb4..b5a4ef4df 100644
--- a/test/syscalls/BUILD
+++ b/test/syscalls/BUILD
@@ -695,6 +695,10 @@ syscall_test(
)
syscall_test(
+ test = "//test/syscalls/linux:socket_ip_unbound_netlink_test",
+)
+
+syscall_test(
test = "//test/syscalls/linux:socket_netdevice_test",
)
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index 572f39a5d..2350f7e69 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -1285,6 +1285,7 @@ cc_binary(
"//test/util:mount_util",
"//test/util:multiprocess_util",
"//test/util:posix_error",
+ "//test/util:save_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
@@ -1801,10 +1802,14 @@ cc_binary(
"@com_google_absl//absl/flags:flag",
"@com_google_absl//absl/time",
gtest,
+ "//test/util:file_descriptor",
+ "//test/util:fs_util",
"//test/util:logging",
+ "//test/util:memory_util",
"//test/util:multiprocess_util",
"//test/util:platform_util",
"//test/util:signal_util",
+ "//test/util:temp_path",
"//test/util:test_util",
"//test/util:thread_util",
"//test/util:time_util",
@@ -2101,10 +2106,12 @@ cc_binary(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
gtest,
+ "//test/util:signal_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
+ "//test/util:timer_util",
],
)
@@ -2124,6 +2131,7 @@ cc_binary(
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
+ "//test/util:timer_util",
],
)
@@ -2137,10 +2145,12 @@ cc_binary(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
gtest,
+ "//test/util:signal_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
+ "//test/util:timer_util",
],
)
@@ -2434,6 +2444,7 @@ cc_library(
"@com_google_absl//absl/memory",
gtest,
"//test/util:posix_error",
+ "//test/util:save_util",
"//test/util:test_util",
],
alwayslink = 1,
@@ -2878,6 +2889,24 @@ cc_binary(
)
cc_binary(
+ name = "socket_ip_unbound_netlink_test",
+ testonly = 1,
+ srcs = [
+ "socket_ip_unbound_netlink.cc",
+ ],
+ linkstatic = 1,
+ deps = [
+ ":ip_socket_test_util",
+ ":socket_netlink_route_util",
+ ":socket_test_util",
+ "//test/util:capability_util",
+ gtest,
+ "//test/util:test_main",
+ "//test/util:test_util",
+ ],
+)
+
+cc_binary(
name = "socket_domain_test",
testonly = 1,
srcs = [
@@ -3441,6 +3470,7 @@ cc_binary(
"@com_google_absl//absl/strings",
gtest,
"//test/util:posix_error",
+ "//test/util:save_util",
"//test/util:temp_path",
"//test/util:test_main",
"//test/util:test_util",
diff --git a/test/syscalls/linux/mknod.cc b/test/syscalls/linux/mknod.cc
index b96907b30..1635c6d0c 100644
--- a/test/syscalls/linux/mknod.cc
+++ b/test/syscalls/linux/mknod.cc
@@ -125,6 +125,16 @@ TEST(MknodTest, Socket) {
ASSERT_THAT(unlink(filename.c_str()), SyscallSucceeds());
}
+PosixErrorOr<FileDescriptor> OpenRetryEINTR(std::string const& path, int flags,
+ mode_t mode = 0) {
+ while (true) {
+ auto maybe_fd = Open(path, flags, mode);
+ if (maybe_fd.ok() || maybe_fd.error().errno_value() != EINTR) {
+ return maybe_fd;
+ }
+ }
+}
+
TEST(MknodTest, Fifo) {
const std::string fifo = NewTempAbsPath();
ASSERT_THAT(mknod(fifo.c_str(), S_IFIFO | S_IRUSR | S_IWUSR, 0),
@@ -139,14 +149,16 @@ TEST(MknodTest, Fifo) {
// Read-end of the pipe.
ScopedThread t([&fifo, &buf, &msg]() {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY));
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY));
EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(msg.length()));
EXPECT_EQ(msg, std::string(buf.data()));
});
// Write-end of the pipe.
- FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY));
+ FileDescriptor wfd =
+ ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_WRONLY));
EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()),
SyscallSucceedsWithValue(msg.length()));
}
@@ -164,15 +176,16 @@ TEST(MknodTest, FifoOtrunc) {
std::vector<char> buf(512);
// Read-end of the pipe.
ScopedThread t([&fifo, &buf, &msg]() {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY));
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY));
EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(msg.length()));
EXPECT_EQ(msg, std::string(buf.data()));
});
// Write-end of the pipe.
- FileDescriptor wfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY | O_TRUNC));
+ FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(
+ OpenRetryEINTR(fifo.c_str(), O_WRONLY | O_TRUNC));
EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()),
SyscallSucceedsWithValue(msg.length()));
}
@@ -192,14 +205,15 @@ TEST(MknodTest, FifoTruncNoOp) {
std::vector<char> buf(512);
// Read-end of the pipe.
ScopedThread t([&fifo, &buf, &msg]() {
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_RDONLY));
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(OpenRetryEINTR(fifo.c_str(), O_RDONLY));
EXPECT_THAT(ReadFd(fd.get(), buf.data(), buf.size()),
SyscallSucceedsWithValue(msg.length()));
EXPECT_EQ(msg, std::string(buf.data()));
});
- FileDescriptor wfd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(fifo.c_str(), O_WRONLY | O_TRUNC));
+ FileDescriptor wfd = ASSERT_NO_ERRNO_AND_VALUE(
+ OpenRetryEINTR(fifo.c_str(), O_WRONLY | O_TRUNC));
EXPECT_THAT(ftruncate(wfd.get(), 0), SyscallFailsWithErrno(EINVAL));
EXPECT_THAT(WriteFd(wfd.get(), msg.c_str(), msg.length()),
SyscallSucceedsWithValue(msg.length()));
diff --git a/test/syscalls/linux/mmap.cc b/test/syscalls/linux/mmap.cc
index e52c9cbcb..83546830d 100644
--- a/test/syscalls/linux/mmap.cc
+++ b/test/syscalls/linux/mmap.cc
@@ -592,6 +592,12 @@ TEST_F(MMapTest, ProtExec) {
memcpy(reinterpret_cast<void*>(addr), machine_code, sizeof(machine_code));
+#if defined(__aarch64__)
+ // We use this as a memory barrier for Arm64.
+ ASSERT_THAT(Protect(addr, kPageSize, PROT_READ | PROT_EXEC),
+ SyscallSucceeds());
+#endif
+
func = reinterpret_cast<uint32_t (*)(void)>(addr);
EXPECT_EQ(42, func());
diff --git a/test/syscalls/linux/mount.cc b/test/syscalls/linux/mount.cc
index 3aab25b23..d65b7d031 100644
--- a/test/syscalls/linux/mount.cc
+++ b/test/syscalls/linux/mount.cc
@@ -34,6 +34,7 @@
#include "test/util/mount_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
@@ -131,7 +132,9 @@ TEST(MountTest, UmountDetach) {
ASSERT_NO_ERRNO_AND_VALUE(Mount("", dir.path(), "tmpfs", 0, "mode=0700",
/* umountflags= */ MNT_DETACH));
const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
- EXPECT_NE(before.st_ino, after.st_ino);
+ EXPECT_FALSE(before.st_dev == after.st_dev && before.st_ino == after.st_ino)
+ << "mount point has device number " << before.st_dev
+ << " and inode number " << before.st_ino << " before and after mount";
// Create files in the new mount.
constexpr char kContents[] = "no no no";
@@ -147,12 +150,14 @@ TEST(MountTest, UmountDetach) {
// Unmount the tmpfs.
mount.Release()();
- // Only check for inode number equality if the directory is not in overlayfs.
- // If xino option is not enabled and if all overlayfs layers do not belong to
- // the same filesystem then "the value of st_ino for directory objects may not
- // be persistent and could change even while the overlay filesystem is
- // mounted." -- Documentation/filesystems/overlayfs.txt
- if (!ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
+ // Inode numbers for gofer-accessed files may change across save/restore.
+ //
+ // For overlayfs, if xino option is not enabled and if all overlayfs layers do
+ // not belong to the same filesystem then "the value of st_ino for directory
+ // objects may not be persistent and could change even while the overlay
+ // filesystem is mounted." -- Documentation/filesystems/overlayfs.txt
+ if (!IsRunningWithSaveRestore() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
const struct stat after2 = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
EXPECT_EQ(before.st_ino, after2.st_ino);
}
@@ -214,18 +219,23 @@ TEST(MountTest, MountTmpfs) {
const struct stat s = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
EXPECT_EQ(s.st_mode, S_IFDIR | 0700);
- EXPECT_NE(s.st_ino, before.st_ino);
+ EXPECT_FALSE(before.st_dev == s.st_dev && before.st_ino == s.st_ino)
+ << "mount point has device number " << before.st_dev
+ << " and inode number " << before.st_ino << " before and after mount";
EXPECT_NO_ERRNO(Open(JoinPath(dir.path(), "foo"), O_CREAT | O_RDWR, 0777));
}
// Now that dir is unmounted again, we should have the old inode back.
- // Only check for inode number equality if the directory is not in overlayfs.
- // If xino option is not enabled and if all overlayfs layers do not belong to
- // the same filesystem then "the value of st_ino for directory objects may not
- // be persistent and could change even while the overlay filesystem is
- // mounted." -- Documentation/filesystems/overlayfs.txt
- if (!ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
+ //
+ // Inode numbers for gofer-accessed files may change across save/restore.
+ //
+ // For overlayfs, if xino option is not enabled and if all overlayfs layers do
+ // not belong to the same filesystem then "the value of st_ino for directory
+ // objects may not be persistent and could change even while the overlay
+ // filesystem is mounted." -- Documentation/filesystems/overlayfs.txt
+ if (!IsRunningWithSaveRestore() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(dir.path()))) {
const struct stat after = ASSERT_NO_ERRNO_AND_VALUE(Stat(dir.path()));
EXPECT_EQ(before.st_ino, after.st_ino);
}
diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc
index b558e3a01..a7c46adbf 100644
--- a/test/syscalls/linux/packet_socket_raw.cc
+++ b/test/syscalls/linux/packet_socket_raw.cc
@@ -664,6 +664,17 @@ TEST_P(RawPacketTest, SetAndGetSocketLinger) {
EXPECT_EQ(0, memcmp(&sl, &got_linger, length));
}
+TEST_P(RawPacketTest, GetSocketAcceptConn) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
INSTANTIATE_TEST_SUITE_P(AllInetTests, RawPacketTest,
::testing::Values(ETH_P_IP, ETH_P_ALL));
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
index e8fcc4439..7a0f33dff 100644
--- a/test/syscalls/linux/proc.cc
+++ b/test/syscalls/linux/proc.cc
@@ -26,6 +26,7 @@
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
+#include <sys/ptrace.h>
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/utsname.h>
@@ -512,6 +513,414 @@ TEST(ProcSelfAuxv, EntryValues) {
EXPECT_EQ(i, proc_auxv.size());
}
+// Just open and read a part of /proc/self/mem, check that we can read an item.
+TEST(ProcPidMem, Read) {
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
+ char input[] = "hello-world";
+ char output[sizeof(input)];
+ ASSERT_THAT(pread(memfd.get(), output, sizeof(output),
+ reinterpret_cast<off_t>(input)),
+ SyscallSucceedsWithValue(sizeof(input)));
+ ASSERT_STREQ(input, output);
+}
+
+// Perform read on an unmapped region.
+TEST(ProcPidMem, Unmapped) {
+ // Strategy: map then unmap, so we have a guaranteed unmapped region
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
+ Mapping mapping = ASSERT_NO_ERRNO_AND_VALUE(
+ MmapAnon(kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
+ // Fill it with things
+ memset(mapping.ptr(), 'x', mapping.len());
+ char expected = 'x', output;
+ ASSERT_THAT(pread(memfd.get(), &output, sizeof(output),
+ reinterpret_cast<off_t>(mapping.ptr())),
+ SyscallSucceedsWithValue(sizeof(output)));
+ ASSERT_EQ(expected, output);
+
+ // Unmap region again
+ ASSERT_THAT(munmap(mapping.ptr(), mapping.len()), SyscallSucceeds());
+
+ // Now we want EIO error
+ ASSERT_THAT(pread(memfd.get(), &output, sizeof(output),
+ reinterpret_cast<off_t>(mapping.ptr())),
+ SyscallFailsWithErrno(EIO));
+}
+
+// Perform read repeatedly to verify offset change.
+TEST(ProcPidMem, RepeatedRead) {
+ auto const num_reads = 3;
+ char expected[] = "01234567890abcdefghijkl";
+ char output[sizeof(expected) / num_reads];
+
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
+ ASSERT_THAT(lseek(memfd.get(), reinterpret_cast<off_t>(&expected), SEEK_SET),
+ SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected)));
+ for (auto i = 0; i < num_reads; i++) {
+ ASSERT_THAT(read(memfd.get(), &output, sizeof(output)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ ASSERT_EQ(strncmp(&expected[i * sizeof(output)], output, sizeof(output)),
+ 0);
+ }
+}
+
+// Perform seek operations repeatedly.
+TEST(ProcPidMem, RepeatedSeek) {
+ auto const num_reads = 3;
+ char expected[] = "01234567890abcdefghijkl";
+ char output[sizeof(expected) / num_reads];
+
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
+ ASSERT_THAT(lseek(memfd.get(), reinterpret_cast<off_t>(&expected), SEEK_SET),
+ SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected)));
+ // Read from start
+ ASSERT_THAT(read(memfd.get(), &output, sizeof(output)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ ASSERT_EQ(strncmp(&expected[0 * sizeof(output)], output, sizeof(output)), 0);
+ // Skip ahead one read
+ ASSERT_THAT(lseek(memfd.get(), sizeof(output), SEEK_CUR),
+ SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected) +
+ sizeof(output) * 2));
+ // Do read again
+ ASSERT_THAT(read(memfd.get(), &output, sizeof(output)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ ASSERT_EQ(strncmp(&expected[2 * sizeof(output)], output, sizeof(output)), 0);
+ // Skip back three reads
+ ASSERT_THAT(lseek(memfd.get(), -3 * sizeof(output), SEEK_CUR),
+ SyscallSucceedsWithValue(reinterpret_cast<off_t>(&expected)));
+ // Do read again
+ ASSERT_THAT(read(memfd.get(), &output, sizeof(output)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ ASSERT_EQ(strncmp(&expected[0 * sizeof(output)], output, sizeof(output)), 0);
+ // Check that SEEK_END does not work
+ ASSERT_THAT(lseek(memfd.get(), 0, SEEK_END), SyscallFailsWithErrno(EINVAL));
+}
+
+// Perform read past an allocated memory region.
+TEST(ProcPidMem, PartialRead) {
+ // Strategy: map large region, then do unmap and remap smaller region
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open("/proc/self/mem", O_RDONLY));
+
+ Mapping mapping = ASSERT_NO_ERRNO_AND_VALUE(
+ MmapAnon(2 * kPageSize, PROT_READ | PROT_WRITE, MAP_PRIVATE));
+ ASSERT_THAT(munmap(mapping.ptr(), mapping.len()), SyscallSucceeds());
+ Mapping smaller_mapping = ASSERT_NO_ERRNO_AND_VALUE(
+ Mmap(mapping.ptr(), kPageSize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
+
+ // Fill it with things
+ memset(smaller_mapping.ptr(), 'x', smaller_mapping.len());
+
+ // Now we want no error
+ char expected[] = {'x'};
+ std::unique_ptr<char[]> output(new char[kPageSize]);
+ off_t read_offset =
+ reinterpret_cast<off_t>(smaller_mapping.ptr()) + kPageSize - 1;
+ ASSERT_THAT(
+ pread(memfd.get(), output.get(), sizeof(output.get()), read_offset),
+ SyscallSucceedsWithValue(sizeof(expected)));
+ // Since output is larger, than expected we have to do manual compare
+ ASSERT_EQ(expected[0], (output).get()[0]);
+}
+
+// Perform read on /proc/[pid]/mem after exit.
+TEST(ProcPidMem, AfterExit) {
+ int pfd1[2] = {};
+ int pfd2[2] = {};
+
+ char expected[] = "hello-world";
+
+ ASSERT_THAT(pipe(pfd1), SyscallSucceeds());
+ ASSERT_THAT(pipe(pfd2), SyscallSucceeds());
+
+ // Create child process
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // Close reading end of first pipe
+ close(pfd1[0]);
+
+ // Tell parent about location of input
+ char ok = 1;
+ TEST_CHECK(WriteFd(pfd1[1], &ok, sizeof(ok)) == sizeof(ok));
+ TEST_PCHECK(close(pfd1[1]) == 0);
+
+ // Close writing end of second pipe
+ TEST_PCHECK(close(pfd2[1]) == 0);
+
+ // Await parent OK to die
+ ok = 0;
+ TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok));
+
+ // Close rest pipes
+ TEST_PCHECK(close(pfd2[0]) == 0);
+ _exit(0);
+ }
+
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Close writing end of first pipe
+ EXPECT_THAT(close(pfd1[1]), SyscallSucceeds());
+
+ // Wait for child to be alive and well
+ char ok = 0;
+ EXPECT_THAT(ReadFd(pfd1[0], &ok, sizeof(ok)),
+ SyscallSucceedsWithValue(sizeof(ok)));
+ // Close reading end of first pipe
+ EXPECT_THAT(close(pfd1[0]), SyscallSucceeds());
+
+ // Open /proc/pid/mem fd
+ std::string mempath = absl::StrCat("/proc/", child_pid, "/mem");
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY));
+
+ // Expect that we can read
+ char output[sizeof(expected)];
+ EXPECT_THAT(pread(memfd.get(), &output, sizeof(output),
+ reinterpret_cast<off_t>(&expected)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ EXPECT_STREQ(expected, output);
+
+ // Tell proc its ok to go
+ EXPECT_THAT(close(pfd2[0]), SyscallSucceeds());
+ ok = 1;
+ EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)),
+ SyscallSucceedsWithValue(sizeof(ok)));
+ EXPECT_THAT(close(pfd2[1]), SyscallSucceeds());
+
+ // Expect termination
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds());
+
+ // Expect that we can't read anymore
+ EXPECT_THAT(pread(memfd.get(), &output, sizeof(output),
+ reinterpret_cast<off_t>(&expected)),
+ SyscallSucceedsWithValue(0));
+}
+
+// Read from /proc/[pid]/mem with different UID/GID and attached state.
+TEST(ProcPidMem, DifferentUserAttached) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_DAC_OVERRIDE)));
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_PTRACE)));
+
+ int pfd1[2] = {};
+ int pfd2[2] = {};
+
+ ASSERT_THAT(pipe(pfd1), SyscallSucceeds());
+ ASSERT_THAT(pipe(pfd2), SyscallSucceeds());
+
+ // Create child process
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // Close reading end of first pipe
+ close(pfd1[0]);
+
+ // Tell parent about location of input
+ char input[] = "hello-world";
+ off_t input_location = reinterpret_cast<off_t>(input);
+ TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) ==
+ sizeof(input_location));
+ TEST_PCHECK(close(pfd1[1]) == 0);
+
+ // Close writing end of second pipe
+ TEST_PCHECK(close(pfd2[1]) == 0);
+
+ // Await parent OK to die
+ char ok = 0;
+ TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok));
+
+ // Close rest pipes
+ TEST_PCHECK(close(pfd2[0]) == 0);
+ _exit(0);
+ }
+
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Close writing end of first pipe
+ EXPECT_THAT(close(pfd1[1]), SyscallSucceeds());
+
+ // Read target location from child
+ off_t target_location;
+ EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)),
+ SyscallSucceedsWithValue(sizeof(target_location)));
+ // Close reading end of first pipe
+ EXPECT_THAT(close(pfd1[0]), SyscallSucceeds());
+
+ ScopedThread([&] {
+ // Attach to child subprocess without stopping it
+ EXPECT_THAT(ptrace(PTRACE_SEIZE, child_pid, NULL, NULL), SyscallSucceeds());
+
+ // Keep capabilities after setuid
+ EXPECT_THAT(prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0), SyscallSucceeds());
+ constexpr int kNobody = 65534;
+ EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds());
+
+ // Only restore CAP_SYS_PTRACE and CAP_DAC_OVERRIDE
+ EXPECT_NO_ERRNO(SetCapability(CAP_SYS_PTRACE, true));
+ EXPECT_NO_ERRNO(SetCapability(CAP_DAC_OVERRIDE, true));
+
+ // Open /proc/pid/mem fd
+ std::string mempath = absl::StrCat("/proc/", child_pid, "/mem");
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY));
+ char expected[] = "hello-world";
+ char output[sizeof(expected)];
+ EXPECT_THAT(pread(memfd.get(), output, sizeof(output),
+ reinterpret_cast<off_t>(target_location)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ EXPECT_STREQ(expected, output);
+
+ // Tell proc its ok to go
+ EXPECT_THAT(close(pfd2[0]), SyscallSucceeds());
+ char ok = 1;
+ EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)),
+ SyscallSucceedsWithValue(sizeof(ok)));
+ EXPECT_THAT(close(pfd2[1]), SyscallSucceeds());
+
+ // Expect termination
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds());
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0)
+ << " status " << status;
+ });
+}
+
+// Attempt to read from /proc/[pid]/mem with different UID/GID.
+TEST(ProcPidMem, DifferentUser) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SETUID)));
+
+ int pfd1[2] = {};
+ int pfd2[2] = {};
+
+ ASSERT_THAT(pipe(pfd1), SyscallSucceeds());
+ ASSERT_THAT(pipe(pfd2), SyscallSucceeds());
+
+ // Create child process
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // Close reading end of first pipe
+ close(pfd1[0]);
+
+ // Tell parent about location of input
+ char input[] = "hello-world";
+ off_t input_location = reinterpret_cast<off_t>(input);
+ TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) ==
+ sizeof(input_location));
+ TEST_PCHECK(close(pfd1[1]) == 0);
+
+ // Close writing end of second pipe
+ TEST_PCHECK(close(pfd2[1]) == 0);
+
+ // Await parent OK to die
+ char ok = 0;
+ TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok));
+
+ // Close rest pipes
+ TEST_PCHECK(close(pfd2[0]) == 0);
+ _exit(0);
+ }
+
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Close writing end of first pipe
+ EXPECT_THAT(close(pfd1[1]), SyscallSucceeds());
+
+ // Read target location from child
+ off_t target_location;
+ EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)),
+ SyscallSucceedsWithValue(sizeof(target_location)));
+ // Close reading end of first pipe
+ EXPECT_THAT(close(pfd1[0]), SyscallSucceeds());
+
+ ScopedThread([&] {
+ constexpr int kNobody = 65534;
+ EXPECT_THAT(syscall(SYS_setuid, kNobody), SyscallSucceeds());
+
+ // Attempt to open /proc/[child_pid]/mem
+ std::string mempath = absl::StrCat("/proc/", child_pid, "/mem");
+ EXPECT_THAT(open(mempath.c_str(), O_RDONLY), SyscallFailsWithErrno(EACCES));
+
+ // Tell proc its ok to go
+ EXPECT_THAT(close(pfd2[0]), SyscallSucceeds());
+ char ok = 1;
+ EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)),
+ SyscallSucceedsWithValue(sizeof(ok)));
+ EXPECT_THAT(close(pfd2[1]), SyscallSucceeds());
+
+ // Expect termination
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds());
+ });
+}
+
+// Perform read on /proc/[pid]/mem with same UID/GID.
+TEST(ProcPidMem, SameUser) {
+ int pfd1[2] = {};
+ int pfd2[2] = {};
+
+ ASSERT_THAT(pipe(pfd1), SyscallSucceeds());
+ ASSERT_THAT(pipe(pfd2), SyscallSucceeds());
+
+ // Create child process
+ pid_t const child_pid = fork();
+ if (child_pid == 0) {
+ // Close reading end of first pipe
+ close(pfd1[0]);
+
+ // Tell parent about location of input
+ char input[] = "hello-world";
+ off_t input_location = reinterpret_cast<off_t>(input);
+ TEST_CHECK(WriteFd(pfd1[1], &input_location, sizeof(input_location)) ==
+ sizeof(input_location));
+ TEST_PCHECK(close(pfd1[1]) == 0);
+
+ // Close writing end of second pipe
+ TEST_PCHECK(close(pfd2[1]) == 0);
+
+ // Await parent OK to die
+ char ok = 0;
+ TEST_CHECK(ReadFd(pfd2[0], &ok, sizeof(ok)) == sizeof(ok));
+
+ // Close rest pipes
+ TEST_PCHECK(close(pfd2[0]) == 0);
+ _exit(0);
+ }
+ // In parent process.
+ ASSERT_THAT(child_pid, SyscallSucceeds());
+
+ // Close writing end of first pipe
+ EXPECT_THAT(close(pfd1[1]), SyscallSucceeds());
+
+ // Read target location from child
+ off_t target_location;
+ EXPECT_THAT(ReadFd(pfd1[0], &target_location, sizeof(target_location)),
+ SyscallSucceedsWithValue(sizeof(target_location)));
+ // Close reading end of first pipe
+ EXPECT_THAT(close(pfd1[0]), SyscallSucceeds());
+
+ // Open /proc/pid/mem fd
+ std::string mempath = absl::StrCat("/proc/", child_pid, "/mem");
+ auto memfd = ASSERT_NO_ERRNO_AND_VALUE(Open(mempath, O_RDONLY));
+ char expected[] = "hello-world";
+ char output[sizeof(expected)];
+ EXPECT_THAT(pread(memfd.get(), output, sizeof(output),
+ reinterpret_cast<off_t>(target_location)),
+ SyscallSucceedsWithValue(sizeof(output)));
+ EXPECT_STREQ(expected, output);
+
+ // Tell proc its ok to go
+ EXPECT_THAT(close(pfd2[0]), SyscallSucceeds());
+ char ok = 1;
+ EXPECT_THAT(WriteFd(pfd2[1], &ok, sizeof(ok)),
+ SyscallSucceedsWithValue(sizeof(ok)));
+ EXPECT_THAT(close(pfd2[1]), SyscallSucceeds());
+
+ // Expect termination
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0), SyscallSucceeds());
+}
+
// Just open and read /proc/self/maps, check that we can find [stack]
TEST(ProcSelfMaps, Basic) {
auto proc_self_maps =
diff --git a/test/syscalls/linux/proc_pid_smaps.cc b/test/syscalls/linux/proc_pid_smaps.cc
index 9fb1b3a2c..738923822 100644
--- a/test/syscalls/linux/proc_pid_smaps.cc
+++ b/test/syscalls/linux/proc_pid_smaps.cc
@@ -191,7 +191,7 @@ PosixErrorOr<std::vector<ProcPidSmapsEntry>> ParseProcPidSmaps(
// amount of whitespace).
if (!entry) {
std::cerr << "smaps line not considered a maps line: "
- << maybe_maps_entry.error_message() << std::endl;
+ << maybe_maps_entry.error().message() << std::endl;
return PosixError(
EINVAL,
absl::StrCat("smaps field line without preceding maps line: ", l));
diff --git a/test/syscalls/linux/ptrace.cc b/test/syscalls/linux/ptrace.cc
index 926690eb8..13c19d4a8 100644
--- a/test/syscalls/linux/ptrace.cc
+++ b/test/syscalls/linux/ptrace.cc
@@ -30,10 +30,13 @@
#include "absl/flags/flag.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
+#include "test/util/fs_util.h"
#include "test/util/logging.h"
+#include "test/util/memory_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/platform_util.h"
#include "test/util/signal_util.h"
+#include "test/util/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
#include "test/util/time_util.h"
@@ -113,10 +116,21 @@ TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) {
// except disabled.
SKIP_IF(ASSERT_NO_ERRNO_AND_VALUE(YamaPtraceScope()) > 0);
- constexpr long kBeforePokeDataValue = 10;
- constexpr long kAfterPokeDataValue = 20;
+ // Test PTRACE_POKE/PEEKDATA on both anonymous and file mappings.
+ const auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ ASSERT_NO_ERRNO(Truncate(file.path(), kPageSize));
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
+ const auto file_mapping = ASSERT_NO_ERRNO_AND_VALUE(Mmap(
+ nullptr, kPageSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd.get(), 0));
- volatile long word = kBeforePokeDataValue;
+ constexpr long kBeforePokeDataAnonValue = 10;
+ constexpr long kAfterPokeDataAnonValue = 20;
+ constexpr long kBeforePokeDataFileValue = 0; // implicit, due to truncate()
+ constexpr long kAfterPokeDataFileValue = 30;
+
+ volatile long anon_word = kBeforePokeDataAnonValue;
+ auto* file_word_ptr = static_cast<volatile long*>(file_mapping.ptr());
pid_t const child_pid = fork();
if (child_pid == 0) {
@@ -134,12 +148,22 @@ TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) {
MaybeSave();
TEST_CHECK(WIFSTOPPED(status) && WSTOPSIG(status) == SIGSTOP);
- // Replace the value of word in the parent process with kAfterPokeDataValue.
- long const parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, &word, 0);
+ // Replace the value of anon_word in the parent process with
+ // kAfterPokeDataAnonValue.
+ long parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, &anon_word, 0);
+ MaybeSave();
+ TEST_CHECK(parent_word == kBeforePokeDataAnonValue);
+ TEST_PCHECK(ptrace(PTRACE_POKEDATA, parent_pid, &anon_word,
+ kAfterPokeDataAnonValue) == 0);
+ MaybeSave();
+
+ // Replace the value pointed to by file_word_ptr in the mapped file with
+ // kAfterPokeDataFileValue, via the parent process' mapping.
+ parent_word = ptrace(PTRACE_PEEKDATA, parent_pid, file_word_ptr, 0);
MaybeSave();
- TEST_CHECK(parent_word == kBeforePokeDataValue);
- TEST_PCHECK(
- ptrace(PTRACE_POKEDATA, parent_pid, &word, kAfterPokeDataValue) == 0);
+ TEST_CHECK(parent_word == kBeforePokeDataFileValue);
+ TEST_PCHECK(ptrace(PTRACE_POKEDATA, parent_pid, file_word_ptr,
+ kAfterPokeDataFileValue) == 0);
MaybeSave();
// Detach from the parent and suppress the SIGSTOP. If the SIGSTOP is not
@@ -160,7 +184,8 @@ TEST(PtraceTest, AttachParent_PeekData_PokeData_SignalSuppression) {
<< " status " << status;
// Check that the child's PTRACE_POKEDATA was effective.
- EXPECT_EQ(kAfterPokeDataValue, word);
+ EXPECT_EQ(kAfterPokeDataAnonValue, anon_word);
+ EXPECT_EQ(kAfterPokeDataFileValue, *file_word_ptr);
}
TEST(PtraceTest, GetSigMask) {
diff --git a/test/syscalls/linux/raw_socket_icmp.cc b/test/syscalls/linux/raw_socket_icmp.cc
index 1b9dbc584..bd779da92 100644
--- a/test/syscalls/linux/raw_socket_icmp.cc
+++ b/test/syscalls/linux/raw_socket_icmp.cc
@@ -438,6 +438,19 @@ TEST_F(RawSocketICMPTest, SetAndGetSocketLinger) {
EXPECT_EQ(0, memcmp(&sl, &got_linger, length));
}
+// Test getsockopt for SO_ACCEPTCONN.
+TEST_F(RawSocketICMPTest, GetSocketAcceptConn) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_RAW)));
+
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
+
void RawSocketICMPTest::ExpectICMPSuccess(const struct icmphdr& icmp) {
// We're going to receive both the echo request and reply, but the order is
// indeterminate.
diff --git a/test/syscalls/linux/semaphore.cc b/test/syscalls/linux/semaphore.cc
index e9b131ca9..890f4a246 100644
--- a/test/syscalls/linux/semaphore.cc
+++ b/test/syscalls/linux/semaphore.cc
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <signal.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
@@ -486,6 +487,292 @@ TEST(SemaphoreTest, SemIpcSet) {
ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallFailsWithErrno(EACCES));
}
+TEST(SemaphoreTest, SemCtlIpcStat) {
+ // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
+ ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ const uid_t kUid = getuid();
+ const gid_t kGid = getgid();
+ time_t start_time = time(nullptr);
+
+ AutoSem sem(semget(IPC_PRIVATE, 10, 0600 | IPC_CREAT));
+ ASSERT_THAT(sem.get(), SyscallSucceeds());
+
+ struct semid_ds ds;
+ EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds), SyscallSucceeds());
+
+ EXPECT_EQ(ds.sem_perm.__key, IPC_PRIVATE);
+ EXPECT_EQ(ds.sem_perm.uid, kUid);
+ EXPECT_EQ(ds.sem_perm.gid, kGid);
+ EXPECT_EQ(ds.sem_perm.cuid, kUid);
+ EXPECT_EQ(ds.sem_perm.cgid, kGid);
+ EXPECT_EQ(ds.sem_perm.mode, 0600);
+ // Last semop time is not set on creation.
+ EXPECT_EQ(ds.sem_otime, 0);
+ EXPECT_GE(ds.sem_ctime, start_time);
+ EXPECT_EQ(ds.sem_nsems, 10);
+
+ // The timestamps only have a resolution of seconds; slow down so we actually
+ // see the timestamps change.
+ absl::SleepFor(absl::Seconds(1));
+
+ // Set semid_ds structure of the set.
+ auto last_ctime = ds.sem_ctime;
+ start_time = time(nullptr);
+ struct semid_ds semid_to_set = {};
+ semid_to_set.sem_perm.uid = kUid;
+ semid_to_set.sem_perm.gid = kGid;
+ semid_to_set.sem_perm.mode = 0666;
+ ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &semid_to_set), SyscallSucceeds());
+ struct sembuf buf = {};
+ buf.sem_op = 1;
+ ASSERT_THAT(semop(sem.get(), &buf, 1), SyscallSucceeds());
+
+ EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds), SyscallSucceeds());
+ EXPECT_EQ(ds.sem_perm.mode, 0666);
+ EXPECT_GE(ds.sem_otime, start_time);
+ EXPECT_GT(ds.sem_ctime, last_ctime);
+
+ // An invalid semid fails the syscall with errno EINVAL.
+ EXPECT_THAT(semctl(sem.get() + 1, 0, IPC_STAT, &ds),
+ SyscallFailsWithErrno(EINVAL));
+
+ // Make semaphore not readable and check the signal fails.
+ semid_to_set.sem_perm.mode = 0200;
+ ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &semid_to_set), SyscallSucceeds());
+ EXPECT_THAT(semctl(sem.get(), 0, IPC_STAT, &ds),
+ SyscallFailsWithErrno(EACCES));
+}
+
+// Calls semctl(semid, 0, cmd) until the returned value is >= target, an
+// internal timeout expires, or semctl returns an error.
+PosixErrorOr<int> WaitSemctl(int semid, int target, int cmd) {
+ constexpr absl::Duration timeout = absl::Seconds(10);
+ const auto deadline = absl::Now() + timeout;
+ int semcnt = 0;
+ while (absl::Now() < deadline) {
+ semcnt = semctl(semid, 0, cmd);
+ if (semcnt < 0) {
+ return PosixError(errno, "semctl(GETZCNT) failed");
+ }
+ if (semcnt >= target) {
+ break;
+ }
+ absl::SleepFor(absl::Milliseconds(10));
+ }
+ return semcnt;
+}
+
+TEST(SemaphoreTest, SemopGetzcnt) {
+ // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
+ ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ // Create a write only semaphore set.
+ AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT));
+ ASSERT_THAT(sem.get(), SyscallSucceeds());
+
+ // No read permission to retrieve semzcnt.
+ EXPECT_THAT(semctl(sem.get(), 0, GETZCNT), SyscallFailsWithErrno(EACCES));
+
+ // Remove the calling thread's read permission.
+ struct semid_ds ds = {};
+ ds.sem_perm.uid = getuid();
+ ds.sem_perm.gid = getgid();
+ ds.sem_perm.mode = 0600;
+ ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &ds), SyscallSucceeds());
+
+ std::vector<pid_t> children;
+ ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds());
+
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = 0;
+ constexpr size_t kLoops = 10;
+ for (auto i = 0; i < kLoops; i++) {
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0);
+ _exit(0);
+ }
+ children.push_back(child_pid);
+ }
+
+ EXPECT_THAT(WaitSemctl(sem.get(), kLoops, GETZCNT),
+ IsPosixErrorOkAndHolds(kLoops));
+ // Set semval to 0, which wakes up children that sleep on the semop.
+ ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 0), SyscallSucceeds());
+ for (const auto& child_pid : children) {
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ }
+ EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0);
+}
+
+TEST(SemaphoreTest, SemopGetzcntOnSetRemoval) {
+ auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT);
+ ASSERT_THAT(semid, SyscallSucceeds());
+ ASSERT_THAT(semctl(semid, 0, SETVAL, 1), SyscallSucceeds());
+ ASSERT_EQ(semctl(semid, 0, GETZCNT), 0);
+
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = 0;
+
+ // Ensure that wait will only unblock when the semaphore is removed. On
+ // EINTR retry it may race with deletion and return EINVAL.
+ TEST_PCHECK(RetryEINTR(semop)(semid, &buf, 1) < 0 &&
+ (errno == EIDRM || errno == EINVAL));
+ _exit(0);
+ }
+
+ EXPECT_THAT(WaitSemctl(semid, 1, GETZCNT), IsPosixErrorOkAndHolds(1));
+ // Remove the semaphore set, which fails the sleep semop.
+ ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds());
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ EXPECT_THAT(semctl(semid, 0, GETZCNT), SyscallFailsWithErrno(EINVAL));
+}
+
+TEST(SemaphoreTest, SemopGetzcntOnSignal_NoRandomSave) {
+ AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
+ ASSERT_THAT(sem.get(), SyscallSucceeds());
+ ASSERT_THAT(semctl(sem.get(), 0, SETVAL, 1), SyscallSucceeds());
+ ASSERT_EQ(semctl(sem.get(), 0, GETZCNT), 0);
+
+ // Saving will cause semop() to be spuriously interrupted.
+ DisableSave ds;
+
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ TEST_PCHECK(signal(SIGHUP, [](int sig) -> void {}) != SIG_ERR);
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = 0;
+
+ TEST_PCHECK(semop(sem.get(), &buf, 1) < 0 && errno == EINTR);
+ _exit(0);
+ }
+
+ EXPECT_THAT(WaitSemctl(sem.get(), 1, GETZCNT), IsPosixErrorOkAndHolds(1));
+ // Send a signal to the child, which fails the sleep semop.
+ ASSERT_EQ(kill(child_pid, SIGHUP), 0);
+
+ ds.reset();
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ EXPECT_EQ(semctl(sem.get(), 0, GETZCNT), 0);
+}
+
+TEST(SemaphoreTest, SemopGetncnt) {
+ // Drop CAP_IPC_OWNER which allows us to bypass semaphore permissions.
+ ASSERT_NO_ERRNO(SetCapability(CAP_IPC_OWNER, false));
+ // Create a write only semaphore set.
+ AutoSem sem(semget(IPC_PRIVATE, 1, 0200 | IPC_CREAT));
+ ASSERT_THAT(sem.get(), SyscallSucceeds());
+
+ // No read permission to retrieve semzcnt.
+ EXPECT_THAT(semctl(sem.get(), 0, GETNCNT), SyscallFailsWithErrno(EACCES));
+
+ // Remove the calling thread's read permission.
+ struct semid_ds ds = {};
+ ds.sem_perm.uid = getuid();
+ ds.sem_perm.gid = getgid();
+ ds.sem_perm.mode = 0600;
+ ASSERT_THAT(semctl(sem.get(), 0, IPC_SET, &ds), SyscallSucceeds());
+
+ std::vector<pid_t> children;
+
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = -1;
+ constexpr size_t kLoops = 10;
+ for (auto i = 0; i < kLoops; i++) {
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ TEST_PCHECK(RetryEINTR(semop)(sem.get(), &buf, 1) == 0);
+ _exit(0);
+ }
+ children.push_back(child_pid);
+ }
+ EXPECT_THAT(WaitSemctl(sem.get(), kLoops, GETNCNT),
+ IsPosixErrorOkAndHolds(kLoops));
+ // Set semval to 1, which wakes up children that sleep on the semop.
+ ASSERT_THAT(semctl(sem.get(), 0, SETVAL, kLoops), SyscallSucceeds());
+ for (const auto& child_pid : children) {
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ }
+ EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0);
+}
+
+TEST(SemaphoreTest, SemopGetncntOnSetRemoval) {
+ auto semid = semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT);
+ ASSERT_THAT(semid, SyscallSucceeds());
+ ASSERT_EQ(semctl(semid, 0, GETNCNT), 0);
+
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = -1;
+
+ // Ensure that wait will only unblock when the semaphore is removed. On
+ // EINTR retry it may race with deletion and return EINVAL
+ TEST_PCHECK(RetryEINTR(semop)(semid, &buf, 1) < 0 &&
+ (errno == EIDRM || errno == EINVAL));
+ _exit(0);
+ }
+
+ EXPECT_THAT(WaitSemctl(semid, 1, GETNCNT), IsPosixErrorOkAndHolds(1));
+ // Remove the semaphore set, which fails the sleep semop.
+ ASSERT_THAT(semctl(semid, 0, IPC_RMID), SyscallSucceeds());
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ EXPECT_THAT(semctl(semid, 0, GETNCNT), SyscallFailsWithErrno(EINVAL));
+}
+
+TEST(SemaphoreTest, SemopGetncntOnSignal_NoRandomSave) {
+ AutoSem sem(semget(IPC_PRIVATE, 1, 0600 | IPC_CREAT));
+ ASSERT_THAT(sem.get(), SyscallSucceeds());
+ ASSERT_EQ(semctl(sem.get(), 0, GETNCNT), 0);
+
+ // Saving will cause semop() to be spuriously interrupted.
+ DisableSave ds;
+
+ auto child_pid = fork();
+ if (child_pid == 0) {
+ TEST_PCHECK(signal(SIGHUP, [](int sig) -> void {}) != SIG_ERR);
+ struct sembuf buf = {};
+ buf.sem_num = 0;
+ buf.sem_op = -1;
+
+ TEST_PCHECK(semop(sem.get(), &buf, 1) < 0 && errno == EINTR);
+ _exit(0);
+ }
+ EXPECT_THAT(WaitSemctl(sem.get(), 1, GETNCNT), IsPosixErrorOkAndHolds(1));
+ // Send a signal to the child, which fails the sleep semop.
+ ASSERT_EQ(kill(child_pid, SIGHUP), 0);
+
+ ds.reset();
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ EXPECT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+ EXPECT_EQ(semctl(sem.get(), 0, GETNCNT), 0);
+}
+
} // namespace
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/sendfile.cc b/test/syscalls/linux/sendfile.cc
index a8bfb01f1..cf0977118 100644
--- a/test/syscalls/linux/sendfile.cc
+++ b/test/syscalls/linux/sendfile.cc
@@ -25,9 +25,11 @@
#include "absl/time/time.h"
#include "test/util/eventfd_util.h"
#include "test/util/file_descriptor.h"
+#include "test/util/signal_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
+#include "test/util/timer_util.h"
namespace gvisor {
namespace testing {
@@ -629,6 +631,57 @@ TEST(SendFileTest, SendFileToPipe) {
SyscallSucceedsWithValue(kDataSize));
}
+static volatile int signaled = 0;
+void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; }
+
+TEST(SendFileTest, ToEventFDDoesNotSpin_NoRandomSave) {
+ FileDescriptor efd = ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, 0));
+
+ // Write the maximum value of an eventfd to a file.
+ const uint64_t kMaxEventfdValue = 0xfffffffffffffffe;
+ const auto tempfile = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ const auto tempfd = ASSERT_NO_ERRNO_AND_VALUE(Open(tempfile.path(), O_RDWR));
+ ASSERT_THAT(
+ pwrite(tempfd.get(), &kMaxEventfdValue, sizeof(kMaxEventfdValue), 0),
+ SyscallSucceedsWithValue(sizeof(kMaxEventfdValue)));
+
+ // Set the eventfd's value to 1.
+ const uint64_t kOne = 1;
+ ASSERT_THAT(write(efd.get(), &kOne, sizeof(kOne)),
+ SyscallSucceedsWithValue(sizeof(kOne)));
+
+ // Set up signal handler.
+ struct sigaction sa = {};
+ sa.sa_sigaction = SigUsr1Handler;
+ sa.sa_flags = SA_SIGINFO;
+ const auto cleanup_sigact =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa));
+
+ // Send SIGUSR1 to this thread in 1 second.
+ struct sigevent sev = {};
+ sev.sigev_notify = SIGEV_THREAD_ID;
+ sev.sigev_signo = SIGUSR1;
+ sev.sigev_notify_thread_id = gettid();
+ auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
+ struct itimerspec its = {};
+ its.it_value = absl::ToTimespec(absl::Seconds(1));
+ DisableSave ds; // Asserting an EINTR.
+ ASSERT_NO_ERRNO(timer.Set(0, its));
+
+ // Sendfile from tempfd to the eventfd. Since the eventfd is not already at
+ // its maximum value, the eventfd is "ready for writing"; however, since the
+ // eventfd's existing value plus the new value would exceed the maximum, the
+ // write should internally fail with EWOULDBLOCK. In this case, sendfile()
+ // should block instead of spinning, and eventually be interrupted by our
+ // timer. See b/172075629.
+ EXPECT_THAT(
+ sendfile(efd.get(), tempfd.get(), nullptr, sizeof(kMaxEventfdValue)),
+ SyscallFailsWithErrno(EINTR));
+
+ // Signal should have been handled.
+ EXPECT_EQ(signaled, 1);
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc
index 11fcec443..e19a83413 100644
--- a/test/syscalls/linux/socket_inet_loopback.cc
+++ b/test/syscalls/linux/socket_inet_loopback.cc
@@ -350,6 +350,10 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownListen) {
sockaddr_storage conn_addr = connector.addr;
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+ // TODO(b/157236388): Remove Disable save after bug is fixed. S/R test can
+ // fail because the last socket may not be delivered to the accept queue
+ // by the time connect returns.
+ DisableSave ds;
for (int i = 0; i < kBacklog; i++) {
auto client = ASSERT_NO_ERRNO_AND_VALUE(
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
@@ -554,7 +558,11 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownWhileConnect) {
});
}
-TEST_P(SocketInetLoopbackTest, TCPbacklog) {
+// TODO(b/157236388): Remove _NoRandomSave once bug is fixed. Test fails w/
+// random save as established connections which can't be delivered to the accept
+// queue because the queue is full are not correctly delivered after restore
+// causing the last accept to timeout on the restore.
+TEST_P(SocketInetLoopbackTest, TCPbacklog_NoRandomSave) {
auto const& param = GetParam();
TestAddress const& listener = param.listener;
@@ -567,7 +575,8 @@ TEST_P(SocketInetLoopbackTest, TCPbacklog) {
ASSERT_THAT(bind(listen_fd.get(), reinterpret_cast<sockaddr*>(&listen_addr),
listener.addr_len),
SyscallSucceeds());
- ASSERT_THAT(listen(listen_fd.get(), 2), SyscallSucceeds());
+ constexpr int kBacklogSize = 2;
+ ASSERT_THAT(listen(listen_fd.get(), kBacklogSize), SyscallSucceeds());
// Get the port bound by the listening socket.
socklen_t addrlen = listener.addr_len;
@@ -931,7 +940,7 @@ void setupTimeWaitClose(const TestAddress* listener,
}
// shutdown to trigger TIME_WAIT.
- ASSERT_THAT(shutdown(active_closefd.get(), SHUT_RDWR), SyscallSucceeds());
+ ASSERT_THAT(shutdown(active_closefd.get(), SHUT_WR), SyscallSucceeds());
{
const int kTimeout = 10000;
struct pollfd pfd = {
@@ -941,7 +950,8 @@ void setupTimeWaitClose(const TestAddress* listener,
ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
ASSERT_EQ(pfd.revents, POLLIN);
}
- ScopedThread t([&]() {
+ ASSERT_THAT(shutdown(passive_closefd.get(), SHUT_WR), SyscallSucceeds());
+ {
constexpr int kTimeout = 10000;
constexpr int16_t want_events = POLLHUP;
struct pollfd pfd = {
@@ -949,11 +959,8 @@ void setupTimeWaitClose(const TestAddress* listener,
.events = want_events,
};
ASSERT_THAT(poll(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
- });
+ }
- passive_closefd.reset();
- t.Join();
- active_closefd.reset();
// This sleep is needed to reduce flake to ensure that the passive-close
// ensures the state transitions to CLOSE from LAST_ACK.
absl::SleepFor(absl::Seconds(1));
@@ -1143,6 +1150,9 @@ TEST_P(SocketInetLoopbackTest, TCPAcceptAfterReset) {
sockaddr_storage conn_addr = connector.addr;
ASSERT_NO_ERRNO(SetAddrPort(connector.family(), &conn_addr, port));
+
+ // TODO(b/157236388): Reenable Cooperative S/R once bug is fixed.
+ DisableSave ds;
ASSERT_THAT(RetryEINTR(connect)(conn_fd.get(),
reinterpret_cast<sockaddr*>(&conn_addr),
connector.addr_len),
diff --git a/test/syscalls/linux/socket_ip_udp_generic.cc b/test/syscalls/linux/socket_ip_udp_generic.cc
index 3f2c0fdf2..f69f8f99f 100644
--- a/test/syscalls/linux/socket_ip_udp_generic.cc
+++ b/test/syscalls/linux/socket_ip_udp_generic.cc
@@ -472,5 +472,19 @@ TEST_P(UDPSocketPairTest, SetAndGetSocketLinger) {
EXPECT_EQ(0, memcmp(&sl, &got_linger, length));
}
+// Test getsockopt for SO_ACCEPTCONN on udp socket.
+TEST_P(UDPSocketPairTest, GetSocketAcceptConn) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(
+ getsockopt(sockets->first_fd(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
+
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_unbound.cc b/test/syscalls/linux/socket_ip_unbound.cc
index 8f7ccc868..029f1e872 100644
--- a/test/syscalls/linux/socket_ip_unbound.cc
+++ b/test/syscalls/linux/socket_ip_unbound.cc
@@ -454,23 +454,15 @@ TEST_P(IPUnboundSocketTest, SetReuseAddr) {
INSTANTIATE_TEST_SUITE_P(
IPUnboundSockets, IPUnboundSocketTest,
- ::testing::ValuesIn(VecCat<SocketKind>(VecCat<SocketKind>(
+ ::testing::ValuesIn(VecCat<SocketKind>(
ApplyVec<SocketKind>(IPv4UDPUnboundSocket,
- AllBitwiseCombinations(List<int>{SOCK_DGRAM},
- List<int>{0,
- SOCK_NONBLOCK})),
+ std::vector<int>{0, SOCK_NONBLOCK}),
ApplyVec<SocketKind>(IPv6UDPUnboundSocket,
- AllBitwiseCombinations(List<int>{SOCK_DGRAM},
- List<int>{0,
- SOCK_NONBLOCK})),
+ std::vector<int>{0, SOCK_NONBLOCK}),
ApplyVec<SocketKind>(IPv4TCPUnboundSocket,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{0,
- SOCK_NONBLOCK})),
+ std::vector{0, SOCK_NONBLOCK}),
ApplyVec<SocketKind>(IPv6TCPUnboundSocket,
- AllBitwiseCombinations(List<int>{SOCK_STREAM},
- List<int>{
- 0, SOCK_NONBLOCK}))))));
+ std::vector{0, SOCK_NONBLOCK}))));
} // namespace testing
} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ip_unbound_netlink.cc b/test/syscalls/linux/socket_ip_unbound_netlink.cc
new file mode 100644
index 000000000..6036bfcaf
--- /dev/null
+++ b/test/syscalls/linux/socket_ip_unbound_netlink.cc
@@ -0,0 +1,104 @@
+// Copyright 2019 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+
+#include <cstdio>
+#include <cstring>
+
+#include "gmock/gmock.h"
+#include "gtest/gtest.h"
+#include "test/syscalls/linux/ip_socket_test_util.h"
+#include "test/syscalls/linux/socket_netlink_route_util.h"
+#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/capability_util.h"
+#include "test/util/test_util.h"
+
+namespace gvisor {
+namespace testing {
+
+// Test fixture for tests that apply to pairs of IP sockets.
+using IPv6UnboundSocketTest = SimpleSocketTest;
+
+TEST_P(IPv6UnboundSocketTest, ConnectToBadLocalAddress_NoRandomSave) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+
+ // TODO(gvisor.dev/issue/4595): Addresses on net devices are not saved
+ // across save/restore.
+ DisableSave ds;
+
+ // Delete the loopback address from the loopback interface.
+ Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
+ EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET6,
+ /*prefixlen=*/128, &in6addr_loopback,
+ sizeof(in6addr_loopback)));
+ Cleanup defer_addr_removal =
+ Cleanup([loopback_link = std::move(loopback_link)] {
+ EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET6,
+ /*prefixlen=*/128, &in6addr_loopback,
+ sizeof(in6addr_loopback)));
+ });
+
+ TestAddress addr = V6Loopback();
+ reinterpret_cast<sockaddr_in6*>(&addr.addr)->sin6_port = 65535;
+ auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+ EXPECT_THAT(connect(sock->get(), reinterpret_cast<sockaddr*>(&addr.addr),
+ addr.addr_len),
+ SyscallFailsWithErrno(EADDRNOTAVAIL));
+}
+
+INSTANTIATE_TEST_SUITE_P(IPUnboundSockets, IPv6UnboundSocketTest,
+ ::testing::ValuesIn(std::vector<SocketKind>{
+ IPv6UDPUnboundSocket(0),
+ IPv6TCPUnboundSocket(0)}));
+
+using IPv4UnboundSocketTest = SimpleSocketTest;
+
+TEST_P(IPv4UnboundSocketTest, ConnectToBadLocalAddress_NoRandomSave) {
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+
+ // TODO(gvisor.dev/issue/4595): Addresses on net devices are not saved
+ // across save/restore.
+ DisableSave ds;
+
+ // Delete the loopback address from the loopback interface.
+ Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
+ struct in_addr laddr;
+ laddr.s_addr = htonl(INADDR_LOOPBACK);
+ EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/8, &laddr, sizeof(laddr)));
+ Cleanup defer_addr_removal = Cleanup(
+ [loopback_link = std::move(loopback_link), addr = std::move(laddr)] {
+ EXPECT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/8, &addr, sizeof(addr)));
+ });
+ TestAddress addr = V4Loopback();
+ reinterpret_cast<sockaddr_in*>(&addr.addr)->sin_port = 65535;
+ auto sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
+ EXPECT_THAT(connect(sock->get(), reinterpret_cast<sockaddr*>(&addr.addr),
+ addr.addr_len),
+ SyscallFailsWithErrno(EADDRNOTAVAIL));
+}
+
+INSTANTIATE_TEST_SUITE_P(IPUnboundSockets, IPv4UnboundSocketTest,
+ ::testing::ValuesIn(std::vector<SocketKind>{
+ IPv4UDPUnboundSocket(0),
+ IPv4TCPUnboundSocket(0)}));
+
+} // namespace testing
+} // namespace gvisor
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc
index a72c76c97..b3f54e7f6 100644
--- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc
@@ -28,6 +28,7 @@
#include "test/syscalls/linux/ip_socket_test_util.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/posix_error.h"
+#include "test/util/save_util.h"
#include "test/util/test_util.h"
namespace gvisor {
@@ -75,7 +76,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNoGroup) {
// Check that we did not receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -209,7 +210,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackAddr) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -265,7 +266,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackNic) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -321,7 +322,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddr) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -377,7 +378,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNic) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -437,7 +438,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrConnect) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -497,7 +498,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicConnect) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -553,7 +554,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelf) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -609,7 +610,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelf) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -669,7 +670,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelfConnect) {
// Check that we did not receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -727,7 +728,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfConnect) {
// Check that we did not receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -785,7 +786,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfAddrSelfNoLoop) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -845,7 +846,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastLoopbackIfNicSelfNoLoop) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
@@ -919,7 +920,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropAddr) {
// Check that we did not receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -977,7 +978,7 @@ TEST_P(IPv4UDPUnboundSocketTest, IpMulticastDropNic) {
// Check that we did not receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -1330,8 +1331,8 @@ TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionOnTwoSockets) {
// Check that we received the multicast packet on both sockets.
for (auto& sockets : socket_pairs) {
char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RecvMsgTimeout(sockets->second_fd(), recv_buf,
- sizeof(recv_buf), 1 /*timeout*/),
+ ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf),
+ 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1409,8 +1410,8 @@ TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionWhenDroppingMemberships) {
// Check that we received the multicast packet on both sockets.
for (auto& sockets : socket_pairs) {
char recv_buf[sizeof(send_buf)] = {};
- ASSERT_THAT(RecvMsgTimeout(sockets->second_fd(), recv_buf,
- sizeof(recv_buf), 1 /*timeout*/),
+ ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf),
+ 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1432,8 +1433,8 @@ TEST_P(IPv4UDPUnboundSocketTest, TestMcastReceptionWhenDroppingMemberships) {
char recv_buf[sizeof(send_buf)] = {};
for (auto& sockets : socket_pairs) {
- ASSERT_THAT(RecvMsgTimeout(sockets->second_fd(), recv_buf,
- sizeof(recv_buf), 1 /*timeout*/),
+ ASSERT_THAT(RecvTimeout(sockets->second_fd(), recv_buf, sizeof(recv_buf),
+ 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
}
@@ -1486,7 +1487,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenJoinThenReceive) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1530,7 +1531,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenNoJoinThenNoReceive) {
// Check that we don't receive the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
PosixErrorIs(EAGAIN, ::testing::_));
}
@@ -1580,7 +1581,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToMcastThenSend) {
// Check that we received the packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1627,7 +1628,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenReceive) {
// Check that we received the multicast packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1678,7 +1679,7 @@ TEST_P(IPv4UDPUnboundSocketTest, TestBindToBcastThenSend) {
// Check that we received the packet.
char recv_buf[sizeof(send_buf)] = {};
ASSERT_THAT(
- RecvMsgTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(socket2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(recv_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1737,7 +1738,7 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrDistribution_NoRandomSave) {
// of the other sockets to have received it, but we will check that later.
char recv_buf[sizeof(send_buf)] = {};
EXPECT_THAT(
- RecvMsgTimeout(last->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ RecvTimeout(last->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(send_buf)));
EXPECT_EQ(0, memcmp(send_buf, recv_buf, sizeof(send_buf)));
}
@@ -1745,9 +1746,9 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrDistribution_NoRandomSave) {
// Verify that no other messages were received.
for (auto& socket : sockets) {
char recv_buf[kMessageSize] = {};
- EXPECT_THAT(RecvMsgTimeout(socket->get(), recv_buf, sizeof(recv_buf),
- 1 /*timeout*/),
- PosixErrorIs(EAGAIN, ::testing::_));
+ EXPECT_THAT(
+ RecvTimeout(socket->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ PosixErrorIs(EAGAIN, ::testing::_));
}
}
@@ -2108,6 +2109,9 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrReusePortDistribution) {
constexpr int kMessageSize = 10;
+ // Saving during each iteration of the following loop is too expensive.
+ DisableSave ds;
+
for (int i = 0; i < 100; ++i) {
// Send a new message to the REUSEADDR/REUSEPORT group. We use a new socket
// each time so that a new ephemerial port will be used each time. This
@@ -2120,16 +2124,18 @@ TEST_P(IPv4UDPUnboundSocketTest, ReuseAddrReusePortDistribution) {
SyscallSucceedsWithValue(sizeof(send_buf)));
}
+ ds.reset();
+
// Check that both receivers got messages. This checks that we are using load
// balancing (REUSEPORT) instead of the most recently bound socket
// (REUSEADDR).
char recv_buf[kMessageSize] = {};
- EXPECT_THAT(RecvMsgTimeout(receiver1->get(), recv_buf, sizeof(recv_buf),
- 1 /*timeout*/),
- IsPosixErrorOkAndHolds(kMessageSize));
- EXPECT_THAT(RecvMsgTimeout(receiver2->get(), recv_buf, sizeof(recv_buf),
- 1 /*timeout*/),
- IsPosixErrorOkAndHolds(kMessageSize));
+ EXPECT_THAT(
+ RecvTimeout(receiver1->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ IsPosixErrorOkAndHolds(kMessageSize));
+ EXPECT_THAT(
+ RecvTimeout(receiver2->get(), recv_buf, sizeof(recv_buf), 1 /*timeout*/),
+ IsPosixErrorOkAndHolds(kMessageSize));
}
// Test that socket will receive packet info control message.
@@ -2193,8 +2199,8 @@ TEST_P(IPv4UDPUnboundSocketTest, SetAndReceiveIPPKTINFO) {
received_msg.msg_controllen = CMSG_LEN(cmsg_data_len);
received_msg.msg_control = received_cmsg_buf;
- ASSERT_THAT(RetryEINTR(recvmsg)(receiver->get(), &received_msg, 0),
- SyscallSucceedsWithValue(kDataLength));
+ ASSERT_THAT(RecvMsgTimeout(receiver->get(), &received_msg, 1 /*timeout*/),
+ IsPosixErrorOkAndHolds(kDataLength));
cmsghdr* cmsg = CMSG_FIRSTHDR(&received_msg);
ASSERT_NE(cmsg, nullptr);
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc
index 49a0f06d9..875016812 100644
--- a/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound_netlink.cc
@@ -40,17 +40,9 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, JoinSubnet) {
/*prefixlen=*/24, &addr, sizeof(addr)));
Cleanup defer_addr_removal = Cleanup(
[loopback_link = std::move(loopback_link), addr = std::move(addr)] {
- if (IsRunningOnGvisor()) {
- // TODO(gvisor.dev/issue/3921): Remove this once deleting addresses
- // via netlink is supported.
- EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET,
- /*prefixlen=*/24, &addr, sizeof(addr)),
- PosixErrorIs(EOPNOTSUPP, ::testing::_));
- } else {
- EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
- /*prefixlen=*/24, &addr,
- sizeof(addr)));
- }
+ EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr,
+ sizeof(addr)));
});
auto snd_sock = ASSERT_NO_ERRNO_AND_VALUE(NewSocket());
@@ -124,17 +116,9 @@ TEST_P(IPv4UDPUnboundSocketNetlinkTest, ReuseAddrSubnetDirectedBroadcast) {
24 /* prefixlen */, &addr, sizeof(addr)));
Cleanup defer_addr_removal = Cleanup(
[loopback_link = std::move(loopback_link), addr = std::move(addr)] {
- if (IsRunningOnGvisor()) {
- // TODO(gvisor.dev/issue/3921): Remove this once deleting addresses
- // via netlink is supported.
- EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET,
- /*prefixlen=*/24, &addr, sizeof(addr)),
- PosixErrorIs(EOPNOTSUPP, ::testing::_));
- } else {
- EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
- /*prefixlen=*/24, &addr,
- sizeof(addr)));
- }
+ EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr,
+ sizeof(addr)));
});
TestAddress broadcast_address("SubnetBroadcastAddress");
diff --git a/test/syscalls/linux/socket_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc
index 241ddad74..ee3c08770 100644
--- a/test/syscalls/linux/socket_netlink_route.cc
+++ b/test/syscalls/linux/socket_netlink_route.cc
@@ -511,53 +511,42 @@ TEST(NetlinkRouteTest, LookupAll) {
ASSERT_GT(count, 0);
}
-TEST(NetlinkRouteTest, AddAddr) {
+TEST(NetlinkRouteTest, AddAndRemoveAddr) {
SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_NET_ADMIN)));
+ // Don't do cooperative save/restore because netstack state is not restored.
+ // TODO(gvisor.dev/issue/4595): enable cooperative save tests.
+ const DisableSave ds;
Link loopback_link = ASSERT_NO_ERRNO_AND_VALUE(LoopbackLink());
- FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(NetlinkBoundSocket(NETLINK_ROUTE));
-
- struct request {
- struct nlmsghdr hdr;
- struct ifaddrmsg ifa;
- struct rtattr rtattr;
- struct in_addr addr;
- char pad[NLMSG_ALIGNTO + RTA_ALIGNTO];
- };
-
- struct request req = {};
- req.hdr.nlmsg_type = RTM_NEWADDR;
- req.hdr.nlmsg_seq = kSeq;
- req.ifa.ifa_family = AF_INET;
- req.ifa.ifa_prefixlen = 24;
- req.ifa.ifa_flags = 0;
- req.ifa.ifa_scope = 0;
- req.ifa.ifa_index = loopback_link.index;
- req.rtattr.rta_type = IFA_LOCAL;
- req.rtattr.rta_len = RTA_LENGTH(sizeof(req.addr));
- inet_pton(AF_INET, "10.0.0.1", &req.addr);
- req.hdr.nlmsg_len =
- NLMSG_LENGTH(sizeof(req.ifa)) + NLMSG_ALIGN(req.rtattr.rta_len);
+ struct in_addr addr;
+ ASSERT_EQ(inet_pton(AF_INET, "10.0.0.1", &addr), 1);
// Create should succeed, as no such address in kernel.
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_ACK;
- EXPECT_NO_ERRNO(
- NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len));
+ ASSERT_NO_ERRNO(LinkAddLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr, sizeof(addr)));
+
+ Cleanup defer_addr_removal = Cleanup(
+ [loopback_link = std::move(loopback_link), addr = std::move(addr)] {
+ // First delete should succeed, as address exists.
+ EXPECT_NO_ERRNO(LinkDelLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr,
+ sizeof(addr)));
+
+ // Second delete should fail, as address no longer exists.
+ EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr, sizeof(addr)),
+ PosixErrorIs(EADDRNOTAVAIL, ::testing::_));
+ });
// Replace an existing address should succeed.
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK;
- req.hdr.nlmsg_seq++;
- EXPECT_NO_ERRNO(
- NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len));
+ ASSERT_NO_ERRNO(LinkReplaceLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr, sizeof(addr)));
// Create exclusive should fail, as we created the address above.
- req.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK;
- req.hdr.nlmsg_seq++;
- EXPECT_THAT(
- NetlinkRequestAckOrError(fd, req.hdr.nlmsg_seq, &req, req.hdr.nlmsg_len),
- PosixErrorIs(EEXIST, ::testing::_));
+ EXPECT_THAT(LinkAddExclusiveLocalAddr(loopback_link.index, AF_INET,
+ /*prefixlen=*/24, &addr, sizeof(addr)),
+ PosixErrorIs(EEXIST, ::testing::_));
}
// GetRouteDump tests a RTM_GETROUTE + NLM_F_DUMP request.
diff --git a/test/syscalls/linux/socket_netlink_route_util.cc b/test/syscalls/linux/socket_netlink_route_util.cc
index 7a0bad4cb..46f749c7c 100644
--- a/test/syscalls/linux/socket_netlink_route_util.cc
+++ b/test/syscalls/linux/socket_netlink_route_util.cc
@@ -29,6 +29,8 @@ constexpr uint32_t kSeq = 12345;
// Types of address modifications that may be performed on an interface.
enum class LinkAddrModification {
kAdd,
+ kAddExclusive,
+ kReplace,
kDelete,
};
@@ -40,6 +42,14 @@ PosixError PopulateNlmsghdr(LinkAddrModification modification,
hdr->nlmsg_type = RTM_NEWADDR;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
return NoError();
+ case LinkAddrModification::kAddExclusive:
+ hdr->nlmsg_type = RTM_NEWADDR;
+ hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_EXCL | NLM_F_ACK;
+ return NoError();
+ case LinkAddrModification::kReplace:
+ hdr->nlmsg_type = RTM_NEWADDR;
+ hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_ACK;
+ return NoError();
case LinkAddrModification::kDelete:
hdr->nlmsg_type = RTM_DELADDR;
hdr->nlmsg_flags = NLM_F_REQUEST | NLM_F_ACK;
@@ -144,6 +154,18 @@ PosixError LinkAddLocalAddr(int index, int family, int prefixlen,
LinkAddrModification::kAdd);
}
+PosixError LinkAddExclusiveLocalAddr(int index, int family, int prefixlen,
+ const void* addr, int addrlen) {
+ return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen,
+ LinkAddrModification::kAddExclusive);
+}
+
+PosixError LinkReplaceLocalAddr(int index, int family, int prefixlen,
+ const void* addr, int addrlen) {
+ return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen,
+ LinkAddrModification::kReplace);
+}
+
PosixError LinkDelLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen) {
return LinkModifyLocalAddr(index, family, prefixlen, addr, addrlen,
diff --git a/test/syscalls/linux/socket_netlink_route_util.h b/test/syscalls/linux/socket_netlink_route_util.h
index e5badca70..eaa91ad79 100644
--- a/test/syscalls/linux/socket_netlink_route_util.h
+++ b/test/syscalls/linux/socket_netlink_route_util.h
@@ -39,10 +39,19 @@ PosixErrorOr<std::vector<Link>> DumpLinks();
// Returns the loopback link on the system. ENOENT if not found.
PosixErrorOr<Link> LoopbackLink();
-// LinkAddLocalAddr sets IFA_LOCAL attribute on the interface.
+// LinkAddLocalAddr adds a new IFA_LOCAL address to the interface.
PosixError LinkAddLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen);
+// LinkAddExclusiveLocalAddr adds a new IFA_LOCAL address with NLM_F_EXCL flag
+// to the interface.
+PosixError LinkAddExclusiveLocalAddr(int index, int family, int prefixlen,
+ const void* addr, int addrlen);
+
+// LinkReplaceLocalAddr replaces an IFA_LOCAL address on the interface.
+PosixError LinkReplaceLocalAddr(int index, int family, int prefixlen,
+ const void* addr, int addrlen);
+
// LinkDelLocalAddr removes IFA_LOCAL attribute on the interface.
PosixError LinkDelLocalAddr(int index, int family, int prefixlen,
const void* addr, int addrlen);
diff --git a/test/syscalls/linux/socket_test_util.cc b/test/syscalls/linux/socket_test_util.cc
index e11792309..a760581b5 100644
--- a/test/syscalls/linux/socket_test_util.cc
+++ b/test/syscalls/linux/socket_test_util.cc
@@ -753,8 +753,7 @@ PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size) {
return ret;
}
-PosixErrorOr<int> RecvMsgTimeout(int sock, char buf[], int buf_size,
- int timeout) {
+PosixErrorOr<int> RecvTimeout(int sock, char buf[], int buf_size, int timeout) {
fd_set rfd;
struct timeval to = {.tv_sec = timeout, .tv_usec = 0};
FD_ZERO(&rfd);
@@ -767,6 +766,19 @@ PosixErrorOr<int> RecvMsgTimeout(int sock, char buf[], int buf_size,
return ret;
}
+PosixErrorOr<int> RecvMsgTimeout(int sock, struct msghdr* msg, int timeout) {
+ fd_set rfd;
+ struct timeval to = {.tv_sec = timeout, .tv_usec = 0};
+ FD_ZERO(&rfd);
+ FD_SET(sock, &rfd);
+
+ int ret;
+ RETURN_ERROR_IF_SYSCALL_FAIL(ret = select(1, &rfd, NULL, NULL, &to));
+ RETURN_ERROR_IF_SYSCALL_FAIL(
+ ret = RetryEINTR(recvmsg)(sock, msg, MSG_DONTWAIT));
+ return ret;
+}
+
void RecvNoData(int sock) {
char data = 0;
struct iovec iov;
diff --git a/test/syscalls/linux/socket_test_util.h b/test/syscalls/linux/socket_test_util.h
index 468bc96e0..5e205339f 100644
--- a/test/syscalls/linux/socket_test_util.h
+++ b/test/syscalls/linux/socket_test_util.h
@@ -467,9 +467,12 @@ PosixError FreeAvailablePort(int port);
// SendMsg converts a buffer to an iovec and adds it to msg before sending it.
PosixErrorOr<int> SendMsg(int sock, msghdr* msg, char buf[], int buf_size);
-// RecvMsgTimeout calls select on sock with timeout and then calls recv on sock.
-PosixErrorOr<int> RecvMsgTimeout(int sock, char buf[], int buf_size,
- int timeout);
+// RecvTimeout calls select on sock with timeout and then calls recv on sock.
+PosixErrorOr<int> RecvTimeout(int sock, char buf[], int buf_size, int timeout);
+
+// RecvMsgTimeout calls select on sock with timeout and then calls recvmsg on
+// sock.
+PosixErrorOr<int> RecvMsgTimeout(int sock, msghdr* msg, int timeout);
// RecvNoData checks that no data is receivable on sock.
void RecvNoData(int sock);
diff --git a/test/syscalls/linux/socket_unix_stream.cc b/test/syscalls/linux/socket_unix_stream.cc
index 1edcb15a7..ad9c4bf37 100644
--- a/test/syscalls/linux/socket_unix_stream.cc
+++ b/test/syscalls/linux/socket_unix_stream.cc
@@ -121,6 +121,19 @@ TEST_P(StreamUnixSocketPairTest, SetAndGetSocketLinger) {
EXPECT_EQ(0, memcmp(&got_linger, &sl, length));
}
+TEST_P(StreamUnixSocketPairTest, GetSocketAcceptConn) {
+ auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
+
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(
+ getsockopt(sockets->first_fd(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceedsWithValue(0));
+
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
+
INSTANTIATE_TEST_SUITE_P(
AllUnixDomainSockets, StreamUnixSocketPairTest,
::testing::ValuesIn(IncludeReversals(VecCat<SocketPairKind>(
diff --git a/test/syscalls/linux/splice.cc b/test/syscalls/linux/splice.cc
index a1d2b9b11..c2369db54 100644
--- a/test/syscalls/linux/splice.cc
+++ b/test/syscalls/linux/splice.cc
@@ -26,9 +26,11 @@
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/util/file_descriptor.h"
+#include "test/util/signal_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
#include "test/util/thread_util.h"
+#include "test/util/timer_util.h"
namespace gvisor {
namespace testing {
@@ -772,6 +774,59 @@ TEST(SpliceTest, FromPipeToDevZero) {
SyscallSucceedsWithValue(0));
}
+static volatile int signaled = 0;
+void SigUsr1Handler(int sig, siginfo_t* info, void* context) { signaled = 1; }
+
+TEST(SpliceTest, ToPipeWithSmallCapacityDoesNotSpin_NoRandomSave) {
+ // Writes to a pipe that are less than PIPE_BUF must be atomic. This test
+ // creates a pipe with only 128 bytes of capacity (< PIPE_BUF) and checks that
+ // splicing to the pipe does not spin. See b/170743336.
+
+ // Create a file with one page of data.
+ std::vector<char> buf(kPageSize);
+ RandomizeBuffer(buf.data(), buf.size());
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
+ GetAbsoluteTestTmpdir(), absl::string_view(buf.data(), buf.size()),
+ TempPath::kDefaultFileMode));
+ auto fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
+
+ // Create a pipe with size 4096, and fill all but 128 bytes of it.
+ int p[2];
+ ASSERT_THAT(pipe(p), SyscallSucceeds());
+ ASSERT_THAT(fcntl(p[1], F_SETPIPE_SZ, kPageSize), SyscallSucceeds());
+ const int kWriteSize = kPageSize - 128;
+ std::vector<char> writeBuf(kWriteSize);
+ RandomizeBuffer(writeBuf.data(), writeBuf.size());
+ ASSERT_THAT(write(p[1], writeBuf.data(), writeBuf.size()),
+ SyscallSucceedsWithValue(kWriteSize));
+
+ // Set up signal handler.
+ struct sigaction sa = {};
+ sa.sa_sigaction = SigUsr1Handler;
+ sa.sa_flags = SA_SIGINFO;
+ const auto cleanup_sigact =
+ ASSERT_NO_ERRNO_AND_VALUE(ScopedSigaction(SIGUSR1, sa));
+
+ // Send SIGUSR1 to this thread in 1 second.
+ struct sigevent sev = {};
+ sev.sigev_notify = SIGEV_THREAD_ID;
+ sev.sigev_signo = SIGUSR1;
+ sev.sigev_notify_thread_id = gettid();
+ auto timer = ASSERT_NO_ERRNO_AND_VALUE(TimerCreate(CLOCK_MONOTONIC, sev));
+ struct itimerspec its = {};
+ its.it_value = absl::ToTimespec(absl::Seconds(1));
+ DisableSave ds; // Asserting an EINTR.
+ ASSERT_NO_ERRNO(timer.Set(0, its));
+
+ // Now splice the file to the pipe. This should block, but not spin, and
+ // should return EINTR because it is interrupted by the signal.
+ EXPECT_THAT(splice(fd.get(), nullptr, p[1], nullptr, kPageSize, 0),
+ SyscallFailsWithErrno(EINTR));
+
+ // Alarm should have been handled.
+ EXPECT_EQ(signaled, 1);
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/stat.cc b/test/syscalls/linux/stat.cc
index 92260b1e1..6e7142a42 100644
--- a/test/syscalls/linux/stat.cc
+++ b/test/syscalls/linux/stat.cc
@@ -31,6 +31,7 @@
#include "test/util/cleanup.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
+#include "test/util/save_util.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
@@ -328,7 +329,10 @@ TEST_F(StatTest, LeadingDoubleSlash) {
ASSERT_THAT(lstat(double_slash_path.c_str(), &double_slash_st),
SyscallSucceeds());
EXPECT_EQ(st.st_dev, double_slash_st.st_dev);
- EXPECT_EQ(st.st_ino, double_slash_st.st_ino);
+ // Inode numbers for gofer-accessed files may change across save/restore.
+ if (!IsRunningWithSaveRestore()) {
+ EXPECT_EQ(st.st_ino, double_slash_st.st_ino);
+ }
}
// Test that a rename doesn't change the underlying file.
@@ -346,8 +350,14 @@ TEST_F(StatTest, StatDoesntChangeAfterRename) {
EXPECT_EQ(st_old.st_nlink, st_new.st_nlink);
EXPECT_EQ(st_old.st_dev, st_new.st_dev);
+ // Inode numbers for gofer-accessed files on which no reference is held may
+ // change across save/restore because the information that the gofer client
+ // uses to track file identity (9P QID path) is inconsistent between gofer
+ // processes, which are restarted across save/restore.
+ //
// Overlay filesystems may synthesize directory inode numbers on the fly.
- if (!ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))) {
+ if (!IsRunningWithSaveRestore() &&
+ !ASSERT_NO_ERRNO_AND_VALUE(IsOverlayfs(GetAbsoluteTestTmpdir()))) {
EXPECT_EQ(st_old.st_ino, st_new.st_ino);
}
EXPECT_EQ(st_old.st_mode, st_new.st_mode);
@@ -541,6 +551,26 @@ TEST_F(StatTest, LstatELOOPPath) {
ASSERT_THAT(lstat(path.c_str(), &s), SyscallFailsWithErrno(ELOOP));
}
+TEST(SimpleStatTest, DifferentFilesHaveDifferentDeviceInodeNumberPairs) {
+ // TODO(gvisor.dev/issue/1624): This test case fails in VFS1 save/restore
+ // tests because VFS1 gofer inode number assignment restarts after
+ // save/restore, such that the inodes for file1 and file2 (which are
+ // unreferenced and therefore not retained in sentry checkpoints before the
+ // calls to lstat()) are assigned the same inode number.
+ SKIP_IF(IsRunningWithVFS1() && IsRunningWithSaveRestore());
+
+ TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ TempPath file2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+
+ MaybeSave();
+ struct stat st1 = ASSERT_NO_ERRNO_AND_VALUE(Lstat(file1.path()));
+ MaybeSave();
+ struct stat st2 = ASSERT_NO_ERRNO_AND_VALUE(Lstat(file2.path()));
+ EXPECT_FALSE(st1.st_dev == st2.st_dev && st1.st_ino == st2.st_ino)
+ << "both files have device number " << st1.st_dev << " and inode number "
+ << st1.st_ino;
+}
+
// Ensure that inode allocation for anonymous devices work correctly across
// save/restore. In particular, inode numbers should be unique across S/R.
TEST(SimpleStatTest, AnonDeviceAllocatesUniqueInodesAcrossSaveRestore) {
diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc
index 9f522f833..ebd873068 100644
--- a/test/syscalls/linux/tcp_socket.cc
+++ b/test/syscalls/linux/tcp_socket.cc
@@ -1725,6 +1725,63 @@ TEST_P(SimpleTcpSocketTest, CloseNonConnectedLingerOption) {
ASSERT_LT((end_time - start_time), absl::Seconds(kLingerTimeout));
}
+// Tests that SO_ACCEPTCONN returns non zero value for listening sockets.
+TEST_P(TcpSocketTest, GetSocketAcceptConnListener) {
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(getsockopt(listener_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceeds());
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 1);
+}
+
+// Tests that SO_ACCEPTCONN returns zero value for not listening sockets.
+TEST_P(TcpSocketTest, GetSocketAcceptConnNonListener) {
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceeds());
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+
+ ASSERT_THAT(getsockopt(t_, SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceeds());
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
+
+TEST_P(SimpleTcpSocketTest, GetSocketAcceptConnWithShutdown) {
+ // TODO(b/171345701): Fix the TCP state for listening socket on shutdown.
+ SKIP_IF(IsRunningOnGvisor());
+
+ FileDescriptor s =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_STREAM, IPPROTO_TCP));
+
+ // Initialize address to the loopback one.
+ sockaddr_storage addr =
+ ASSERT_NO_ERRNO_AND_VALUE(InetLoopbackAddr(GetParam()));
+ socklen_t addrlen = sizeof(addr);
+
+ // Bind to some port then start listening.
+ ASSERT_THAT(bind(s.get(), reinterpret_cast<struct sockaddr*>(&addr), addrlen),
+ SyscallSucceeds());
+
+ ASSERT_THAT(listen(s.get(), SOMAXCONN), SyscallSucceeds());
+
+ int got = -1;
+ socklen_t length = sizeof(got);
+ ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceeds());
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 1);
+
+ EXPECT_THAT(shutdown(s.get(), SHUT_RD), SyscallSucceeds());
+ ASSERT_THAT(getsockopt(s.get(), SOL_SOCKET, SO_ACCEPTCONN, &got, &length),
+ SyscallSucceeds());
+ ASSERT_EQ(length, sizeof(got));
+ EXPECT_EQ(got, 0);
+}
+
INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest,
::testing::Values(AF_INET, AF_INET6));
diff --git a/test/syscalls/linux/timers.cc b/test/syscalls/linux/timers.cc
index cac94d9e1..93a98adb1 100644
--- a/test/syscalls/linux/timers.cc
+++ b/test/syscalls/linux/timers.cc
@@ -322,11 +322,6 @@ TEST(IntervalTimerTest, PeriodicGroupDirectedSignal) {
EXPECT_GE(counted_signals.load(), kCycles);
}
-// From Linux's include/uapi/asm-generic/siginfo.h.
-#ifndef sigev_notify_thread_id
-#define sigev_notify_thread_id _sigev_un._tid
-#endif
-
TEST(IntervalTimerTest, PeriodicThreadDirectedSignal) {
constexpr int kSigno = SIGUSR1;
constexpr int kSigvalue = 42;
diff --git a/test/syscalls/linux/udp_socket.cc b/test/syscalls/linux/udp_socket.cc
index 1a7673317..d65275fd3 100644
--- a/test/syscalls/linux/udp_socket.cc
+++ b/test/syscalls/linux/udp_socket.cc
@@ -679,6 +679,43 @@ TEST_P(UdpSocketTest, SendToAddressOtherThanConnected) {
SyscallSucceedsWithValue(sizeof(buf)));
}
+TEST_P(UdpSocketTest, ConnectAndSendNoReceiver) {
+ ASSERT_NO_ERRNO(BindLoopback());
+ // Close the socket to release the port so that we get an ICMP error.
+ ASSERT_THAT(close(bind_.release()), SyscallSucceeds());
+
+ // Connect to loopback:bind_addr_ which should *hopefully* not be bound by an
+ // UDP socket. There is no easy way to ensure that the UDP port is not bound
+ // by another conncurrently running test. *This is potentially flaky*.
+ ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
+
+ char buf[512];
+ EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
+ SyscallSucceedsWithValue(sizeof(buf)));
+
+ constexpr int kTimeout = 1000;
+ // Poll to make sure we get the ICMP error back before issuing more writes.
+ struct pollfd pfd = {sock_.get(), POLLERR, 0};
+ ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
+
+ // Next write should fail with ECONNREFUSED due to the ICMP error generated in
+ // response to the previous write.
+ ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
+ SyscallFailsWithErrno(ECONNREFUSED));
+
+ // The next write should succeed again since the last write call would have
+ // retrieved and cleared the socket error.
+ ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0), SyscallSucceeds());
+
+ // Poll to make sure we get the ICMP error back before issuing more writes.
+ ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kTimeout), SyscallSucceedsWithValue(1));
+
+ // Next write should fail with ECONNREFUSED due to the ICMP error generated in
+ // response to the previous write.
+ ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
+ SyscallFailsWithErrno(ECONNREFUSED));
+}
+
TEST_P(UdpSocketTest, ZerolengthWriteAllowed) {
// TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes.
SKIP_IF(IsRunningWithHostinet());
@@ -838,7 +875,7 @@ TEST_P(UdpSocketTest, ReceiveBeforeConnect) {
// Receive the data. It works because it was sent before the connect.
char received[sizeof(buf)];
EXPECT_THAT(
- RecvMsgTimeout(bind_.get(), received, sizeof(received), 1 /*timeout*/),
+ RecvTimeout(bind_.get(), received, sizeof(received), 1 /*timeout*/),
IsPosixErrorOkAndHolds(sizeof(received)));
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
@@ -928,9 +965,8 @@ TEST_P(UdpSocketTest, ReadShutdownNonblockPendingData) {
SyscallSucceedsWithValue(1));
// We should get the data even though read has been shutdown.
- EXPECT_THAT(
- RecvMsgTimeout(bind_.get(), received, 2 /*buf_size*/, 1 /*timeout*/),
- IsPosixErrorOkAndHolds(2));
+ EXPECT_THAT(RecvTimeout(bind_.get(), received, 2 /*buf_size*/, 1 /*timeout*/),
+ IsPosixErrorOkAndHolds(2));
// Because we read less than the entire packet length, since it's a packet
// based socket any subsequent reads should return EWOULDBLOCK.
@@ -1698,8 +1734,8 @@ TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) {
sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
SyscallSucceedsWithValue(buf.size()));
std::vector<char> received(buf.size());
- EXPECT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(),
- 1 /*timeout*/),
+ EXPECT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(),
+ 1 /*timeout*/),
IsPosixErrorOkAndHolds(received.size()));
}
@@ -1714,8 +1750,8 @@ TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) {
SyscallSucceedsWithValue(buf.size()));
std::vector<char> received(buf.size());
- ASSERT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(),
- 1 /*timeout*/),
+ ASSERT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(),
+ 1 /*timeout*/),
IsPosixErrorOkAndHolds(received.size()));
}
}
@@ -1785,8 +1821,8 @@ TEST_P(UdpSocketTest, RecvBufLimits) {
for (int i = 0; i < sent - 1; i++) {
// Receive the data.
std::vector<char> received(buf.size());
- EXPECT_THAT(RecvMsgTimeout(bind_.get(), received.data(), received.size(),
- 1 /*timeout*/),
+ EXPECT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(),
+ 1 /*timeout*/),
IsPosixErrorOkAndHolds(received.size()));
EXPECT_EQ(memcmp(buf.data(), received.data(), buf.size()), 0);
}
@@ -1851,6 +1887,22 @@ TEST_P(UdpSocketTest, GetSocketDetachFilter) {
SyscallFailsWithErrno(ENOPROTOOPT));
}
+TEST_P(UdpSocketTest, SendToZeroPort) {
+ char buf[8];
+ struct sockaddr_storage addr = InetLoopbackAddr();
+
+ // Sending to an invalid port should fail.
+ SetPort(&addr, 0);
+ EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0,
+ reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
+ SyscallFailsWithErrno(EINVAL));
+
+ SetPort(&addr, 1234);
+ EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0,
+ reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr)),
+ SyscallSucceedsWithValue(sizeof(buf)));
+}
+
INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketTest,
::testing::Values(AddressFamily::kIpv4,
AddressFamily::kIpv6,
diff --git a/test/util/BUILD b/test/util/BUILD
index 26c2b6a2f..1b028a477 100644
--- a/test/util/BUILD
+++ b/test/util/BUILD
@@ -155,6 +155,10 @@ cc_library(
],
hdrs = ["save_util.h"],
defines = select_system(),
+ deps = [
+ ":logging",
+ "@com_google_absl//absl/types:optional",
+ ],
)
cc_library(
diff --git a/test/util/posix_error.cc b/test/util/posix_error.cc
index cebf7e0ac..deed0c05b 100644
--- a/test/util/posix_error.cc
+++ b/test/util/posix_error.cc
@@ -87,7 +87,7 @@ bool PosixErrorIsMatcherCommonImpl::MatchAndExplain(
return false;
}
- if (!message_matcher_.Matches(error.error_message())) {
+ if (!message_matcher_.Matches(error.message())) {
return false;
}
diff --git a/test/util/posix_error.h b/test/util/posix_error.h
index ad666bce0..b634a7f78 100644
--- a/test/util/posix_error.h
+++ b/test/util/posix_error.h
@@ -26,11 +26,6 @@
namespace gvisor {
namespace testing {
-class PosixErrorIsMatcherCommonImpl;
-
-template <typename T>
-class PosixErrorOr;
-
class ABSL_MUST_USE_RESULT PosixError {
public:
PosixError() {}
@@ -49,7 +44,8 @@ class ABSL_MUST_USE_RESULT PosixError {
// PosixErrorOr.
const PosixError& error() const { return *this; }
- std::string error_message() const { return msg_; }
+ int errno_value() const { return errno_; }
+ std::string message() const { return msg_; }
// ToString produces a full string representation of this posix error
// including the printable representation of the errno and the error message.
@@ -61,14 +57,8 @@ class ABSL_MUST_USE_RESULT PosixError {
void IgnoreError() const {}
private:
- int errno_value() const { return errno_; }
int errno_ = 0;
std::string msg_;
-
- friend class PosixErrorIsMatcherCommonImpl;
-
- template <typename T>
- friend class PosixErrorOr;
};
template <typename T>
@@ -94,15 +84,12 @@ class ABSL_MUST_USE_RESULT PosixErrorOr {
template <typename U>
PosixErrorOr& operator=(PosixErrorOr<U> other);
- // Return a reference to the error or NoError().
- PosixError error() const;
-
- // Returns this->error().error_message();
- std::string error_message() const;
-
// Returns true if this PosixErrorOr contains some T.
bool ok() const;
+ // Return a copy of the contained PosixError or NoError().
+ PosixError error() const;
+
// Returns a reference to our current value, or CHECK-fails if !this->ok().
const T& ValueOrDie() const&;
T& ValueOrDie() &;
@@ -115,7 +102,6 @@ class ABSL_MUST_USE_RESULT PosixErrorOr {
void IgnoreError() const {}
private:
- int errno_value() const;
absl::variant<T, PosixError> value_;
friend class PosixErrorIsMatcherCommonImpl;
@@ -171,16 +157,6 @@ PosixError PosixErrorOr<T>::error() const {
}
template <typename T>
-int PosixErrorOr<T>::errno_value() const {
- return error().errno_value();
-}
-
-template <typename T>
-std::string PosixErrorOr<T>::error_message() const {
- return error().error_message();
-}
-
-template <typename T>
bool PosixErrorOr<T>::ok() const {
return absl::holds_alternative<T>(value_);
}
diff --git a/test/util/save_util.cc b/test/util/save_util.cc
index 384d626f0..59d47e06e 100644
--- a/test/util/save_util.cc
+++ b/test/util/save_util.cc
@@ -21,35 +21,47 @@
#include <atomic>
#include <cerrno>
-#define GVISOR_COOPERATIVE_SAVE_TEST "GVISOR_COOPERATIVE_SAVE_TEST"
+#include "absl/types/optional.h"
namespace gvisor {
namespace testing {
namespace {
-enum class CooperativeSaveMode {
- kUnknown = 0, // cooperative_save_mode is statically-initialized to 0
- kAvailable,
- kNotAvailable,
-};
-
-std::atomic<CooperativeSaveMode> cooperative_save_mode;
-
-bool CooperativeSaveEnabled() {
- auto mode = cooperative_save_mode.load();
- if (mode == CooperativeSaveMode::kUnknown) {
- mode = (getenv(GVISOR_COOPERATIVE_SAVE_TEST) != nullptr)
- ? CooperativeSaveMode::kAvailable
- : CooperativeSaveMode::kNotAvailable;
- cooperative_save_mode.store(mode);
+std::atomic<absl::optional<bool>> cooperative_save_present;
+std::atomic<absl::optional<bool>> random_save_present;
+
+bool CooperativeSavePresent() {
+ auto present = cooperative_save_present.load();
+ if (!present.has_value()) {
+ present = getenv("GVISOR_COOPERATIVE_SAVE_TEST") != nullptr;
+ cooperative_save_present.store(present);
+ }
+ return present.value();
+}
+
+bool RandomSavePresent() {
+ auto present = random_save_present.load();
+ if (!present.has_value()) {
+ present = getenv("GVISOR_RANDOM_SAVE_TEST") != nullptr;
+ random_save_present.store(present);
}
- return mode == CooperativeSaveMode::kAvailable;
+ return present.value();
}
std::atomic<int> save_disable;
} // namespace
+bool IsRunningWithSaveRestore() {
+ return CooperativeSavePresent() || RandomSavePresent();
+}
+
+void MaybeSave() {
+ if (CooperativeSavePresent() && save_disable.load() == 0) {
+ internal::DoCooperativeSave();
+ }
+}
+
DisableSave::DisableSave() { save_disable++; }
DisableSave::~DisableSave() { reset(); }
@@ -61,11 +73,5 @@ void DisableSave::reset() {
}
}
-namespace internal {
-bool ShouldSave() {
- return CooperativeSaveEnabled() && (save_disable.load() == 0);
-}
-} // namespace internal
-
} // namespace testing
} // namespace gvisor
diff --git a/test/util/save_util.h b/test/util/save_util.h
index bddad6120..e7218ae88 100644
--- a/test/util/save_util.h
+++ b/test/util/save_util.h
@@ -17,9 +17,17 @@
namespace gvisor {
namespace testing {
-// Disable save prevents saving while the given function executes.
+
+// Returns true if the environment in which the calling process is executing
+// allows the test to be checkpointed and restored during execution.
+bool IsRunningWithSaveRestore();
+
+// May perform a co-operative save cycle.
//
-// This lasts the duration of the object, unless reset is called.
+// errno is guaranteed to be preserved.
+void MaybeSave();
+
+// Causes MaybeSave to become a no-op until destroyed or reset.
class DisableSave {
public:
DisableSave();
@@ -37,13 +45,13 @@ class DisableSave {
bool reset_ = false;
};
-// May perform a co-operative save cycle.
+namespace internal {
+
+// Causes a co-operative save cycle to occur.
//
// errno is guaranteed to be preserved.
-void MaybeSave();
+void DoCooperativeSave();
-namespace internal {
-bool ShouldSave();
} // namespace internal
} // namespace testing
diff --git a/test/util/save_util_linux.cc b/test/util/save_util_linux.cc
index fbac94912..57431b3ea 100644
--- a/test/util/save_util_linux.cc
+++ b/test/util/save_util_linux.cc
@@ -30,19 +30,19 @@
namespace gvisor {
namespace testing {
-
-void MaybeSave() {
- if (internal::ShouldSave()) {
- int orig_errno = errno;
- // We use it to trigger saving the sentry state
- // when this syscall is called.
- // Notice: this needs to be a valid syscall
- // that is not used in any of the syscall tests.
- syscall(SYS_TRIGGER_SAVE, nullptr, 0);
- errno = orig_errno;
- }
+namespace internal {
+
+void DoCooperativeSave() {
+ int orig_errno = errno;
+ // We use it to trigger saving the sentry state
+ // when this syscall is called.
+ // Notice: this needs to be a valid syscall
+ // that is not used in any of the syscall tests.
+ syscall(SYS_TRIGGER_SAVE, nullptr, 0);
+ errno = orig_errno;
}
+} // namespace internal
} // namespace testing
} // namespace gvisor
diff --git a/test/util/save_util_other.cc b/test/util/save_util_other.cc
index 931af2c29..7749ded76 100644
--- a/test/util/save_util_other.cc
+++ b/test/util/save_util_other.cc
@@ -14,13 +14,17 @@
#ifndef __linux__
+#include "test/util/logging.h"
+
namespace gvisor {
namespace testing {
+namespace internal {
-void MaybeSave() {
- // Saving is never available in a non-linux environment.
+void DoCooperativeSave() {
+ TEST_CHECK_MSG(false, "DoCooperativeSave not implemented");
}
+} // namespace internal
} // namespace testing
} // namespace gvisor
diff --git a/test/util/signal_util.h b/test/util/signal_util.h
index e7b66aa51..20eebd7e4 100644
--- a/test/util/signal_util.h
+++ b/test/util/signal_util.h
@@ -88,7 +88,7 @@ inline void FixupFault(ucontext_t* ctx) {
#elif __aarch64__
inline void Fault() {
// Zero and dereference x0.
- asm("mov xzr, x0\r\n"
+ asm("mov x0, xzr\r\n"
"str xzr, [x0]\r\n"
:
:
diff --git a/test/util/timer_util.h b/test/util/timer_util.h
index 926e6632f..e389108ef 100644
--- a/test/util/timer_util.h
+++ b/test/util/timer_util.h
@@ -33,6 +33,11 @@
namespace gvisor {
namespace testing {
+// From Linux's include/uapi/asm-generic/siginfo.h.
+#ifndef sigev_notify_thread_id
+#define sigev_notify_thread_id _sigev_un._tid
+#endif
+
// Returns the current time.
absl::Time Now(clockid_t id);