summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/benchmarks/base/BUILD2
-rw-r--r--test/fuse/linux/stat_test.cc10
-rw-r--r--test/iptables/README.md34
-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/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/root/oom_score_adj_test.go12
-rw-r--r--test/runner/defs.bzl2
-rw-r--r--test/runtimes/exclude/php7.3.6.csv3
-rw-r--r--test/runtimes/exclude/python3.7.3.csv1
-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/BUILD27
-rw-r--r--test/syscalls/linux/ptrace.cc43
-rw-r--r--test/syscalls/linux/semaphore.cc231
-rw-r--r--test/syscalls/linux/sendfile.cc53
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc10
-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_netlink_route.cc2
-rw-r--r--test/syscalls/linux/splice.cc55
-rw-r--r--test/syscalls/linux/timers.cc5
-rw-r--r--test/syscalls/linux/udp_socket.cc16
-rw-r--r--test/util/timer_util.h5
28 files changed, 950 insertions, 192 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/fuse/linux/stat_test.cc b/test/fuse/linux/stat_test.cc
index 6f032cac1..73321592b 100644
--- a/test/fuse/linux/stat_test.cc
+++ b/test/fuse/linux/stat_test.cc
@@ -76,8 +76,13 @@ TEST_F(StatTest, StatNormal) {
// Check filesystem operation result.
struct stat expected_stat = {
.st_ino = attr.ino,
+#ifdef __aarch64__
+ .st_mode = expected_mode,
+ .st_nlink = attr.nlink,
+#else
.st_nlink = attr.nlink,
.st_mode = expected_mode,
+#endif
.st_uid = attr.uid,
.st_gid = attr.gid,
.st_rdev = attr.rdev,
@@ -152,8 +157,13 @@ TEST_F(StatTest, FstatNormal) {
// Check filesystem operation result.
struct stat expected_stat = {
.st_ino = attr.ino,
+#ifdef __aarch64__
+ .st_mode = expected_mode,
+ .st_nlink = attr.nlink,
+#else
.st_nlink = attr.nlink,
.st_mode = expected_mode,
+#endif
.st_uid = attr.uid,
.st_gid = attr.gid,
.st_rdev = attr.rdev,
diff --git a/test/iptables/README.md b/test/iptables/README.md
index 28ab195ca..1196f8eb5 100644
--- a/test/iptables/README.md
+++ b/test/iptables/README.md
@@ -2,8 +2,38 @@
iptables tests are run via `make iptables-tests`.
-iptables requires raw socket support, so you must add the `--net-raw=true` flag
-to `/etc/docker/daemon.json` in order to use it.
+iptables require some extra Docker configuration to work. Enable IPv6 in
+`/etc/docker/daemon.json` (make sure to restart Docker if you change this file):
+
+```json
+{
+ "experimental": true,
+ "fixed-cidr-v6": "2001:db8:1::/64",
+ "ipv6": true,
+ // Runtimes and other Docker config...
+}
+```
+
+And if you're running manually (i.e. not using the `make` target), you'll need
+to:
+
+* Enable iptables via `modprobe iptables_filter && modprobe ip6table_filter`.
+* Enable `--net-raw` in your chosen runtime in `/etc/docker/daemon.json` (make
+ sure to restart Docker if you change this file).
+
+The resulting runtime should look something like this:
+
+```json
+"runsc": {
+ "path": "/tmp/iptables/runsc",
+ "runtimeArgs": [
+ "--debug-log",
+ "/tmp/iptables/logs/runsc.log.%TEST%.%TIMESTAMP%.%COMMAND%",
+ "--net-raw"
+ ]
+},
+// ...
+```
## Test Structure
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/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/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/runtimes/exclude/php7.3.6.csv b/test/runtimes/exclude/php7.3.6.csv
index f984a579a..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,,
@@ -29,7 +30,7 @@ ext/standard/tests/file/php_fd_wrapper_04.phpt,,
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
diff --git a/test/runtimes/exclude/python3.7.3.csv b/test/runtimes/exclude/python3.7.3.csv
index 911f22855..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.
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 c94c1d5bd..2350f7e69 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -1802,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",
@@ -2102,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",
],
)
@@ -2125,6 +2131,7 @@ cc_binary(
"//test/util:test_main",
"//test/util:test_util",
"//test/util:thread_util",
+ "//test/util:timer_util",
],
)
@@ -2138,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",
],
)
@@ -2880,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 = [
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/semaphore.cc b/test/syscalls/linux/semaphore.cc
index ed6a1c2aa..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>
@@ -542,6 +543,236 @@ TEST(SemaphoreTest, SemCtlIpcStat) {
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 39a68c5a5..e19a83413 100644
--- a/test/syscalls/linux/socket_inet_loopback.cc
+++ b/test/syscalls/linux/socket_inet_loopback.cc
@@ -940,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 = {
@@ -950,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 = {
@@ -958,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));
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_netlink_route.cc b/test/syscalls/linux/socket_netlink_route.cc
index e83f0d81f..ee3c08770 100644
--- a/test/syscalls/linux/socket_netlink_route.cc
+++ b/test/syscalls/linux/socket_netlink_route.cc
@@ -536,7 +536,7 @@ TEST(NetlinkRouteTest, AddAndRemoveAddr) {
// Second delete should fail, as address no longer exists.
EXPECT_THAT(LinkDelLocalAddr(loopback_link.index, AF_INET,
/*prefixlen=*/24, &addr, sizeof(addr)),
- PosixErrorIs(EINVAL, ::testing::_));
+ PosixErrorIs(EADDRNOTAVAIL, ::testing::_));
});
// Replace an existing address should succeed.
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/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 bc5bd9218..d65275fd3 100644
--- a/test/syscalls/linux/udp_socket.cc
+++ b/test/syscalls/linux/udp_socket.cc
@@ -1887,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/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);