summaryrefslogtreecommitdiffhomepage
path: root/test/packetimpact
diff options
context:
space:
mode:
Diffstat (limited to 'test/packetimpact')
-rw-r--r--test/packetimpact/dut/BUILD2
-rw-r--r--test/packetimpact/dut/posix_server.cc40
-rw-r--r--test/packetimpact/proto/posix_server.proto23
-rw-r--r--test/packetimpact/runner/defs.bzl6
-rw-r--r--test/packetimpact/testbench/connections.go40
-rw-r--r--test/packetimpact/testbench/dut.go52
-rw-r--r--test/packetimpact/tests/BUILD27
-rw-r--r--test/packetimpact/tests/ipv4_fragment_reassembly_test.go12
-rw-r--r--test/packetimpact/tests/tcp_info_test.go109
-rw-r--r--test/packetimpact/tests/tcp_noaccept_close_rst_test.go15
-rw-r--r--test/packetimpact/tests/tcp_outside_the_window_test.go18
-rw-r--r--test/packetimpact/tests/tcp_rack_test.go204
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go28
-rw-r--r--test/packetimpact/tests/udp_recv_mcast_bcast_test.go115
-rw-r--r--test/packetimpact/tests/udp_send_recv_dgram_test.go338
15 files changed, 817 insertions, 212 deletions
diff --git a/test/packetimpact/dut/BUILD b/test/packetimpact/dut/BUILD
index ccf1c735f..0be14ca3e 100644
--- a/test/packetimpact/dut/BUILD
+++ b/test/packetimpact/dut/BUILD
@@ -14,6 +14,7 @@ cc_binary(
grpcpp,
"//test/packetimpact/proto:posix_server_cc_grpc_proto",
"//test/packetimpact/proto:posix_server_cc_proto",
+ "@com_google_absl//absl/strings:str_format",
],
)
@@ -24,5 +25,6 @@ cc_binary(
grpcpp,
"//test/packetimpact/proto:posix_server_cc_grpc_proto",
"//test/packetimpact/proto:posix_server_cc_proto",
+ "@com_google_absl//absl/strings:str_format",
],
)
diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc
index 4de8540f6..eba21df12 100644
--- a/test/packetimpact/dut/posix_server.cc
+++ b/test/packetimpact/dut/posix_server.cc
@@ -16,6 +16,7 @@
#include <getopt.h>
#include <netdb.h>
#include <netinet/in.h>
+#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -30,6 +31,7 @@
#include "include/grpcpp/security/server_credentials.h"
#include "include/grpcpp/server_builder.h"
#include "include/grpcpp/server_context.h"
+#include "absl/strings/str_format.h"
#include "test/packetimpact/proto/posix_server.grpc.pb.h"
#include "test/packetimpact/proto/posix_server.pb.h"
@@ -256,6 +258,44 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
+ ::grpc::Status Poll(::grpc::ServerContext *context,
+ const ::posix_server::PollRequest *request,
+ ::posix_server::PollResponse *response) override {
+ std::vector<struct pollfd> pfds;
+ pfds.reserve(request->pfds_size());
+ for (const auto &pfd : request->pfds()) {
+ pfds.push_back({
+ .fd = pfd.fd(),
+ .events = static_cast<short>(pfd.events()),
+ });
+ }
+ int ret = ::poll(pfds.data(), pfds.size(), request->timeout_millis());
+
+ response->set_ret(ret);
+ if (ret < 0) {
+ response->set_errno_(errno);
+ } else {
+ // Only pollfds that have non-empty revents are returned, the client can't
+ // rely on indexes of the request array.
+ for (const auto &pfd : pfds) {
+ if (pfd.revents) {
+ auto *proto_pfd = response->add_pfds();
+ proto_pfd->set_fd(pfd.fd);
+ proto_pfd->set_events(pfd.revents);
+ }
+ }
+ if (int ready = response->pfds_size(); ret != ready) {
+ return ::grpc::Status(
+ ::grpc::StatusCode::INTERNAL,
+ absl::StrFormat(
+ "poll's return value(%d) doesn't match the number of "
+ "file descriptors that are actually ready(%d)",
+ ret, ready));
+ }
+ }
+ return ::grpc::Status::OK;
+ }
+
::grpc::Status Send(::grpc::ServerContext *context,
const ::posix_server::SendRequest *request,
::posix_server::SendResponse *response) override {
diff --git a/test/packetimpact/proto/posix_server.proto b/test/packetimpact/proto/posix_server.proto
index f32ed54ef..b4c68764a 100644
--- a/test/packetimpact/proto/posix_server.proto
+++ b/test/packetimpact/proto/posix_server.proto
@@ -142,6 +142,25 @@ message ListenResponse {
int32 errno_ = 2; // "errno" may fail to compile in c++.
}
+// The events field is overloaded: when used for request, it is copied into the
+// events field of posix struct pollfd; when used for response, it is filled by
+// the revents field from the posix struct pollfd.
+message PollFd {
+ int32 fd = 1;
+ uint32 events = 2;
+}
+
+message PollRequest {
+ repeated PollFd pfds = 1;
+ int32 timeout_millis = 2;
+}
+
+message PollResponse {
+ int32 ret = 1;
+ int32 errno_ = 2; // "errno" may fail to compile in c++.
+ repeated PollFd pfds = 3;
+}
+
message SendRequest {
int32 sockfd = 1;
bytes buf = 2;
@@ -226,6 +245,10 @@ service Posix {
rpc GetSockOpt(GetSockOptRequest) returns (GetSockOptResponse);
// Call listen() on the DUT.
rpc Listen(ListenRequest) returns (ListenResponse);
+ // Call poll() on the DUT. Only pollfds that have non-empty revents are
+ // returned, the only way to tie the response back to the original request
+ // is using the fd number.
+ rpc Poll(PollRequest) returns (PollResponse);
// Call send() on the DUT.
rpc Send(SendRequest) returns (SendResponse);
// Call sendto() on the DUT.
diff --git a/test/packetimpact/runner/defs.bzl b/test/packetimpact/runner/defs.bzl
index 5c3c569de..a7c46781f 100644
--- a/test/packetimpact/runner/defs.bzl
+++ b/test/packetimpact/runner/defs.bzl
@@ -175,9 +175,6 @@ ALL_TESTS = [
name = "udp_discard_mcast_source_addr",
),
PacketimpactTestInfo(
- name = "udp_recv_mcast_bcast",
- ),
- PacketimpactTestInfo(
name = "udp_any_addr_recv_unicast",
),
PacketimpactTestInfo(
@@ -281,6 +278,9 @@ ALL_TESTS = [
name = "tcp_rack",
expect_netstack_failure = True,
),
+ PacketimpactTestInfo(
+ name = "tcp_info",
+ ),
]
def validate_all_tests():
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
index 576577310..1453ac232 100644
--- a/test/packetimpact/testbench/connections.go
+++ b/test/packetimpact/testbench/connections.go
@@ -1008,6 +1008,13 @@ func (conn *UDPIPv4) LocalAddr(t *testing.T) *unix.SockaddrInet4 {
return sa
}
+// SrcPort returns the source port of this connection.
+func (conn *UDPIPv4) SrcPort(t *testing.T) uint16 {
+ t.Helper()
+
+ return *conn.udpState(t).out.SrcPort
+}
+
// Send sends a packet with reasonable defaults, potentially overriding the UDP
// layer and adding additionLayers.
func (conn *UDPIPv4) Send(t *testing.T, udp UDP, additionalLayers ...Layer) {
@@ -1024,6 +1031,11 @@ func (conn *UDPIPv4) SendIP(t *testing.T, ip IPv4, udp UDP, additionalLayers ...
(*Connection)(conn).send(t, Layers{&ip, &udp}, additionalLayers...)
}
+// SendFrame sends a frame on the wire and updates the state of all layers.
+func (conn *UDPIPv4) SendFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) {
+ (*Connection)(conn).send(t, overrideLayers, additionalLayers...)
+}
+
// Expect expects a frame with the UDP layer matching the provided UDP within
// the timeout specified. If it doesn't arrive in time, an error is returned.
func (conn *UDPIPv4) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP, error) {
@@ -1053,6 +1065,14 @@ func (conn *UDPIPv4) ExpectData(t *testing.T, udp UDP, payload Payload, timeout
return (*Connection)(conn).ExpectFrame(t, expected, timeout)
}
+// 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 (conn *UDPIPv4) ExpectFrame(t *testing.T, frame Layers, timeout time.Duration) (Layers, error) {
+ t.Helper()
+
+ return (*Connection)(conn).ExpectFrame(t, frame, timeout)
+}
+
// Close frees associated resources held by the UDPIPv4 connection.
func (conn *UDPIPv4) Close(t *testing.T) {
t.Helper()
@@ -1136,6 +1156,13 @@ func (conn *UDPIPv6) LocalAddr(t *testing.T, zoneID uint32) *unix.SockaddrInet6
return sa
}
+// SrcPort returns the source port of this connection.
+func (conn *UDPIPv6) SrcPort(t *testing.T) uint16 {
+ t.Helper()
+
+ return *conn.udpState(t).out.SrcPort
+}
+
// Send sends a packet with reasonable defaults, potentially overriding the UDP
// layer and adding additionLayers.
func (conn *UDPIPv6) Send(t *testing.T, udp UDP, additionalLayers ...Layer) {
@@ -1152,6 +1179,11 @@ func (conn *UDPIPv6) SendIPv6(t *testing.T, ip IPv6, udp UDP, additionalLayers .
(*Connection)(conn).send(t, Layers{&ip, &udp}, additionalLayers...)
}
+// SendFrame sends a frame on the wire and updates the state of all layers.
+func (conn *UDPIPv6) SendFrame(t *testing.T, overrideLayers Layers, additionalLayers ...Layer) {
+ (*Connection)(conn).send(t, overrideLayers, additionalLayers...)
+}
+
// Expect expects a frame with the UDP layer matching the provided UDP within
// the timeout specified. If it doesn't arrive in time, an error is returned.
func (conn *UDPIPv6) Expect(t *testing.T, udp UDP, timeout time.Duration) (*UDP, error) {
@@ -1181,6 +1213,14 @@ func (conn *UDPIPv6) ExpectData(t *testing.T, udp UDP, payload Payload, timeout
return (*Connection)(conn).ExpectFrame(t, expected, timeout)
}
+// 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 (conn *UDPIPv6) ExpectFrame(t *testing.T, frame Layers, timeout time.Duration) (Layers, error) {
+ t.Helper()
+
+ return (*Connection)(conn).ExpectFrame(t, frame, timeout)
+}
+
// Close frees associated resources held by the UDPIPv6 connection.
func (conn *UDPIPv6) Close(t *testing.T) {
t.Helper()
diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go
index 66a0255b8..aedcf6013 100644
--- a/test/packetimpact/testbench/dut.go
+++ b/test/packetimpact/testbench/dut.go
@@ -486,6 +486,56 @@ func (dut *DUT) ListenWithErrno(ctx context.Context, t *testing.T, sockfd, backl
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
+// Poll calls poll on the DUT and causes a fatal test failure if it doesn't
+// succeed. If more control over error handling is needed, use PollWithErrno.
+// Only pollfds with non-empty revents are returned, the only way to tie the
+// response back to the original request is using the fd number.
+func (dut *DUT) Poll(t *testing.T, pfds []unix.PollFd, timeout time.Duration) []unix.PollFd {
+ t.Helper()
+
+ ctx := context.Background()
+ var cancel context.CancelFunc
+ if timeout >= 0 {
+ ctx, cancel = context.WithTimeout(ctx, timeout+RPCTimeout)
+ defer cancel()
+ }
+ ret, result, err := dut.PollWithErrno(ctx, t, pfds, timeout)
+ if ret < 0 {
+ t.Fatalf("failed to poll: %s", err)
+ }
+ return result
+}
+
+// PollWithErrno calls poll on the DUT.
+func (dut *DUT) PollWithErrno(ctx context.Context, t *testing.T, pfds []unix.PollFd, timeout time.Duration) (int32, []unix.PollFd, error) {
+ t.Helper()
+
+ req := pb.PollRequest{
+ TimeoutMillis: int32(timeout.Milliseconds()),
+ }
+ for _, pfd := range pfds {
+ req.Pfds = append(req.Pfds, &pb.PollFd{
+ Fd: pfd.Fd,
+ Events: uint32(pfd.Events),
+ })
+ }
+ resp, err := dut.posixServer.Poll(ctx, &req)
+ if err != nil {
+ t.Fatalf("failed to call Poll: %s", err)
+ }
+ if ret, npfds := resp.GetRet(), len(resp.GetPfds()); ret >= 0 && int(ret) != npfds {
+ t.Fatalf("nonsensical poll response: ret(%d) != len(pfds)(%d)", ret, npfds)
+ }
+ var result []unix.PollFd
+ for _, protoPfd := range resp.GetPfds() {
+ result = append(result, unix.PollFd{
+ Fd: protoPfd.GetFd(),
+ Revents: int16(protoPfd.GetEvents()),
+ })
+ }
+ return resp.GetRet(), result, syscall.Errno(resp.GetErrno_())
+}
+
// Send calls send on the DUT and causes a fatal test failure if it doesn't
// succeed. If more control over the timeout or error handling is needed, use
// SendWithErrno.
@@ -544,7 +594,7 @@ func (dut *DUT) SendToWithErrno(ctx context.Context, t *testing.T, sockfd int32,
}
resp, err := dut.posixServer.SendTo(ctx, &req)
if err != nil {
- t.Fatalf("faled to call SendTo: %s", err)
+ t.Fatalf("failed to call SendTo: %s", err)
}
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index 6c6f2bdf7..baa3ae5e9 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -38,18 +38,6 @@ packetimpact_testbench(
)
packetimpact_testbench(
- name = "udp_recv_mcast_bcast",
- srcs = ["udp_recv_mcast_bcast_test.go"],
- deps = [
- "//pkg/tcpip",
- "//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 = "udp_any_addr_recv_unicast",
srcs = ["udp_any_addr_recv_unicast_test.go"],
deps = [
@@ -340,6 +328,8 @@ packetimpact_testbench(
name = "udp_send_recv_dgram",
srcs = ["udp_send_recv_dgram_test.go"],
deps = [
+ "//pkg/tcpip",
+ "//pkg/tcpip/header",
"//test/packetimpact/testbench",
"@com_github_google_go_cmp//cmp:go_default_library",
"@org_golang_x_sys//unix:go_default_library",
@@ -390,6 +380,19 @@ packetimpact_testbench(
],
)
+packetimpact_testbench(
+ name = "tcp_info",
+ srcs = ["tcp_info_test.go"],
+ deps = [
+ "//pkg/abi/linux",
+ "//pkg/binary",
+ "//pkg/tcpip/header",
+ "//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/ipv4_fragment_reassembly_test.go b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go
index d2203082d..ee050e2c6 100644
--- a/test/packetimpact/tests/ipv4_fragment_reassembly_test.go
+++ b/test/packetimpact/tests/ipv4_fragment_reassembly_test.go
@@ -45,8 +45,6 @@ func TestIPv4FragmentReassembly(t *testing.T) {
ipPayloadLen int
fragments []fragmentInfo
expectReply bool
- skip bool
- skipReason string
}{
{
description: "basic reassembly",
@@ -78,8 +76,6 @@ func TestIPv4FragmentReassembly(t *testing.T) {
{offset: 2000, size: 1000, id: 7, more: 0},
},
expectReply: true,
- skip: true,
- skipReason: "gvisor.dev/issues/4971",
},
{
description: "fragment subset",
@@ -91,8 +87,6 @@ func TestIPv4FragmentReassembly(t *testing.T) {
{offset: 2000, size: 1000, id: 8, more: 0},
},
expectReply: true,
- skip: true,
- skipReason: "gvisor.dev/issues/4971",
},
{
description: "fragment overlap",
@@ -104,16 +98,10 @@ func TestIPv4FragmentReassembly(t *testing.T) {
{offset: 2000, size: 1000, id: 9, more: 0},
},
expectReply: false,
- skip: true,
- skipReason: "gvisor.dev/issues/4971",
},
}
for _, test := range tests {
- if test.skip {
- t.Skip("%s test skipped: %s", test.description, test.skipReason)
- continue
- }
t.Run(test.description, func(t *testing.T) {
dut := testbench.NewDUT(t)
conn := dut.Net.NewIPv4Conn(t, testbench.IPv4{}, testbench.IPv4{})
diff --git a/test/packetimpact/tests/tcp_info_test.go b/test/packetimpact/tests/tcp_info_test.go
new file mode 100644
index 000000000..69275e54b
--- /dev/null
+++ b/test/packetimpact/tests/tcp_info_test.go
@@ -0,0 +1,109 @@
+// 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_info_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/usermem"
+ "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+func init() {
+ testbench.Initialize(flag.CommandLine)
+}
+
+func TestTCPInfo(t *testing.T) {
+ // Create a socket, listen, TCP connect, and accept.
+ dut := testbench.NewDUT(t)
+ listenFD, remotePort := dut.CreateListener(t, unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(t, listenFD)
+
+ conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
+ defer conn.Close(t)
+ conn.Connect(t)
+
+ acceptFD, _ := dut.Accept(t, listenFD)
+ defer dut.Close(t, acceptFD)
+
+ // Send and receive sample data.
+ sampleData := []byte("Sample Data")
+ samplePayload := &testbench.Payload{Bytes: sampleData}
+ dut.Send(t, acceptFD, sampleData, 0)
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ }
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck)})
+
+ info := linux.TCPInfo{}
+ infoBytes := dut.GetSockOpt(t, acceptFD, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
+ if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
+ t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
+ }
+ binary.Unmarshal(infoBytes, usermem.ByteOrder, &info)
+
+ rtt := time.Duration(info.RTT) * time.Microsecond
+ rttvar := time.Duration(info.RTTVar) * time.Microsecond
+ rto := time.Duration(info.RTO) * time.Microsecond
+ if rtt == 0 || rttvar == 0 || rto == 0 {
+ t.Errorf("expected rtt(%v), rttvar(%v) and rto(%v) to be greater than zero", rtt, rttvar, rto)
+ }
+ if info.ReordSeen != 0 {
+ t.Errorf("expected the connection to not have any reordering, got: %v want: 0", info.ReordSeen)
+ }
+ if info.SndCwnd == 0 {
+ t.Errorf("expected send congestion window to be greater than zero")
+ }
+ if info.CaState != linux.TCP_CA_Open {
+ t.Errorf("expected the connection to be in open state, got: %v want: %v", info.CaState, linux.TCP_CA_Open)
+ }
+
+ if t.Failed() {
+ t.FailNow()
+ }
+
+ // Check the congestion control state and send congestion window after
+ // retransmission timeout.
+ seq := testbench.Uint32(uint32(*conn.RemoteSeqNum(t)))
+ dut.Send(t, acceptFD, sampleData, 0)
+ if _, err := conn.ExpectData(t, &testbench.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ }
+
+ // Expect retransmission of the packet within 1.5*RTO.
+ timeout := time.Duration(float64(info.RTO)*1.5) * time.Microsecond
+ if _, err := conn.ExpectData(t, &testbench.TCP{SeqNum: seq}, samplePayload, timeout); err != nil {
+ t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ }
+
+ info = linux.TCPInfo{}
+ infoBytes = dut.GetSockOpt(t, acceptFD, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
+ if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
+ t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
+ }
+ binary.Unmarshal(infoBytes, usermem.ByteOrder, &info)
+ if info.CaState != linux.TCP_CA_Loss {
+ t.Errorf("expected the connection to be in loss recovery, got: %v want: %v", info.CaState, linux.TCP_CA_Loss)
+ }
+ if info.SndCwnd != 1 {
+ t.Errorf("expected send congestion window to be 1, got: %v %v", info.SndCwnd)
+ }
+}
diff --git a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
index f0af5352d..c874a8912 100644
--- a/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
+++ b/test/packetimpact/tests/tcp_noaccept_close_rst_test.go
@@ -34,6 +34,21 @@ func TestTcpNoAcceptCloseReset(t *testing.T) {
conn := dut.Net.NewTCPIPv4(t, testbench.TCP{DstPort: &remotePort}, testbench.TCP{SrcPort: &remotePort})
conn.Connect(t)
defer conn.Close(t)
+ // We need to wait for POLLIN event on listenFd to know the connection is
+ // established. Otherwise there could be a race when we issue the Close
+ // command prior to the DUT receiving the last ack of the handshake and
+ // it will only respond RST instead of RST+ACK.
+ timeout := time.Second
+ pfds := dut.Poll(t, []unix.PollFd{{Fd: listenFd, Events: unix.POLLIN}}, timeout)
+ if n := len(pfds); n != 1 {
+ t.Fatalf("poll returned %d ready file descriptors, expected 1", n)
+ }
+ if readyFd := pfds[0].Fd; readyFd != listenFd {
+ t.Fatalf("poll returned an fd %d that was not requested (%d)", readyFd, listenFd)
+ }
+ if got, want := pfds[0].Revents, int16(unix.POLLIN); got&want == 0 {
+ t.Fatalf("poll returned no events in our interest, got: %#b, want: %#b", got, want)
+ }
dut.Close(t, listenFd)
if _, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagRst | header.TCPFlagAck)}, 1*time.Second); err != nil {
t.Fatalf("expected a RST-ACK packet but got none: %s", err)
diff --git a/test/packetimpact/tests/tcp_outside_the_window_test.go b/test/packetimpact/tests/tcp_outside_the_window_test.go
index 1b041932a..8909a348e 100644
--- a/test/packetimpact/tests/tcp_outside_the_window_test.go
+++ b/test/packetimpact/tests/tcp_outside_the_window_test.go
@@ -84,6 +84,24 @@ func TestTCPOutsideTheWindow(t *testing.T) {
if tt.expectACK && err != nil {
t.Fatalf("expected an ACK packet within %s but got none: %s", timeout, err)
}
+ // Data packets w/o SYN bits are always acked by Linux. Netstack ACK's data packets
+ // always right now. So only send a second segment and test for no ACK for packets
+ // with no data.
+ if tt.expectACK && tt.payload == nil {
+ // Sending another out-of-window segment immediately should not trigger
+ // an ACK if less than 500ms(default rate limit for out-of-window ACKs)
+ // has passed since the last ACK was sent.
+ t.Logf("sending another segment")
+ conn.Send(t, testbench.TCP{
+ Flags: testbench.Uint8(tt.tcpFlags),
+ SeqNum: testbench.Uint32(uint32(conn.LocalSeqNum(t).Add(windowSize))),
+ }, tt.payload...)
+ timeout := 3 * time.Second
+ gotACK, err := conn.Expect(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: localSeqNum}, timeout)
+ if err == nil {
+ t.Fatalf("expected no ACK packet but got one: %s", gotACK)
+ }
+ }
if !tt.expectACK && gotACK != nil {
t.Fatalf("expected no ACK packet within %s but got one: %s", timeout, gotACK)
}
diff --git a/test/packetimpact/tests/tcp_rack_test.go b/test/packetimpact/tests/tcp_rack_test.go
index 0a2381c97..fb2a4cc90 100644
--- a/test/packetimpact/tests/tcp_rack_test.go
+++ b/test/packetimpact/tests/tcp_rack_test.go
@@ -70,8 +70,11 @@ func closeSACKConnection(t *testing.T, dut testbench.DUT, conn testbench.TCPIPv4
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)
+ infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
+ if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
+ t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
+ }
+ binary.Unmarshal(infoBytes, usermem.ByteOrder, &info)
return time.Duration(info.RTT) * time.Microsecond, time.Duration(info.RTO) * time.Microsecond
}
@@ -219,3 +222,200 @@ func TestRACKTLPWithSACK(t *testing.T) {
}
closeSACKConnection(t, dut, conn, acceptFd, listenFd)
}
+
+// TestRACKWithoutReorder tests that without reordering RACK will retransmit the
+// lost packets after reorder timer expires.
+func TestRACKWithoutReorder(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 = 4
+ sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ // SACK for [3,4] packets.
+ sackBlock := make([]byte, 40)
+ start := seqNum1.Add(seqnum.Size(2 * payloadSize))
+ end := start.Add(seqnum.Size(2 * payloadSize))
+ sbOff := 0
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
+ start, end,
+ }}, sackBlock[sbOff:])
+ time.Sleep(simulatedRTT)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
+
+ // RACK marks #1 and #2 packets as lost and retransmits both after
+ // RTT + reorderWindow. The reorderWindow initially will be a small
+ // fraction of RTT.
+ rtt, _ := getRTTAndRTO(t, dut, acceptFd)
+ timeout := 2 * rtt
+ for i, sn := 0, seqNum1; i < 2; i++ {
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(sn))}, timeout); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+ sn.UpdateForward(seqnum.Size(payloadSize))
+ }
+ closeSACKConnection(t, dut, conn, acceptFd, listenFd)
+}
+
+// TestRACKWithReorder tests that RACK will retransmit segments when there is
+// reordering in the connection and reorder timer expires.
+func TestRACKWithReorder(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 = 4
+ sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ time.Sleep(simulatedRTT)
+ // SACK in reverse order for the connection to detect reorder.
+ var start seqnum.Value
+ var end seqnum.Value
+ for i := 0; i < numPkts-1; i++ {
+ sackBlock := make([]byte, 40)
+ sbOff := 0
+ start = seqNum1.Add(seqnum.Size((numPkts - i - 1) * payloadSize))
+ end = start.Add(seqnum.Size((i + 1) * payloadSize))
+ sackBlock = make([]byte, 40)
+ 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]})
+ }
+
+ // Send a DSACK block indicating both original and retransmitted
+ // packets are received, RACK will increase the reordering window on
+ // every DSACK.
+ dsackBlock := make([]byte, 40)
+ dbOff := 0
+ start = seqNum1
+ end = start.Add(seqnum.Size(2 * payloadSize))
+ dbOff += header.EncodeNOP(dsackBlock[dbOff:])
+ dbOff += header.EncodeNOP(dsackBlock[dbOff:])
+ dbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
+ start, end,
+ }}, dsackBlock[dbOff:])
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1 + numPkts*payloadSize)), Options: dsackBlock[:dbOff]})
+
+ seqNum1.UpdateForward(seqnum.Size(numPkts * payloadSize))
+ sendTime := time.Now()
+ sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ time.Sleep(simulatedRTT)
+ // Send SACK for [2-5] packets.
+ sackBlock := make([]byte, 40)
+ sbOff := 0
+ start = seqNum1.Add(seqnum.Size(payloadSize))
+ end = start.Add(seqnum.Size(3 * payloadSize))
+ 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]})
+
+ // Expect the retransmission of #1 packet after RTT+ReorderWindow.
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, time.Second); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+ rtt, _ := getRTTAndRTO(t, dut, acceptFd)
+ diff := time.Now().Sub(sendTime)
+ if diff < rtt {
+ t.Fatalf("expected payload was received too sonn, within RTT")
+ }
+
+ closeSACKConnection(t, dut, conn, acceptFd, listenFd)
+}
+
+// TestRACKWithLostRetransmission tests that RACK will not enter RTO when a
+// retransmitted segment is lost and enters fast recovery.
+func TestRACKWithLostRetransmission(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
+ sendAndReceive(t, dut, conn, numPkts, acceptFd, false /* sendACK */)
+
+ // SACK for [2-5] packets.
+ sackBlock := make([]byte, 40)
+ start := seqNum1.Add(seqnum.Size(payloadSize))
+ end := start.Add(seqnum.Size(4 * payloadSize))
+ sbOff := 0
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeNOP(sackBlock[sbOff:])
+ sbOff += header.EncodeSACKBlocks([]header.SACKBlock{{
+ start, end,
+ }}, sackBlock[sbOff:])
+ time.Sleep(simulatedRTT)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock[:sbOff]})
+
+ // RACK marks #1 packet as lost and retransmits it after
+ // RTT + reorderWindow. The reorderWindow is bounded between a small
+ // fraction of RTT and 1 RTT.
+ rtt, _ := getRTTAndRTO(t, dut, acceptFd)
+ timeout := 2 * rtt
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+
+ // Send #6 packet.
+ payload := make([]byte, payloadSize)
+ dut.Send(t, acceptFd, payload, 0)
+ gotOne, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1 + 5*payloadSize))}, time.Second)
+ if err != nil {
+ t.Fatalf("Expect #6: %s", err)
+ }
+ if gotOne == nil {
+ t.Fatalf("#6: expected a packet within a second but got none")
+ }
+
+ // SACK for [2-6] packets.
+ sackBlock1 := make([]byte, 40)
+ start = seqNum1.Add(seqnum.Size(payloadSize))
+ end = start.Add(seqnum.Size(5 * payloadSize))
+ sbOff1 := 0
+ sbOff1 += header.EncodeNOP(sackBlock1[sbOff1:])
+ sbOff1 += header.EncodeNOP(sackBlock1[sbOff1:])
+ sbOff1 += header.EncodeSACKBlocks([]header.SACKBlock{{
+ start, end,
+ }}, sackBlock1[sbOff1:])
+ time.Sleep(simulatedRTT)
+ conn.Send(t, testbench.TCP{Flags: testbench.Uint8(header.TCPFlagAck), AckNum: testbench.Uint32(uint32(seqNum1)), Options: sackBlock1[:sbOff1]})
+
+ // Expect re-retransmission of #1 packet without entering an RTO.
+ if _, err := conn.Expect(t, testbench.TCP{SeqNum: testbench.Uint32(uint32(seqNum1))}, timeout); err != nil {
+ t.Fatalf("expected payload was not received: %s", err)
+ }
+
+ // Check the congestion control state.
+ info := linux.TCPInfo{}
+ infoBytes := dut.GetSockOpt(t, acceptFd, unix.SOL_TCP, unix.TCP_INFO, int32(linux.SizeOfTCPInfo))
+ if got, want := len(infoBytes), linux.SizeOfTCPInfo; got != want {
+ t.Fatalf("expected %T, got %d bytes want %d bytes", info, got, want)
+ }
+ binary.Unmarshal(infoBytes, usermem.ByteOrder, &info)
+ if info.CaState != linux.TCP_CA_Recovery {
+ t.Fatalf("expected connection to be in fast recovery, want: %v got: %v", linux.TCP_CA_Recovery, info.CaState)
+ }
+
+ 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/packetimpact/tests/udp_recv_mcast_bcast_test.go b/test/packetimpact/tests/udp_recv_mcast_bcast_test.go
deleted file mode 100644
index b29c07825..000000000
--- a/test/packetimpact/tests/udp_recv_mcast_bcast_test.go
+++ /dev/null
@@ -1,115 +0,0 @@
-// 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 udp_recv_mcast_bcast_test
-
-import (
- "context"
- "flag"
- "fmt"
- "net"
- "syscall"
- "testing"
-
- "github.com/google/go-cmp/cmp"
- "golang.org/x/sys/unix"
- "gvisor.dev/gvisor/pkg/tcpip"
- "gvisor.dev/gvisor/test/packetimpact/testbench"
-)
-
-func init() {
- testbench.Initialize(flag.CommandLine)
-}
-
-func TestUDPRecvMcastBcast(t *testing.T) {
- dut := testbench.NewDUT(t)
- subnetBcastAddr := broadcastAddr(dut.Net.RemoteIPv4, net.CIDRMask(dut.Net.IPv4PrefixLength, 32))
- for _, v := range []struct {
- bound, to net.IP
- }{
- {bound: net.IPv4zero, to: subnetBcastAddr},
- {bound: net.IPv4zero, to: net.IPv4bcast},
- {bound: net.IPv4zero, to: net.IPv4allsys},
-
- {bound: subnetBcastAddr, to: subnetBcastAddr},
-
- // FIXME(gvisor.dev/issue/4896): Previously by the time subnetBcastAddr is
- // created, IPv4PrefixLength is still 0 because genPseudoFlags is not called
- // yet, it was only called in NewDUT, so the test didn't do what the author
- // original intended to and becomes failing because we process all flags at
- // the very beginning.
- //
- // {bound: subnetBcastAddr, to: net.IPv4bcast},
-
- {bound: net.IPv4bcast, to: net.IPv4bcast},
- {bound: net.IPv4allsys, to: net.IPv4allsys},
- } {
- t.Run(fmt.Sprintf("bound=%s,to=%s", v.bound, v.to), func(t *testing.T) {
- boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, v.bound)
- defer dut.Close(t, boundFD)
- conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */)
- conn.SendIP(
- t,
- testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(v.to.To4()))},
- testbench.UDP{},
- &testbench.Payload{Bytes: payload},
- )
- got, want := dut.Recv(t, boundFD, int32(len(payload)+1), 0), payload
- if diff := cmp.Diff(want, got); diff != "" {
- t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
- }
- })
- }
-}
-
-func TestUDPDoesntRecvMcastBcastOnUnicastAddr(t *testing.T) {
- dut := testbench.NewDUT(t)
- boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, dut.Net.RemoteIPv4)
- dut.SetSockOptTimeval(t, boundFD, unix.SOL_SOCKET, unix.SO_RCVTIMEO, &unix.Timeval{Sec: 1, Usec: 0})
- defer dut.Close(t, boundFD)
- conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- defer conn.Close(t)
-
- for _, to := range []net.IP{
- broadcastAddr(dut.Net.RemoteIPv4, net.CIDRMask(dut.Net.IPv4PrefixLength, 32)),
- net.IPv4(255, 255, 255, 255),
- net.IPv4(224, 0, 0, 1),
- } {
- t.Run(fmt.Sprint("to=%s", to), func(t *testing.T) {
- payload := testbench.GenerateRandomPayload(t, 1<<10 /* 1 KiB */)
- conn.SendIP(
- t,
- testbench.IPv4{DstAddr: testbench.Address(tcpip.Address(to.To4()))},
- testbench.UDP{},
- &testbench.Payload{Bytes: payload},
- )
- ret, payload, errno := dut.RecvWithErrno(context.Background(), t, boundFD, 100, 0)
- if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK {
- t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, payload, errno)
- }
- })
- }
-}
-
-func broadcastAddr(ip net.IP, mask net.IPMask) net.IP {
- result := make(net.IP, net.IPv4len)
- ip4 := ip.To4()
- for i := range ip4 {
- result[i] = ip4[i] | ^mask[i]
- }
- return result
-}
diff --git a/test/packetimpact/tests/udp_send_recv_dgram_test.go b/test/packetimpact/tests/udp_send_recv_dgram_test.go
index 7ee2c8014..6e45cb143 100644
--- a/test/packetimpact/tests/udp_send_recv_dgram_test.go
+++ b/test/packetimpact/tests/udp_send_recv_dgram_test.go
@@ -15,13 +15,18 @@
package udp_send_recv_dgram_test
import (
+ "context"
"flag"
+ "fmt"
"net"
+ "syscall"
"testing"
"time"
"github.com/google/go-cmp/cmp"
"golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/test/packetimpact/testbench"
)
@@ -30,74 +35,295 @@ func init() {
}
type udpConn interface {
- Send(*testing.T, testbench.UDP, ...testbench.Layer)
- ExpectData(*testing.T, testbench.UDP, testbench.Payload, time.Duration) (testbench.Layers, error)
- Drain(*testing.T)
+ SrcPort(*testing.T) uint16
+ SendFrame(*testing.T, testbench.Layers, ...testbench.Layer)
+ ExpectFrame(*testing.T, testbench.Layers, time.Duration) (testbench.Layers, error)
Close(*testing.T)
}
+type testCase struct {
+ bindTo, sendTo net.IP
+ sendToBroadcast, bindToDevice, expectData bool
+}
+
func TestUDP(t *testing.T) {
dut := testbench.NewDUT(t)
+ subnetBcast := func() net.IP {
+ subnet := (&tcpip.AddressWithPrefix{
+ Address: tcpip.Address(dut.Net.RemoteIPv4.To4()),
+ PrefixLen: dut.Net.IPv4PrefixLength,
+ }).Subnet()
+ return net.IP(subnet.Broadcast())
+ }()
- for _, isIPv4 := range []bool{true, false} {
- ipVersionName := "IPv6"
- if isIPv4 {
- ipVersionName = "IPv4"
- }
- t.Run(ipVersionName, func(t *testing.T) {
- var addr net.IP
- if isIPv4 {
- addr = dut.Net.RemoteIPv4
- } else {
- addr = dut.Net.RemoteIPv6
+ t.Run("Send", func(t *testing.T) {
+ var testCases []testCase
+ // Test every valid combination of bound/unbound, broadcast/multicast/unicast
+ // bound/destination address, and bound/not-bound to device.
+ for _, bindTo := range []net.IP{
+ nil, // Do not bind.
+ net.IPv4zero,
+ net.IPv4bcast,
+ net.IPv4allsys,
+ subnetBcast,
+ dut.Net.RemoteIPv4,
+ dut.Net.RemoteIPv6,
+ } {
+ for _, sendTo := range []net.IP{
+ net.IPv4bcast,
+ net.IPv4allsys,
+ subnetBcast,
+ dut.Net.LocalIPv4,
+ dut.Net.LocalIPv6,
+ } {
+ // Cannot send to an IPv4 address from a socket bound to IPv6 (except for IPv4-mapped IPv6),
+ // and viceversa.
+ if bindTo != nil && ((bindTo.To4() == nil) != (sendTo.To4() == nil)) {
+ continue
+ }
+ for _, bindToDevice := range []bool{true, false} {
+ expectData := true
+ switch {
+ case bindTo.Equal(dut.Net.RemoteIPv4):
+ // If we're explicitly bound to an interface's unicast address,
+ // packets are always sent on that interface.
+ case bindToDevice:
+ // If we're explicitly bound to an interface, packets are always
+ // sent on that interface.
+ case !sendTo.Equal(net.IPv4bcast) && !sendTo.IsMulticast():
+ // If we're not sending to limited broadcast or multicast, the route table
+ // will be consulted and packets will be sent on the correct interface.
+ default:
+ expectData = false
+ }
+ testCases = append(
+ testCases,
+ testCase{
+ bindTo: bindTo,
+ sendTo: sendTo,
+ sendToBroadcast: sendTo.Equal(subnetBcast) || sendTo.Equal(net.IPv4bcast),
+ bindToDevice: bindToDevice,
+ expectData: expectData,
+ },
+ )
+ }
}
- boundFD, remotePort := dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, addr)
- defer dut.Close(t, boundFD)
-
- var conn udpConn
- var localAddr unix.Sockaddr
- if isIPv4 {
- v4Conn := dut.Net.NewUDPIPv4(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- localAddr = v4Conn.LocalAddr(t)
- conn = &v4Conn
- } else {
- v6Conn := dut.Net.NewUDPIPv6(t, testbench.UDP{DstPort: &remotePort}, testbench.UDP{SrcPort: &remotePort})
- localAddr = v6Conn.LocalAddr(t, dut.Net.RemoteDevID)
- conn = &v6Conn
- }
- defer conn.Close(t)
-
- testCases := []struct {
- name string
- payload []byte
- }{
- {"emptypayload", nil},
- {"small payload", []byte("hello world")},
- {"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)},
- // Even though UDP allows larger dgrams we don't test it here as
- // they need to be fragmented and written out as individual
- // frames.
+ }
+ for _, tc := range testCases {
+ boundTestCaseName := "unbound"
+ if tc.bindTo != nil {
+ boundTestCaseName = fmt.Sprintf("bindTo=%s", tc.bindTo)
}
- for _, tc := range testCases {
- t.Run(tc.name, func(t *testing.T) {
- t.Run("Send", func(t *testing.T) {
- conn.Send(t, testbench.UDP{}, &testbench.Payload{Bytes: tc.payload})
- got, want := dut.Recv(t, boundFD, int32(len(tc.payload)+1), 0), tc.payload
- if diff := cmp.Diff(want, got); diff != "" {
- t.Fatalf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
+ t.Run(fmt.Sprintf("%s/sendTo=%s/bindToDevice=%t/expectData=%t", boundTestCaseName, tc.sendTo, tc.bindToDevice, tc.expectData), func(t *testing.T) {
+ runTestCase(
+ t,
+ dut,
+ tc,
+ func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers) {
+ var destSockaddr unix.Sockaddr
+ if sendTo4 := tc.sendTo.To4(); sendTo4 != nil {
+ addr := unix.SockaddrInet4{
+ Port: int(conn.SrcPort(t)),
+ }
+ copy(addr.Addr[:], sendTo4)
+ destSockaddr = &addr
+ } else {
+ addr := unix.SockaddrInet6{
+ Port: int(conn.SrcPort(t)),
+ ZoneId: dut.Net.RemoteDevID,
+ }
+ copy(addr.Addr[:], tc.sendTo.To16())
+ destSockaddr = &addr
}
- })
- t.Run("Recv", func(t *testing.T) {
- conn.Drain(t)
- if got, want := int(dut.SendTo(t, boundFD, tc.payload, 0, localAddr)), len(tc.payload); got != want {
- t.Fatalf("short write got: %d, want: %d", got, want)
+ if got, want := dut.SendTo(t, socketFD, payload, 0, destSockaddr), len(payload); int(got) != want {
+ t.Fatalf("got dut.SendTo = %d, want %d", got, want)
}
- if _, err := conn.ExpectData(t, testbench.UDP{SrcPort: &remotePort}, testbench.Payload{Bytes: tc.payload}, time.Second); err != nil {
+ layers = append(layers, &testbench.Payload{
+ Bytes: payload,
+ })
+ _, err := conn.ExpectFrame(t, layers, time.Second)
+
+ if !tc.expectData && err == nil {
+ t.Fatal("received unexpected packet, socket is not bound to device")
+ }
+ if err != nil && tc.expectData {
t.Fatal(err)
}
- })
- })
+ },
+ )
+ })
+ }
+ })
+ t.Run("Recv", func(t *testing.T) {
+ // Test every valid combination of broadcast/multicast/unicast
+ // bound/destination address, and bound/not-bound to device.
+ var testCases []testCase
+ for _, addr := range []net.IP{
+ net.IPv4bcast,
+ net.IPv4allsys,
+ dut.Net.RemoteIPv4,
+ dut.Net.RemoteIPv6,
+ } {
+ for _, bindToDevice := range []bool{true, false} {
+ testCases = append(
+ testCases,
+ testCase{
+ bindTo: addr,
+ sendTo: addr,
+ sendToBroadcast: addr.Equal(subnetBcast) || addr.Equal(net.IPv4bcast),
+ bindToDevice: bindToDevice,
+ expectData: true,
+ },
+ )
}
- })
+ }
+ for _, bindTo := range []net.IP{
+ net.IPv4zero,
+ subnetBcast,
+ dut.Net.RemoteIPv4,
+ } {
+ for _, sendTo := range []net.IP{
+ subnetBcast,
+ net.IPv4bcast,
+ net.IPv4allsys,
+ } {
+ // TODO(gvisor.dev/issue/4896): Add bindTo=subnetBcast/sendTo=IPv4bcast
+ // and bindTo=subnetBcast/sendTo=IPv4allsys test cases.
+ if bindTo.Equal(subnetBcast) && (sendTo.Equal(net.IPv4bcast) || sendTo.IsMulticast()) {
+ continue
+ }
+ // Expect that a socket bound to a unicast address does not receive
+ // packets sent to an address other than the bound unicast address.
+ //
+ // Note: we cannot use net.IP.IsGlobalUnicast to test this condition
+ // because IsGlobalUnicast does not check whether the address is the
+ // subnet broadcast, and returns true in that case.
+ expectData := !bindTo.Equal(dut.Net.RemoteIPv4) || sendTo.Equal(dut.Net.RemoteIPv4)
+ for _, bindToDevice := range []bool{true, false} {
+ testCases = append(
+ testCases,
+ testCase{
+ bindTo: bindTo,
+ sendTo: sendTo,
+ sendToBroadcast: sendTo.Equal(subnetBcast) || sendTo.Equal(net.IPv4bcast),
+ bindToDevice: bindToDevice,
+ expectData: expectData,
+ },
+ )
+ }
+ }
+ }
+ for _, tc := range testCases {
+ t.Run(fmt.Sprintf("bindTo=%s/sendTo=%s/bindToDevice=%t/expectData=%t", tc.bindTo, tc.sendTo, tc.bindToDevice, tc.expectData), func(t *testing.T) {
+ runTestCase(
+ t,
+ dut,
+ tc,
+ func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers) {
+ conn.SendFrame(t, layers, &testbench.Payload{Bytes: payload})
+
+ if tc.expectData {
+ got, want := dut.Recv(t, socketFD, int32(len(payload)+1), 0), payload
+ if diff := cmp.Diff(want, got); diff != "" {
+ t.Errorf("received payload does not match sent payload, diff (-want, +got):\n%s", diff)
+ }
+ } else {
+ // Expected receive error, set a short receive timeout.
+ dut.SetSockOptTimeval(
+ t,
+ socketFD,
+ unix.SOL_SOCKET,
+ unix.SO_RCVTIMEO,
+ &unix.Timeval{
+ Sec: 1,
+ Usec: 0,
+ },
+ )
+ ret, recvPayload, errno := dut.RecvWithErrno(context.Background(), t, socketFD, 100, 0)
+ if errno != syscall.EAGAIN || errno != syscall.EWOULDBLOCK {
+ t.Errorf("Recv got unexpected result, ret=%d, payload=%q, errno=%s", ret, recvPayload, errno)
+ }
+ }
+ },
+ )
+ })
+ }
+ })
+}
+
+func runTestCase(
+ t *testing.T,
+ dut testbench.DUT,
+ tc testCase,
+ runTc func(t *testing.T, dut testbench.DUT, conn udpConn, socketFD int32, tc testCase, payload []byte, layers testbench.Layers),
+) {
+ var (
+ socketFD int32
+ outgoingUDP, incomingUDP testbench.UDP
+ )
+ if tc.bindTo != nil {
+ var remotePort uint16
+ socketFD, remotePort = dut.CreateBoundSocket(t, unix.SOCK_DGRAM, unix.IPPROTO_UDP, tc.bindTo)
+ outgoingUDP.DstPort = &remotePort
+ incomingUDP.SrcPort = &remotePort
+ } else {
+ // An unbound socket will auto-bind to INNADDR_ANY and a random
+ // port on sendto.
+ socketFD = dut.Socket(t, unix.AF_INET6, unix.SOCK_DGRAM, unix.IPPROTO_UDP)
+ }
+ defer dut.Close(t, socketFD)
+ if tc.bindToDevice {
+ dut.SetSockOpt(t, socketFD, unix.SOL_SOCKET, unix.SO_BINDTODEVICE, []byte(dut.Net.RemoteDevName))
+ }
+
+ var ethernetLayer testbench.Ether
+ if tc.sendToBroadcast {
+ dut.SetSockOptInt(t, socketFD, unix.SOL_SOCKET, unix.SO_BROADCAST, 1)
+
+ // When sending to broadcast (subnet or limited), the expected ethernet
+ // address is also broadcast.
+ ethernetBroadcastAddress := header.EthernetBroadcastAddress
+ ethernetLayer.DstAddr = &ethernetBroadcastAddress
+ } else if tc.sendTo.IsMulticast() {
+ ethernetMulticastAddress := header.EthernetAddressFromMulticastIPv4Address(tcpip.Address(tc.sendTo.To4()))
+ ethernetLayer.DstAddr = &ethernetMulticastAddress
+ }
+ expectedLayers := testbench.Layers{&ethernetLayer}
+
+ var conn udpConn
+ if sendTo4 := tc.sendTo.To4(); sendTo4 != nil {
+ v4Conn := dut.Net.NewUDPIPv4(t, outgoingUDP, incomingUDP)
+ conn = &v4Conn
+ expectedLayers = append(
+ expectedLayers,
+ &testbench.IPv4{
+ DstAddr: testbench.Address(tcpip.Address(sendTo4)),
+ },
+ )
+ } else {
+ v6Conn := dut.Net.NewUDPIPv6(t, outgoingUDP, incomingUDP)
+ conn = &v6Conn
+ expectedLayers = append(
+ expectedLayers,
+ &testbench.IPv6{
+ DstAddr: testbench.Address(tcpip.Address(tc.sendTo)),
+ },
+ )
+ }
+ defer conn.Close(t)
+
+ expectedLayers = append(expectedLayers, &incomingUDP)
+ for _, v := range []struct {
+ name string
+ payload []byte
+ }{
+ {"emptypayload", nil},
+ {"small payload", []byte("hello world")},
+ {"1kPayload", testbench.GenerateRandomPayload(t, 1<<10)},
+ // Even though UDP allows larger dgrams we don't test it here as
+ // they need to be fragmented and written out as individual
+ // frames.
+ } {
+ runTc(t, dut, conn, socketFD, tc, v.payload, expectedLayers)
}
}