summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/fsstress/fsstress_test.go2
-rw-r--r--test/iptables/filter_input.go198
-rw-r--r--test/iptables/iptables_test.go24
-rw-r--r--test/iptables/iptables_util.go2
-rw-r--r--test/packetimpact/runner/defs.bzl4
-rw-r--r--test/packetimpact/tests/BUILD14
-rw-r--r--test/packetimpact/tests/tcp_rack_test.go221
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go28
-rw-r--r--test/syscalls/linux/BUILD3
-rw-r--r--test/syscalls/linux/fcntl.cc199
-rw-r--r--test/syscalls/linux/inotify.cc94
-rw-r--r--test/syscalls/linux/packet_socket_raw.cc8
-rw-r--r--test/syscalls/linux/raw_socket.cc8
-rw-r--r--test/syscalls/linux/shm.cc28
-rw-r--r--test/syscalls/linux/socket.cc75
-rw-r--r--test/syscalls/linux/socket_ipv4_udp_unbound.cc6
-rw-r--r--test/syscalls/linux/socket_unix_cmsg.cc2
-rw-r--r--test/syscalls/linux/tcp_socket.cc23
-rw-r--r--test/util/BUILD1
-rw-r--r--test/util/multiprocess_util.cc3
20 files changed, 863 insertions, 80 deletions
diff --git a/test/fsstress/fsstress_test.go b/test/fsstress/fsstress_test.go
index 63f692ca9..300c21ceb 100644
--- a/test/fsstress/fsstress_test.go
+++ b/test/fsstress/fsstress_test.go
@@ -41,7 +41,7 @@ func fsstress(t *testing.T, dir string) {
image = "basic/fsstress"
)
seed := strconv.FormatUint(uint64(rand.Uint32()), 10)
- args := []string{"-d", dir, "-n", operations, "-p", processes, "-seed", seed, "-X"}
+ args := []string{"-d", dir, "-n", operations, "-p", processes, "-s", seed, "-X"}
t.Logf("Repro: docker run --rm --runtime=runsc %s %s", image, strings.Join(args, ""))
out, err := d.Run(ctx, dockerutil.RunOpts{Image: image}, args...)
if err != nil {
diff --git a/test/iptables/filter_input.go b/test/iptables/filter_input.go
index 37a1a6694..c47660026 100644
--- a/test/iptables/filter_input.go
+++ b/test/iptables/filter_input.go
@@ -51,6 +51,12 @@ func init() {
RegisterTestCase(FilterInputInvertDestination{})
RegisterTestCase(FilterInputSource{})
RegisterTestCase(FilterInputInvertSource{})
+ RegisterTestCase(FilterInputInterfaceAccept{})
+ RegisterTestCase(FilterInputInterfaceDrop{})
+ RegisterTestCase(FilterInputInterface{})
+ RegisterTestCase(FilterInputInterfaceBeginsWith{})
+ RegisterTestCase(FilterInputInterfaceInvertDrop{})
+ RegisterTestCase(FilterInputInterfaceInvertAccept{})
}
// FilterInputDropUDP tests that we can drop UDP traffic.
@@ -744,3 +750,195 @@ func (FilterInputInvertSource) ContainerAction(ctx context.Context, ip net.IP, i
func (FilterInputInvertSource) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
return sendUDPLoop(ctx, ip, acceptPort)
}
+
+// FilterInputInterfaceAccept tests that packets are accepted from interface
+// matching the iptables rule.
+type FilterInputInterfaceAccept struct{ localCase }
+
+var _ TestCase = FilterInputInterfaceAccept{}
+
+// Name implements TestCase.Name.
+func (FilterInputInterfaceAccept) Name() string {
+ return "FilterInputInterfaceAccept"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputInterfaceAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ ifname, ok := getInterfaceName()
+ if !ok {
+ return fmt.Errorf("no interface is present, except loopback")
+ }
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", ifname, "-j", "ACCEPT"); err != nil {
+ return err
+ }
+ if err := listenUDP(ctx, acceptPort); err != nil {
+ return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %w", acceptPort, err)
+ }
+
+ return nil
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputInterfaceAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ return sendUDPLoop(ctx, ip, acceptPort)
+}
+
+// FilterInputInterfaceDrop tests that packets are dropped from interface
+// matching the iptables rule.
+type FilterInputInterfaceDrop struct{ localCase }
+
+var _ TestCase = FilterInputInterfaceDrop{}
+
+// Name implements TestCase.Name.
+func (FilterInputInterfaceDrop) Name() string {
+ return "FilterInputInterfaceDrop"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputInterfaceDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ ifname, ok := getInterfaceName()
+ if !ok {
+ return fmt.Errorf("no interface is present, except loopback")
+ }
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", ifname, "-j", "DROP"); err != nil {
+ return err
+ }
+ timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout)
+ defer cancel()
+ if err := listenUDP(timedCtx, acceptPort); err != nil {
+ if errors.Is(err, context.DeadlineExceeded) {
+ return nil
+ }
+ return fmt.Errorf("error reading: %w", err)
+ }
+ return fmt.Errorf("packets should have been dropped, but got a packet")
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputInterfaceDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ return sendUDPLoop(ctx, ip, acceptPort)
+}
+
+// FilterInputInterface tests that packets are not dropped from interface which
+// is not matching the interface name in the iptables rule.
+type FilterInputInterface struct{ localCase }
+
+var _ TestCase = FilterInputInterface{}
+
+// Name implements TestCase.Name.
+func (FilterInputInterface) Name() string {
+ return "FilterInputInterface"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputInterface) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", "lo", "-j", "DROP"); err != nil {
+ return err
+ }
+ if err := listenUDP(ctx, acceptPort); err != nil {
+ return fmt.Errorf("packets on port %d should be allowed, but encountered an error: %w", acceptPort, err)
+ }
+ return nil
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputInterface) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ return sendUDPLoop(ctx, ip, acceptPort)
+}
+
+// FilterInputInterfaceBeginsWith tests that packets are dropped from an
+// interface which begins with the given interface name.
+type FilterInputInterfaceBeginsWith struct{ localCase }
+
+var _ TestCase = FilterInputInterfaceBeginsWith{}
+
+// Name implements TestCase.Name.
+func (FilterInputInterfaceBeginsWith) Name() string {
+ return "FilterInputInterfaceBeginsWith"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputInterfaceBeginsWith) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "udp", "-i", "e+", "-j", "DROP"); err != nil {
+ return err
+ }
+ timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout)
+ defer cancel()
+ if err := listenUDP(timedCtx, acceptPort); err != nil {
+ if errors.Is(err, context.DeadlineExceeded) {
+ return nil
+ }
+ return fmt.Errorf("error reading: %w", err)
+ }
+ return fmt.Errorf("packets should have been dropped, but got a packet")
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputInterfaceBeginsWith) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ return sendUDPLoop(ctx, ip, acceptPort)
+}
+
+// FilterInputInterfaceInvertDrop tests that we selectively drop packets from
+// interface not matching the interface name.
+type FilterInputInterfaceInvertDrop struct{ baseCase }
+
+var _ TestCase = FilterInputInterfaceInvertDrop{}
+
+// Name implements TestCase.Name.
+func (FilterInputInterfaceInvertDrop) Name() string {
+ return "FilterInputInterfaceInvertDrop"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputInterfaceInvertDrop) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "-i", "lo", "-j", "DROP"); err != nil {
+ return err
+ }
+ timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout)
+ defer cancel()
+ if err := listenTCP(timedCtx, acceptPort); err != nil {
+ if errors.Is(err, context.DeadlineExceeded) {
+ return nil
+ }
+ return fmt.Errorf("error reading: %w", err)
+ }
+ return fmt.Errorf("connection on port %d should not be accepted, but was accepted", acceptPort)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputInterfaceInvertDrop) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ timedCtx, cancel := context.WithTimeout(ctx, NegativeTimeout)
+ defer cancel()
+ if err := connectTCP(timedCtx, ip, acceptPort); err != nil {
+ var operr *net.OpError
+ if errors.As(err, &operr) && operr.Timeout() {
+ return nil
+ }
+ return fmt.Errorf("error connecting: %w", err)
+ }
+ return fmt.Errorf("connection destined to port %d should not be accepted, but was accepted", acceptPort)
+}
+
+// FilterInputInterfaceInvertAccept tests that we can selectively accept packets
+// not matching the specific incoming interface.
+type FilterInputInterfaceInvertAccept struct{ baseCase }
+
+var _ TestCase = FilterInputInterfaceInvertAccept{}
+
+// Name implements TestCase.Name.
+func (FilterInputInterfaceInvertAccept) Name() string {
+ return "FilterInputInterfaceInvertAccept"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (FilterInputInterfaceInvertAccept) ContainerAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ if err := filterTable(ipv6, "-A", "INPUT", "-p", "tcp", "!", "-i", "lo", "-j", "ACCEPT"); err != nil {
+ return err
+ }
+ return listenTCP(ctx, acceptPort)
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (FilterInputInterfaceInvertAccept) LocalAction(ctx context.Context, ip net.IP, ipv6 bool) error {
+ return connectTCP(ctx, ip, acceptPort)
+}
diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go
index 9a4f60a9a..ef92e3fff 100644
--- a/test/iptables/iptables_test.go
+++ b/test/iptables/iptables_test.go
@@ -392,6 +392,30 @@ func TestInputInvertSource(t *testing.T) {
singleTest(t, FilterInputInvertSource{})
}
+func TestInputInterfaceAccept(t *testing.T) {
+ singleTest(t, FilterInputInterfaceAccept{})
+}
+
+func TestInputInterfaceDrop(t *testing.T) {
+ singleTest(t, FilterInputInterfaceDrop{})
+}
+
+func TestInputInterface(t *testing.T) {
+ singleTest(t, FilterInputInterface{})
+}
+
+func TestInputInterfaceBeginsWith(t *testing.T) {
+ singleTest(t, FilterInputInterfaceBeginsWith{})
+}
+
+func TestInputInterfaceInvertDrop(t *testing.T) {
+ singleTest(t, FilterInputInterfaceInvertDrop{})
+}
+
+func TestInputInterfaceInvertAccept(t *testing.T) {
+ singleTest(t, FilterInputInterfaceInvertAccept{})
+}
+
func TestFilterAddrs(t *testing.T) {
tcs := []struct {
ipv6 bool
diff --git a/test/iptables/iptables_util.go b/test/iptables/iptables_util.go
index a6ec5cca3..4cd770a65 100644
--- a/test/iptables/iptables_util.go
+++ b/test/iptables/iptables_util.go
@@ -171,7 +171,7 @@ func connectTCP(ctx context.Context, ip net.IP, port int) error {
return err
}
if err := testutil.PollContext(ctx, callback); err != nil {
- return fmt.Errorf("timed out waiting to connect IP on port %v, most recent error: %v", port, err)
+ return fmt.Errorf("timed out waiting to connect IP on port %v, most recent error: %w", port, err)
}
return nil
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index c6c95546a..5c3c569de 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -277,6 +277,10 @@ ALL_TESTS = [
PacketimpactTestInfo(
name = "tcp_rcv_buf_space",
),
+ PacketimpactTestInfo(
+ name = "tcp_rack",
+ expect_netstack_failure = True,
+ ),
]
def validate_all_tests():
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index b1b3c578b..6c6f2bdf7 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -376,6 +376,20 @@ packetimpact_testbench(
],
)
+packetimpact_testbench(
+ name = "tcp_rack",
+ srcs = ["tcp_rack_test.go"],
+ deps = [
+ "//pkg/abi/linux",
+ "//pkg/binary",
+ "//pkg/tcpip/header",
+ "//pkg/tcpip/seqnum",
+ "//pkg/usermem",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
validate_all_tests()
[packetimpact_go_test(
diff --git a/test/packetimpact/tests/tcp_rack_test.go b/test/packetimpact/tests/tcp_rack_test.go
new file mode 100644
index 000000000..0a2381c97
--- /dev/null
+++ b/test/packetimpact/tests/tcp_rack_test.go
@@ -0,0 +1,221 @@
+// Copyright 2020 The gVisor Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package tcp_rack_test
+
+import (
+ "flag"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/abi/linux"
+ "gvisor.dev/gvisor/pkg/binary"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ "gvisor.dev/gvisor/pkg/tcpip/seqnum"
+ "gvisor.dev/gvisor/pkg/usermem"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.Initialize(flag.CommandLine)
+}
+
+const (
+ // payloadSize is the size used to send packets.
+ payloadSize = header.TCPDefaultMSS
+
+ // simulatedRTT is the time delay between packets sent and acked to
+ // increase the RTT.
+ simulatedRTT = 30 * time.Millisecond
+
+ // numPktsForRTT is the number of packets sent and acked to establish
+ // RTT.
+ numPktsForRTT = 10
+)
+
+func createSACKConnection(t *testing.T) (testbench.DUT, testbench.TCPIPv4, int32, int32) {
+ dut := testbench.NewDUT(t)
+ listenFd, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+
+ // Enable SACK.
+ opts := make([]byte, 40)
+ optsOff := 0
+ optsOff += header.EncodeNOP(opts[optsOff:])
+ optsOff += header.EncodeNOP(opts[optsOff:])
+ optsOff += header.EncodeSACKPermittedOption(opts[optsOff:])
+
+ conn.ConnectWithOptions(t, opts[:optsOff])
+ acceptFd, _ := dut.Accept(t, listenFd)
+ return dut, conn, acceptFd, listenFd
+}
+
+func closeSACKConnection(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, acceptFd, listenFd int32) {
+ dut.Close(t, acceptFd)
+ dut.Close(t, listenFd)
+ conn.Close(t)
+}
+
+func getRTTAndRTO(t *testing.T, dut testbench.DUT, acceptFd int32) (rtt, rto time.Duration) {
+ info := linux.TCPInfo{}
+ ret := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
+ binary.Unmarshal(ret, usermem.ByteOrder, &info)
+ return time.Duration(info.RTT) * time.Microsecond, time.Duration(info.RTO) * time.Microsecond
+}
+
+func sendAndReceive(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4, numPkts int, acceptFd int32, sendACK bool) time.Time {
+ seqNum1 := *conn.RemoteSeqNum(t)
+ payload := make([]byte, payloadSize)
+ var lastSent time.Time
+ for i, sn := 0, seqNum1; i < numPkts; i++ {
+ lastSent = time.Now()
+ dut.Send(t, acceptFd, payload, 0)
+ gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, time.Second)
+ if err != nil {
+ t.Fatalf("Expect #%d: %s", i+1, err)
+ continue
+ }
+ if gotOne == nil {
+ t.Fatalf("#%d: expected a packet within a second but got none", i+1)
+ }
+ sn.UpdateForward(seqnum.Size(payloadSize))
+
+ if sendACK {
+ time.Sleep(simulatedRTT)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(sn))})
+ }
+ }
+ return lastSent
+}
+
+// TestRACKTLPAllPacketsLost tests TLP when an entire flight of data is lost.
+func TestRACKTLPAllPacketsLost(t *testing.T) {
+ dut, conn, acceptFd, listenFd := createSACKConnection(t)
+ seqNum1 := *conn.RemoteSeqNum(t)
+
+ // Send ACK for data packets to establish RTT.
+ sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
+ seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
+
+ // We are not sending ACK for these packets.
+ const numPkts = 5
+ lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ // Probe Timeout (PTO) should be two times RTT. Check that the last
+ // packet is retransmitted after probe timeout.
+ rtt, _ := getRTTAndRTO(t, dut, acceptFd)
+ pto := rtt * 2
+ // We expect the 5th packet (the last unacknowledged packet) to be
+ // retransmitted.
+ tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize))
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s %v %v", err, rtt, pto)
+ }
+ diff := time.Now().Sub(lastSent)
+ if diff < pto {
+ t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto)
+ }
+ closeSACKConnection(t, dut, conn, acceptFd, listenFd)
+}
+
+// TestRACKTLPLost tests TLP when there are tail losses.
+// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-7.4
+func TestRACKTLPLost(t *testing.T) {
+ dut, conn, acceptFd, listenFd := createSACKConnection(t)
+ seqNum1 := *conn.RemoteSeqNum(t)
+
+ // Send ACK for data packets to establish RTT.
+ sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
+ seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
+
+ // We are not sending ACK for these packets.
+ const numPkts = 10
+ lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ // Cumulative ACK for #[1-5] packets.
+ ackNum := seqNum1.Add(seqnum.Size(6 * payloadSize))
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(ackNum))})
+
+ // Probe Timeout (PTO) should be two times RTT. Check that the last
+ // packet is retransmitted after probe timeout.
+ rtt, _ := getRTTAndRTO(t, dut, acceptFd)
+ pto := rtt * 2
+ // We expect the 10th packet (the last unacknowledged packet) to be
+ // retransmitted.
+ tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize))
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+ diff := time.Now().Sub(lastSent)
+ if diff < pto {
+ t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto)
+ }
+ closeSACKConnection(t, dut, conn, acceptFd, listenFd)
+}
+
+// TestRACKTLPWithSACK tests TLP by acknowledging out of order packets.
+// See: https://tools.ietf.org/html/draft-ietf-tcpm-rack-08#section-8.1
+func TestRACKTLPWithSACK(t *testing.T) {
+ dut, conn, acceptFd, listenFd := createSACKConnection(t)
+ seqNum1 := *conn.RemoteSeqNum(t)
+
+ // Send ACK for data packets to establish RTT.
+ sendAndReceive(t, dut, conn, numPktsForRTT, acceptFd, true /* sendACK */)
+ seqNum1.UpdateForward(seqnum.Size(numPktsForRTT * payloadSize))
+
+ // We are not sending ACK for these packets.
+ const numPkts = 3
+ lastSent := sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ // SACK for #2 packet.
+ sackBlock := make([]byte, 40)
+ start := seqNum1.Add(seqnum.Size(payloadSize))
+ end := start.Add(seqnum.Size(payloadSize))
+ sbOff := 0
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
+ start, end,
+ }}, sackBlock[sbOff:])
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
+
+ // RACK marks #1 packet as lost and retransmits it.
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+
+ // ACK for #1 packet.
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(end))})
+
+ // Probe Timeout (PTO) should be two times RTT. TLP will trigger for #3
+ // packet. RACK adds an additional timeout of 200ms if the number of
+ // outstanding packets is equal to 1.
+ rtt, rto := getRTTAndRTO(t, dut, acceptFd)
+ pto := rtt*2 + (200 * time.Millisecond)
+ if rto < pto {
+ pto = rto
+ }
+ // We expect the 3rd packet (the last unacknowledged packet) to be
+ // retransmitted.
+ tlpProbe := testbench.Uint32(uint32(seqNum1) + uint32((numPkts-1)*payloadSize))
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: tlpProbe}, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+ diff := time.Now().Sub(lastSent)
+ if diff < pto {
+ t.Fatalf("expected payload was received before the probe timeout, got: %v, want: %v", diff, pto)
+ }
+ closeSACKConnection(t, dut, conn, acceptFd, listenFd)
+}
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
index 1ab9ee1b2..b15b8fc25 100644
--- a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
+++ b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
@@ -66,33 +66,39 @@ func TestZeroWindowProbeRetransmit(t *testing.T) {
probeSeq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t) - 1))
ackProbe := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))
- startProbeDuration := time.Second
- current := startProbeDuration
- first := time.Now()
// Ask the dut to send out data.
dut.Send(t, acceptFd, sampleData, 0)
+
+ var prev time.Duration
// Expect the dut to keep the connection alive as long as the remote is
// acknowledging the zero-window probes.
- for i := 0; i < 5; i++ {
+ for i := 1; i <= 5; i++ {
start := time.Now()
// Expect zero-window probe with a timeout which is a function of the typical
// first retransmission time. The retransmission times is supposed to
// exponentially increase.
- if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, 2*current); err != nil {
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: probeSeq}, nil, time.Duration(i)*time.Second); err != nil {
t.Fatalf("expected a probe with sequence number %d: loop %d", probeSeq, i)
}
- if i == 0 {
- startProbeDuration = time.Now().Sub(first)
- current = 2 * startProbeDuration
+ if i == 1 {
+ // Skip the first probe as computing transmit time for that is
+ // non-deterministic because of the arbitrary time taken for
+ // the dut to receive a send command and issue a send.
continue
}
- // Check if the probes came at exponentially increasing intervals.
- if got, want := time.Since(start), current-startProbeDuration; got < want {
+
+ // Check if the time taken to receive the probe from the dut is
+ // increasing exponentially. To avoid flakes, use a correction
+ // factor for the expected duration which accounts for any
+ // scheduling non-determinism.
+ const timeCorrection = 200 * time.Millisecond
+ got := time.Since(start)
+ if want := (2 * prev) - timeCorrection; prev != 0 && got < want {
t.Errorf("got zero probe %d after %s, want >= %s", i, got, want)
}
+ prev = got
// Acknowledge the zero-window probes from the dut.
conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.Uint8(header.TCPFlagAck), WindowSize: testbench.Uint16(0)})
- current *= 2
}
// Advertize non-zero window.
conn.Send(t, testbench.TCP{AckNum: ackProbe, Flags: testbench.Uint8(header.TCPFlagAck)})
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index b37716c48..0da295e2d 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -797,6 +797,7 @@ cc_binary(
linkstatic = 1,
deps = [
":socket_test_util",
+ "//test/util:capability_util",
"//test/util:cleanup",
"//test/util:eventfd_util",
"//test/util:file_descriptor",
@@ -807,6 +808,7 @@ cc_binary(
"@com_google_absl//absl/strings",
"@com_google_absl//absl/time",
gtest,
+ "//test/util:memory_util",
"//test/util:multiprocess_util",
"//test/util:posix_error",
"//test/util:save_util",
@@ -978,6 +980,7 @@ cc_binary(
"//test/util:epoll_util",
"//test/util:file_descriptor",
"//test/util:fs_util",
+ "//test/util:multiprocess_util",
"//test/util:posix_error",
"//test/util:temp_path",
"//test/util:test_main",
diff --git a/test/syscalls/linux/fcntl.cc b/test/syscalls/linux/fcntl.cc
index 4b581045b..75a5c9f17 100644
--- a/test/syscalls/linux/fcntl.cc
+++ b/test/syscalls/linux/fcntl.cc
@@ -15,6 +15,7 @@
#include <fcntl.h>
#include <signal.h>
#include <sys/epoll.h>
+#include <sys/mman.h>
#include <sys/types.h>
#include <syscall.h>
#include <unistd.h>
@@ -35,10 +36,12 @@
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "test/syscalls/linux/socket_test_util.h"
+#include "test/util/capability_util.h"
#include "test/util/cleanup.h"
#include "test/util/eventfd_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
+#include "test/util/memory_util.h"
#include "test/util/multiprocess_util.h"
#include "test/util/posix_error.h"
#include "test/util/save_util.h"
@@ -1642,6 +1645,202 @@ TEST(FcntlTest, SetFlSetOwnSetSigDoNotRace) {
}
}
+TEST_F(FcntlLockTest, GetLockOnNothing) {
+ SKIP_IF(IsRunningWithVFS1());
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
+
+ struct flock fl;
+ fl.l_type = F_RDLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 40;
+ ASSERT_THAT(fcntl(fd.get(), F_GETLK, &fl), SyscallSucceeds());
+ ASSERT_TRUE(fl.l_type == F_UNLCK);
+}
+
+TEST_F(FcntlLockTest, GetLockOnLockSameProcess) {
+ SKIP_IF(IsRunningWithVFS1());
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
+
+ struct flock fl;
+ fl.l_type = F_RDLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 40;
+ ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
+ ASSERT_THAT(fcntl(fd.get(), F_GETLK, &fl), SyscallSucceeds());
+ ASSERT_TRUE(fl.l_type == F_UNLCK);
+
+ fl.l_type = F_WRLCK;
+ ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
+ ASSERT_THAT(fcntl(fd.get(), F_GETLK, &fl), SyscallSucceeds());
+ ASSERT_TRUE(fl.l_type == F_UNLCK);
+}
+
+TEST_F(FcntlLockTest, GetReadLockOnReadLock) {
+ SKIP_IF(IsRunningWithVFS1());
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
+
+ struct flock fl;
+ fl.l_type = F_RDLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 40;
+ ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
+
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ TEST_CHECK(fcntl(fd.get(), F_GETLK, &fl) >= 0);
+ TEST_CHECK(fl.l_type == F_UNLCK);
+ _exit(0);
+ }
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
+TEST_F(FcntlLockTest, GetReadLockOnWriteLock) {
+ SKIP_IF(IsRunningWithVFS1());
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
+
+ struct flock fl;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 40;
+ ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
+
+ fl.l_type = F_RDLCK;
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ TEST_CHECK(fcntl(fd.get(), F_GETLK, &fl) >= 0);
+ TEST_CHECK(fl.l_type == F_WRLCK);
+ TEST_CHECK(fl.l_pid == getppid());
+ _exit(0);
+ }
+
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
+TEST_F(FcntlLockTest, GetWriteLockOnReadLock) {
+ SKIP_IF(IsRunningWithVFS1());
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
+
+ struct flock fl;
+ fl.l_type = F_RDLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 40;
+ ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
+
+ fl.l_type = F_WRLCK;
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ TEST_CHECK(fcntl(fd.get(), F_GETLK, &fl) >= 0);
+ TEST_CHECK(fl.l_type == F_RDLCK);
+ TEST_CHECK(fl.l_pid == getppid());
+ _exit(0);
+ }
+
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
+TEST_F(FcntlLockTest, GetWriteLockOnWriteLock) {
+ SKIP_IF(IsRunningWithVFS1());
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR, 0666));
+
+ struct flock fl;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 40;
+ ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
+
+ pid_t child_pid = fork();
+ if (child_pid == 0) {
+ TEST_CHECK(fcntl(fd.get(), F_GETLK, &fl) >= 0);
+ TEST_CHECK(fl.l_type == F_WRLCK);
+ TEST_CHECK(fl.l_pid == getppid());
+ _exit(0);
+ }
+
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
+// Tests that the pid returned from F_GETLK is relative to the caller's PID
+// namespace.
+TEST_F(FcntlLockTest, GetLockRespectsPIDNamespace) {
+ SKIP_IF(IsRunningWithVFS1());
+ SKIP_IF(!ASSERT_NO_ERRNO_AND_VALUE(HaveCapability(CAP_SYS_ADMIN)));
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ std::string filename = file.path();
+ const FileDescriptor fd =
+ ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_RDWR, 0666));
+
+ // Lock in the parent process.
+ struct flock fl;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 40;
+ ASSERT_THAT(fcntl(fd.get(), F_SETLK, &fl), SyscallSucceeds());
+
+ auto child_getlk = [](void* filename) {
+ int fd = open((char*)filename, O_RDWR, 0666);
+ TEST_CHECK(fd >= 0);
+
+ struct flock fl;
+ fl.l_type = F_WRLCK;
+ fl.l_whence = SEEK_SET;
+ fl.l_start = 0;
+ fl.l_len = 40;
+ TEST_CHECK(fcntl(fd, F_GETLK, &fl) >= 0);
+ TEST_CHECK(fl.l_type == F_WRLCK);
+ // Parent PID should be 0 in the child PID namespace.
+ TEST_CHECK(fl.l_pid == 0);
+ close(fd);
+ return 0;
+ };
+
+ // Set up child process in a new PID namespace.
+ constexpr int kStackSize = 4096;
+ Mapping stack = ASSERT_NO_ERRNO_AND_VALUE(
+ Mmap(nullptr, kStackSize, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0));
+ pid_t child_pid;
+ ASSERT_THAT(
+ child_pid = clone(child_getlk, (char*)stack.ptr() + stack.len(),
+ CLONE_NEWPID | SIGCHLD, (void*)filename.c_str()),
+ SyscallSucceeds());
+
+ int status;
+ ASSERT_THAT(waitpid(child_pid, &status, 0),
+ SyscallSucceedsWithValue(child_pid));
+ ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
} // namespace
} // namespace testing
diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc
index 8137f0e29..a88c89e20 100644
--- a/test/syscalls/linux/inotify.cc
+++ b/test/syscalls/linux/inotify.cc
@@ -36,6 +36,7 @@
#include "test/util/epoll_util.h"
#include "test/util/file_descriptor.h"
#include "test/util/fs_util.h"
+#include "test/util/multiprocess_util.h"
#include "test/util/posix_error.h"
#include "test/util/temp_path.h"
#include "test/util/test_util.h"
@@ -315,8 +316,7 @@ PosixErrorOr<std::vector<Event>> DrainEvents(int fd) {
}
PosixErrorOr<FileDescriptor> InotifyInit1(int flags) {
- int fd;
- EXPECT_THAT(fd = inotify_init1(flags), SyscallSucceeds());
+ int fd = inotify_init1(flags);
if (fd < 0) {
return PosixError(errno, "inotify_init1() failed");
}
@@ -325,9 +325,7 @@ PosixErrorOr<FileDescriptor> InotifyInit1(int flags) {
PosixErrorOr<int> InotifyAddWatch(int fd, const std::string& path,
uint32_t mask) {
- int wd;
- EXPECT_THAT(wd = inotify_add_watch(fd, path.c_str(), mask),
- SyscallSucceeds());
+ int wd = inotify_add_watch(fd, path.c_str(), mask);
if (wd < 0) {
return PosixError(errno, "inotify_add_watch() failed");
}
@@ -784,6 +782,38 @@ TEST(Inotify, MoveWatchedTargetGeneratesEvents) {
EXPECT_EQ(events[0].cookie, events[1].cookie);
}
+// Tests that close events are only emitted when a file description drops its
+// last reference.
+TEST(Inotify, DupFD) {
+ SKIP_IF(IsRunningWithVFS1());
+
+ const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ const FileDescriptor inotify_fd =
+ ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
+
+ const int wd = ASSERT_NO_ERRNO_AND_VALUE(
+ InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS));
+
+ FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDONLY));
+ FileDescriptor fd2 = ASSERT_NO_ERRNO_AND_VALUE(fd.Dup());
+
+ std::vector<Event> events =
+ ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({
+ Event(IN_OPEN, wd),
+ }));
+
+ fd.reset();
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({}));
+
+ fd2.reset();
+ events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
+ EXPECT_THAT(events, Are({
+ Event(IN_CLOSE_NOWRITE, wd),
+ }));
+}
+
TEST(Inotify, CoalesceEvents) {
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const FileDescriptor fd =
@@ -1779,11 +1809,9 @@ TEST(Inotify, Sendfile) {
EXPECT_THAT(out_events, Are({Event(IN_MODIFY, out_wd)}));
}
-// On Linux, inotify behavior is not very consistent with splice(2). We try our
-// best to emulate Linux for very basic calls to splice.
TEST(Inotify, SpliceOnWatchTarget) {
- int pipes[2];
- ASSERT_THAT(pipe2(pipes, O_NONBLOCK), SyscallSucceeds());
+ int pipefds[2];
+ ASSERT_THAT(pipe2(pipefds, O_NONBLOCK), SyscallSucceeds());
const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const FileDescriptor inotify_fd =
@@ -1798,15 +1826,20 @@ TEST(Inotify, SpliceOnWatchTarget) {
const int file_wd = ASSERT_NO_ERRNO_AND_VALUE(
InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS));
- EXPECT_THAT(splice(fd.get(), nullptr, pipes[1], nullptr, 1, /*flags=*/0),
+ EXPECT_THAT(splice(fd.get(), nullptr, pipefds[1], nullptr, 1, /*flags=*/0),
SyscallSucceedsWithValue(1));
- // Surprisingly, events are not generated in Linux if we read from a file.
+ // Surprisingly, events may not be generated in Linux if we read from a file.
+ // fs/splice.c:generic_file_splice_read, which is used most often, does not
+ // generate events, whereas fs/splice.c:default_file_splice_read does.
std::vector<Event> events =
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
- ASSERT_THAT(events, Are({}));
+ if (IsRunningOnGvisor() && !IsRunningWithVFS1()) {
+ ASSERT_THAT(events, Are({Event(IN_ACCESS, dir_wd, Basename(file.path())),
+ Event(IN_ACCESS, file_wd)}));
+ }
- EXPECT_THAT(splice(pipes[0], nullptr, fd.get(), nullptr, 1, /*flags=*/0),
+ EXPECT_THAT(splice(pipefds[0], nullptr, fd.get(), nullptr, 1, /*flags=*/0),
SyscallSucceedsWithValue(1));
events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
@@ -1817,8 +1850,8 @@ TEST(Inotify, SpliceOnWatchTarget) {
}
TEST(Inotify, SpliceOnInotifyFD) {
- int pipes[2];
- ASSERT_THAT(pipe2(pipes, O_NONBLOCK), SyscallSucceeds());
+ int pipefds[2];
+ ASSERT_THAT(pipe2(pipefds, O_NONBLOCK), SyscallSucceeds());
const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
const FileDescriptor fd =
@@ -1834,11 +1867,11 @@ TEST(Inotify, SpliceOnInotifyFD) {
char buf;
EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(splice(fd.get(), nullptr, pipes[1], nullptr,
+ EXPECT_THAT(splice(fd.get(), nullptr, pipefds[1], nullptr,
sizeof(struct inotify_event) + 1, SPLICE_F_NONBLOCK),
SyscallSucceedsWithValue(sizeof(struct inotify_event)));
- const FileDescriptor read_fd(pipes[0]);
+ const FileDescriptor read_fd(pipefds[0]);
const std::vector<Event> events =
ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(read_fd.get()));
ASSERT_THAT(events, Are({Event(IN_ACCESS, watcher)}));
@@ -1936,24 +1969,29 @@ TEST(Inotify, Xattr) {
}
TEST(Inotify, Exec) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath bin = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo(dir.path(), "/bin/true"));
-
+ SKIP_IF(IsRunningWithVFS1());
const FileDescriptor fd =
ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), bin.path(), IN_ALL_EVENTS));
+ InotifyAddWatch(fd.get(), "/bin/true", IN_ALL_EVENTS));
// Perform exec.
- ScopedThread t([&bin]() {
- ASSERT_THAT(execl(bin.path().c_str(), bin.path().c_str(), (char*)nullptr),
- SyscallSucceeds());
- });
- t.Join();
+ pid_t child = -1;
+ int execve_errno = -1;
+ auto kill = ASSERT_NO_ERRNO_AND_VALUE(
+ ForkAndExec("/bin/true", {}, {}, nullptr, &child, &execve_errno));
+ ASSERT_EQ(0, execve_errno);
+
+ int status;
+ ASSERT_THAT(RetryEINTR(waitpid)(child, &status, 0), SyscallSucceeds());
+ EXPECT_EQ(0, status);
+
+ // Process cleanup no longer needed.
+ kill.Release();
std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_THAT(events, Are({Event(IN_OPEN, wd), Event(IN_ACCESS, wd)}));
+ EXPECT_THAT(events, Are({Event(IN_OPEN, wd), Event(IN_ACCESS, wd),
+ Event(IN_CLOSE_NOWRITE, wd)}));
}
// Watches without IN_EXCL_UNLINK, should continue to emit events for file
diff --git a/test/syscalls/linux/packet_socket_raw.cc b/test/syscalls/linux/packet_socket_raw.cc
index 2ed4f6f9c..d25be0e30 100644
--- a/test/syscalls/linux/packet_socket_raw.cc
+++ b/test/syscalls/linux/packet_socket_raw.cc
@@ -548,13 +548,7 @@ TEST_P(RawPacketTest, SetSocketSendBuf) {
ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len),
SyscallSucceeds());
- // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF.
- // TODO(gvisor.dev/issue/2926): Remove the gvisor special casing when Netstack
- // matches linux behavior.
- if (!IsRunningOnGvisor()) {
- quarter_sz *= 2;
- }
-
+ quarter_sz *= 2;
ASSERT_EQ(quarter_sz, val);
}
diff --git a/test/syscalls/linux/raw_socket.cc b/test/syscalls/linux/raw_socket.cc
index 955bcee4b..32924466f 100644
--- a/test/syscalls/linux/raw_socket.cc
+++ b/test/syscalls/linux/raw_socket.cc
@@ -621,13 +621,7 @@ TEST_P(RawSocketTest, SetSocketSendBuf) {
ASSERT_THAT(getsockopt(s_, SOL_SOCKET, SO_SNDBUF, &val, &val_len),
SyscallSucceeds());
- // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF.
- // TODO(gvisor.dev/issue/2926): Remove the gvisor special casing when Netstack
- // matches linux behavior.
- if (!IsRunningOnGvisor()) {
- quarter_sz *= 2;
- }
-
+ quarter_sz *= 2;
ASSERT_EQ(quarter_sz, val);
}
diff --git a/test/syscalls/linux/shm.cc b/test/syscalls/linux/shm.cc
index d6e8b3e59..6aabd79e7 100644
--- a/test/syscalls/linux/shm.cc
+++ b/test/syscalls/linux/shm.cc
@@ -256,32 +256,26 @@ TEST(ShmTest, IpcInfo) {
}
TEST(ShmTest, ShmInfo) {
- struct shm_info info;
-
- // We generally can't know what other processes on a linux machine
- // does with shared memory segments, so we can't test specific
- // numbers on Linux. When running under gvisor, we're guaranteed to
- // be the only ones using shm, so we can easily verify machine-wide
- // numbers.
- if (IsRunningOnGvisor()) {
- ASSERT_NO_ERRNO(Shmctl(0, SHM_INFO, &info));
- EXPECT_EQ(info.used_ids, 0);
- EXPECT_EQ(info.shm_tot, 0);
- EXPECT_EQ(info.shm_rss, 0);
- EXPECT_EQ(info.shm_swp, 0);
- }
+ // Take a snapshot of the system before the test runs.
+ struct shm_info snap;
+ ASSERT_NO_ERRNO(Shmctl(0, SHM_INFO, &snap));
const ShmSegment shm = ASSERT_NO_ERRNO_AND_VALUE(
Shmget(IPC_PRIVATE, kAllocSize, IPC_CREAT | 0777));
const char* addr = ASSERT_NO_ERRNO_AND_VALUE(Shmat(shm.id(), nullptr, 0));
+ struct shm_info info;
ASSERT_NO_ERRNO(Shmctl(1, SHM_INFO, &info));
+ // We generally can't know what other processes on a linux machine do with
+ // shared memory segments, so we can't test specific numbers on Linux. When
+ // running under gvisor, we're guaranteed to be the only ones using shm, so
+ // we can easily verify machine-wide numbers.
if (IsRunningOnGvisor()) {
ASSERT_NO_ERRNO(Shmctl(shm.id(), SHM_INFO, &info));
- EXPECT_EQ(info.used_ids, 1);
- EXPECT_EQ(info.shm_tot, kAllocSize / kPageSize);
- EXPECT_EQ(info.shm_rss, kAllocSize / kPageSize);
+ EXPECT_EQ(info.used_ids, snap.used_ids + 1);
+ EXPECT_EQ(info.shm_tot, snap.shm_tot + (kAllocSize / kPageSize));
+ EXPECT_EQ(info.shm_rss, snap.shm_rss + (kAllocSize / kPageSize));
EXPECT_EQ(info.shm_swp, 0); // Gvisor currently never swaps.
}
diff --git a/test/syscalls/linux/socket.cc b/test/syscalls/linux/socket.cc
index 32f583581..b616c2c87 100644
--- a/test/syscalls/linux/socket.cc
+++ b/test/syscalls/linux/socket.cc
@@ -16,6 +16,7 @@
#include <sys/stat.h>
#include <sys/statfs.h>
#include <sys/types.h>
+#include <sys/wait.h>
#include <unistd.h>
#include "gtest/gtest.h"
@@ -90,8 +91,7 @@ TEST(SocketTest, UnixSocketStat) {
EXPECT_EQ(statbuf.st_mode, S_IFSOCK | (sock_perm & ~mask));
// Timestamps should be equal and non-zero.
- // TODO(b/158882152): Sockets currently don't implement timestamps.
- if (!IsRunningOnGvisor()) {
+ if (!IsRunningWithVFS1()) {
EXPECT_NE(statbuf.st_atime, 0);
EXPECT_EQ(statbuf.st_atime, statbuf.st_mtime);
EXPECT_EQ(statbuf.st_atime, statbuf.st_ctime);
@@ -111,6 +111,77 @@ TEST(SocketTest, UnixSocketStatFS) {
EXPECT_EQ(st.f_namelen, NAME_MAX);
}
+TEST(SocketTest, UnixSCMRightsOnlyPassedOnce_NoRandomSave) {
+ const DisableSave ds;
+
+ int sockets[2];
+ ASSERT_THAT(socketpair(AF_UNIX, SOCK_STREAM, 0, sockets), SyscallSucceeds());
+ // Send more than what will fit inside the send/receive buffers, so that it is
+ // split into multiple messages.
+ constexpr int kBufSize = 0x100000;
+
+ pid_t pid = fork();
+ if (pid == 0) {
+ TEST_PCHECK(close(sockets[0]) == 0);
+
+ // Construct a message with some control message.
+ struct msghdr msg = {};
+ char control[CMSG_SPACE(sizeof(int))] = {};
+ std::vector<char> buf(kBufSize);
+ struct iovec iov = {};
+ msg.msg_control = control;
+ msg.msg_controllen = sizeof(control);
+
+ struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ ((int*)CMSG_DATA(cmsg))[0] = sockets[1];
+
+ iov.iov_base = buf.data();
+ iov.iov_len = kBufSize;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ int n = sendmsg(sockets[1], &msg, 0);
+ TEST_PCHECK(n == kBufSize);
+ TEST_PCHECK(shutdown(sockets[1], SHUT_RDWR) == 0);
+ TEST_PCHECK(close(sockets[1]) == 0);
+ _exit(0);
+ }
+
+ close(sockets[1]);
+
+ struct msghdr msg = {};
+ char control[CMSG_SPACE(sizeof(int))] = {};
+ std::vector<char> buf(kBufSize);
+ struct iovec iov = {};
+ msg.msg_control = &control;
+ msg.msg_controllen = sizeof(control);
+
+ iov.iov_base = buf.data();
+ iov.iov_len = kBufSize;
+ msg.msg_iov = &iov;
+ msg.msg_iovlen = 1;
+
+ // The control message should only be present in the first message received.
+ int n;
+ ASSERT_THAT(n = recvmsg(sockets[0], &msg, 0), SyscallSucceeds());
+ ASSERT_GT(n, 0);
+ ASSERT_EQ(msg.msg_controllen, CMSG_SPACE(sizeof(int)));
+
+ while (n > 0) {
+ ASSERT_THAT(n = recvmsg(sockets[0], &msg, 0), SyscallSucceeds());
+ ASSERT_EQ(msg.msg_controllen, 0);
+ }
+
+ close(sockets[0]);
+
+ int status;
+ ASSERT_THAT(waitpid(pid, &status, 0), SyscallSucceedsWithValue(pid));
+ ASSERT_TRUE(WIFEXITED(status) && WEXITSTATUS(status) == 0);
+}
+
using SocketOpenTest = ::testing::TestWithParam<int>;
// UDS cannot be opened.
diff --git a/test/syscalls/linux/socket_ipv4_udp_unbound.cc b/test/syscalls/linux/socket_ipv4_udp_unbound.cc
index e557572a7..8eec31a46 100644
--- a/test/syscalls/linux/socket_ipv4_udp_unbound.cc
+++ b/test/syscalls/linux/socket_ipv4_udp_unbound.cc
@@ -2513,11 +2513,7 @@ TEST_P(IPv4UDPUnboundSocketTest, SetSocketSendBuf) {
ASSERT_THAT(getsockopt(s->get(), SOL_SOCKET, SO_SNDBUF, &val, &val_len),
SyscallSucceeds());
- // Linux doubles the value set by SO_SNDBUF/SO_RCVBUF.
- if (!IsRunningOnGvisor()) {
- quarter_sz *= 2;
- }
-
+ quarter_sz *= 2;
ASSERT_EQ(quarter_sz, val);
}
diff --git a/test/syscalls/linux/socket_unix_cmsg.cc b/test/syscalls/linux/socket_unix_cmsg.cc
index a16899493..22a4ee0d1 100644
--- a/test/syscalls/linux/socket_unix_cmsg.cc
+++ b/test/syscalls/linux/socket_unix_cmsg.cc
@@ -362,7 +362,7 @@ TEST_P(UnixSocketPairCmsgTest, BasicThreeFDPassTruncationMsgCtrunc) {
// BasicFDPassUnalignedRecv starts off by sending a single FD just like
// BasicFDPass. The difference is that when calling recvmsg, the length of the
-// receive data is only aligned on a 4 byte boundry instead of the normal 8.
+// receive data is only aligned on a 4 byte boundary instead of the normal 8.
TEST_P(UnixSocketPairCmsgTest, BasicFDPassUnalignedRecv) {
auto sockets = ASSERT_NO_ERRNO_AND_VALUE(NewSocketPair());
diff --git a/test/syscalls/linux/tcp_socket.cc b/test/syscalls/linux/tcp_socket.cc
index 714848b8e..9028ab024 100644
--- a/test/syscalls/linux/tcp_socket.cc
+++ b/test/syscalls/linux/tcp_socket.cc
@@ -2008,6 +2008,29 @@ TEST_P(SimpleTcpSocketTest, GetSocketAcceptConnWithShutdown) {
EXPECT_EQ(got, 0);
}
+// Tests that connecting to an unspecified address results in ECONNREFUSED.
+TEST_P(SimpleTcpSocketTest, ConnectUnspecifiedAddress) {
+ sockaddr_storage addr;
+ socklen_t addrlen = sizeof(addr);
+ memset(&addr, 0, addrlen);
+ addr.ss_family = GetParam();
+ auto do_connect = [&addr, addrlen]() {
+ FileDescriptor s = ASSERT_NO_ERRNO_AND_VALUE(
+ Socket(addr.ss_family, SOCK_STREAM, IPPROTO_TCP));
+ ASSERT_THAT(
+ RetryEINTR(connect)(s.get(), reinterpret_cast<struct sockaddr*>(&addr),
+ addrlen),
+ SyscallFailsWithErrno(ECONNREFUSED));
+ };
+ do_connect();
+ // Test the v4 mapped address as well.
+ if (GetParam() == AF_INET6) {
+ auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr);
+ sin6->sin6_addr.s6_addr[10] = sin6->sin6_addr.s6_addr[11] = 0xff;
+ do_connect();
+ }
+}
+
INSTANTIATE_TEST_SUITE_P(AllInetTests, SimpleTcpSocketTest,
::testing::Values(AF_INET, AF_INET6));
diff --git a/test/util/BUILD b/test/util/BUILD
index 1b028a477..e561f3daa 100644
--- a/test/util/BUILD
+++ b/test/util/BUILD
@@ -172,6 +172,7 @@ cc_library(
":posix_error",
":save_util",
":test_util",
+ gtest,
"@com_google_absl//absl/strings",
],
)
diff --git a/test/util/multiprocess_util.cc b/test/util/multiprocess_util.cc
index 8b676751b..a6b0de24b 100644
--- a/test/util/multiprocess_util.cc
+++ b/test/util/multiprocess_util.cc
@@ -154,6 +154,9 @@ PosixErrorOr<int> InForkedProcess(const std::function<void()>& fn) {
pid_t pid = fork();
if (pid == 0) {
fn();
+ TEST_CHECK_MSG(!::testing::Test::HasFailure(),
+ "EXPECT*/ASSERT* failed. These are not async-signal-safe "
+ "and must not be called from fn.");
_exit(0);
}
MaybeSave();