summaryrefslogtreecommitdiffhomepage
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/iptables/filter_output.go2
-rw-r--r--test/iptables/iptables_test.go66
-rw-r--r--test/iptables/iptables_util.go2
-rw-r--r--test/iptables/nat.go103
-rw-r--r--test/packetimpact/dut/posix_server.cc216
-rw-r--r--test/packetimpact/proto/posix_server.proto82
-rw-r--r--test/packetimpact/testbench/connections.go120
-rw-r--r--test/packetimpact/testbench/dut.go211
-rw-r--r--test/packetimpact/testbench/layers.go104
-rw-r--r--test/packetimpact/tests/BUILD45
-rw-r--r--test/packetimpact/tests/tcp_close_wait_ack_test.go15
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go99
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_test.go107
-rw-r--r--test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go93
-rw-r--r--test/packetimpact/tests/udp_icmp_error_propagation_test.go355
-rw-r--r--test/runtimes/proctor/BUILD1
-rw-r--r--test/syscalls/linux/BUILD1
-rw-r--r--test/syscalls/linux/eventfd.cc16
-rw-r--r--test/syscalls/linux/pipe.cc29
-rw-r--r--test/syscalls/linux/proc.cc89
-rw-r--r--test/syscalls/linux/socket.cc61
-rw-r--r--test/syscalls/linux/socket_inet_loopback.cc51
22 files changed, 1555 insertions, 313 deletions
diff --git a/test/iptables/filter_output.go b/test/iptables/filter_output.go
index f6d974b85..b1382d353 100644
--- a/test/iptables/filter_output.go
+++ b/test/iptables/filter_output.go
@@ -42,7 +42,7 @@ func (FilterOutputDropTCPDestPort) Name() string {
// ContainerAction implements TestCase.ContainerAction.
func (FilterOutputDropTCPDestPort) ContainerAction(ip net.IP) error {
- if err := filterTable("-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "DROP"); err != nil {
+ if err := filterTable("-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", "1024:65535", "-j", "DROP"); err != nil {
return err
}
diff --git a/test/iptables/iptables_test.go b/test/iptables/iptables_test.go
index 334d8e676..63a862d35 100644
--- a/test/iptables/iptables_test.go
+++ b/test/iptables/iptables_test.go
@@ -115,30 +115,6 @@ func TestFilterInputDropOnlyUDP(t *testing.T) {
singleTest(t, FilterInputDropOnlyUDP{})
}
-func TestNATRedirectUDPPort(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
- singleTest(t, NATRedirectUDPPort{})
-}
-
-func TestNATRedirectTCPPort(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
- singleTest(t, NATRedirectTCPPort{})
-}
-
-func TestNATDropUDP(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
- singleTest(t, NATDropUDP{})
-}
-
-func TestNATAcceptAll(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
- singleTest(t, NATAcceptAll{})
-}
-
func TestFilterInputDropTCPDestPort(t *testing.T) {
singleTest(t, FilterInputDropTCPDestPort{})
}
@@ -164,14 +140,10 @@ func TestFilterInputReturnUnderflow(t *testing.T) {
}
func TestFilterOutputDropTCPDestPort(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("filter OUTPUT isn't supported yet (gvisor.dev/issue/170).")
singleTest(t, FilterOutputDropTCPDestPort{})
}
func TestFilterOutputDropTCPSrcPort(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("filter OUTPUT isn't supported yet (gvisor.dev/issue/170).")
singleTest(t, FilterOutputDropTCPSrcPort{})
}
@@ -235,44 +207,54 @@ func TestOutputInvertDestination(t *testing.T) {
singleTest(t, FilterOutputInvertDestination{})
}
+func TestNATPreRedirectUDPPort(t *testing.T) {
+ singleTest(t, NATPreRedirectUDPPort{})
+}
+
+func TestNATPreRedirectTCPPort(t *testing.T) {
+ singleTest(t, NATPreRedirectTCPPort{})
+}
+
+func TestNATOutRedirectUDPPort(t *testing.T) {
+ singleTest(t, NATOutRedirectUDPPort{})
+}
+
+func TestNATOutRedirectTCPPort(t *testing.T) {
+ singleTest(t, NATOutRedirectTCPPort{})
+}
+
+func TestNATDropUDP(t *testing.T) {
+ singleTest(t, NATDropUDP{})
+}
+
+func TestNATAcceptAll(t *testing.T) {
+ singleTest(t, NATAcceptAll{})
+}
+
func TestNATOutRedirectIP(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
singleTest(t, NATOutRedirectIP{})
}
func TestNATOutDontRedirectIP(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
singleTest(t, NATOutDontRedirectIP{})
}
func TestNATOutRedirectInvert(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
singleTest(t, NATOutRedirectInvert{})
}
func TestNATPreRedirectIP(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
singleTest(t, NATPreRedirectIP{})
}
func TestNATPreDontRedirectIP(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
singleTest(t, NATPreDontRedirectIP{})
}
func TestNATPreRedirectInvert(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
singleTest(t, NATPreRedirectInvert{})
}
func TestNATRedirectRequiresProtocol(t *testing.T) {
- // TODO(gvisor.dev/issue/170): Enable when supported.
- t.Skip("NAT isn't supported yet (gvisor.dev/issue/170).")
singleTest(t, NATRedirectRequiresProtocol{})
}
diff --git a/test/iptables/iptables_util.go b/test/iptables/iptables_util.go
index 2a00677be..2f988cd18 100644
--- a/test/iptables/iptables_util.go
+++ b/test/iptables/iptables_util.go
@@ -151,7 +151,7 @@ func connectTCP(ip net.IP, port int, timeout time.Duration) error {
return err
}
if err := testutil.Poll(callback, timeout); err != nil {
- return fmt.Errorf("timed out waiting to connect IP, most recent error: %v", err)
+ return fmt.Errorf("timed out waiting to connect IP on port %v, most recent error: %v", port, err)
}
return nil
diff --git a/test/iptables/nat.go b/test/iptables/nat.go
index 40096901c..0a10ce7fe 100644
--- a/test/iptables/nat.go
+++ b/test/iptables/nat.go
@@ -26,8 +26,10 @@ const (
)
func init() {
- RegisterTestCase(NATRedirectUDPPort{})
- RegisterTestCase(NATRedirectTCPPort{})
+ RegisterTestCase(NATPreRedirectUDPPort{})
+ RegisterTestCase(NATPreRedirectTCPPort{})
+ RegisterTestCase(NATOutRedirectUDPPort{})
+ RegisterTestCase(NATOutRedirectTCPPort{})
RegisterTestCase(NATDropUDP{})
RegisterTestCase(NATAcceptAll{})
RegisterTestCase(NATPreRedirectIP{})
@@ -39,16 +41,16 @@ func init() {
RegisterTestCase(NATRedirectRequiresProtocol{})
}
-// NATRedirectUDPPort tests that packets are redirected to different port.
-type NATRedirectUDPPort struct{}
+// NATPreRedirectUDPPort tests that packets are redirected to different port.
+type NATPreRedirectUDPPort struct{}
// Name implements TestCase.Name.
-func (NATRedirectUDPPort) Name() string {
- return "NATRedirectUDPPort"
+func (NATPreRedirectUDPPort) Name() string {
+ return "NATPreRedirectUDPPort"
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATRedirectUDPPort) ContainerAction(ip net.IP) error {
+func (NATPreRedirectUDPPort) ContainerAction(ip net.IP) error {
if err := natTable("-A", "PREROUTING", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
return err
}
@@ -61,33 +63,53 @@ func (NATRedirectUDPPort) ContainerAction(ip net.IP) error {
}
// LocalAction implements TestCase.LocalAction.
-func (NATRedirectUDPPort) LocalAction(ip net.IP) error {
+func (NATPreRedirectUDPPort) LocalAction(ip net.IP) error {
return sendUDPLoop(ip, acceptPort, sendloopDuration)
}
-// NATRedirectTCPPort tests that connections are redirected on specified ports.
-type NATRedirectTCPPort struct{}
+// NATPreRedirectTCPPort tests that connections are redirected on specified ports.
+type NATPreRedirectTCPPort struct{}
// Name implements TestCase.Name.
-func (NATRedirectTCPPort) Name() string {
- return "NATRedirectTCPPort"
+func (NATPreRedirectTCPPort) Name() string {
+ return "NATPreRedirectTCPPort"
}
// ContainerAction implements TestCase.ContainerAction.
-func (NATRedirectTCPPort) ContainerAction(ip net.IP) error {
- if err := natTable("-A", "PREROUTING", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", redirectPort)); err != nil {
+func (NATPreRedirectTCPPort) ContainerAction(ip net.IP) error {
+ if err := natTable("-A", "PREROUTING", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
return err
}
// Listen for TCP packets on redirect port.
- return listenTCP(redirectPort, sendloopDuration)
+ return listenTCP(acceptPort, sendloopDuration)
}
// LocalAction implements TestCase.LocalAction.
-func (NATRedirectTCPPort) LocalAction(ip net.IP) error {
+func (NATPreRedirectTCPPort) LocalAction(ip net.IP) error {
return connectTCP(ip, dropPort, sendloopDuration)
}
+// NATOutRedirectUDPPort tests that packets are redirected to different port.
+type NATOutRedirectUDPPort struct{}
+
+// Name implements TestCase.Name.
+func (NATOutRedirectUDPPort) Name() string {
+ return "NATOutRedirectUDPPort"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (NATOutRedirectUDPPort) ContainerAction(ip net.IP) error {
+ dest := []byte{200, 0, 0, 1}
+ return loopbackTest(dest, "-A", "OUTPUT", "-p", "udp", "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort))
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (NATOutRedirectUDPPort) LocalAction(ip net.IP) error {
+ // No-op.
+ return nil
+}
+
// NATDropUDP tests that packets are not received in ports other than redirect
// port.
type NATDropUDP struct{}
@@ -329,3 +351,52 @@ func loopbackTest(dest net.IP, args ...string) error {
// sendCh will always take the full sendloop time.
return <-sendCh
}
+
+// NATOutRedirectTCPPort tests that connections are redirected on specified ports.
+type NATOutRedirectTCPPort struct{}
+
+// Name implements TestCase.Name.
+func (NATOutRedirectTCPPort) Name() string {
+ return "NATOutRedirectTCPPort"
+}
+
+// ContainerAction implements TestCase.ContainerAction.
+func (NATOutRedirectTCPPort) ContainerAction(ip net.IP) error {
+ if err := natTable("-A", "OUTPUT", "-p", "tcp", "-m", "tcp", "--dport", fmt.Sprintf("%d", dropPort), "-j", "REDIRECT", "--to-ports", fmt.Sprintf("%d", acceptPort)); err != nil {
+ return err
+ }
+
+ timeout := 20 * time.Second
+ dest := []byte{127, 0, 0, 1}
+ localAddr := net.TCPAddr{
+ IP: dest,
+ Port: acceptPort,
+ }
+
+ // Starts listening on port.
+ lConn, err := net.ListenTCP("tcp", &localAddr)
+ if err != nil {
+ return err
+ }
+ defer lConn.Close()
+
+ // Accept connections on port.
+ lConn.SetDeadline(time.Now().Add(timeout))
+ err = connectTCP(ip, dropPort, timeout)
+ if err != nil {
+ return err
+ }
+
+ conn, err := lConn.AcceptTCP()
+ if err != nil {
+ return err
+ }
+ conn.Close()
+
+ return nil
+}
+
+// LocalAction implements TestCase.LocalAction.
+func (NATOutRedirectTCPPort) LocalAction(ip net.IP) error {
+ return nil
+}
diff --git a/test/packetimpact/dut/posix_server.cc b/test/packetimpact/dut/posix_server.cc
index 86e580c6f..dc3024f44 100644
--- a/test/packetimpact/dut/posix_server.cc
+++ b/test/packetimpact/dut/posix_server.cc
@@ -11,6 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+#include <arpa/inet.h>
#include <fcntl.h>
#include <getopt.h>
#include <netdb.h>
@@ -25,7 +26,6 @@
#include <iostream>
#include <unordered_map>
-#include "arpa/inet.h"
#include "include/grpcpp/security/server_credentials.h"
#include "include/grpcpp/server_builder.h"
#include "test/packetimpact/proto/posix_server.grpc.pb.h"
@@ -60,6 +60,45 @@
return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT, "Unknown Sockaddr");
}
+::grpc::Status proto_to_sockaddr(const posix_server::Sockaddr &sockaddr_proto,
+ sockaddr_storage *addr) {
+ switch (sockaddr_proto.sockaddr_case()) {
+ case posix_server::Sockaddr::SockaddrCase::kIn: {
+ auto proto_in = sockaddr_proto.in();
+ if (proto_in.addr().size() != 4) {
+ return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "IPv4 address must be 4 bytes");
+ }
+ auto addr_in = reinterpret_cast<sockaddr_in *>(addr);
+ addr_in->sin_family = proto_in.family();
+ addr_in->sin_port = htons(proto_in.port());
+ proto_in.addr().copy(reinterpret_cast<char *>(&addr_in->sin_addr.s_addr),
+ 4);
+ break;
+ }
+ case posix_server::Sockaddr::SockaddrCase::kIn6: {
+ auto proto_in6 = sockaddr_proto.in6();
+ if (proto_in6.addr().size() != 16) {
+ return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "IPv6 address must be 16 bytes");
+ }
+ auto addr_in6 = reinterpret_cast<sockaddr_in6 *>(addr);
+ addr_in6->sin6_family = proto_in6.family();
+ addr_in6->sin6_port = htons(proto_in6.port());
+ addr_in6->sin6_flowinfo = htonl(proto_in6.flowinfo());
+ proto_in6.addr().copy(
+ reinterpret_cast<char *>(&addr_in6->sin6_addr.s6_addr), 16);
+ addr_in6->sin6_scope_id = htonl(proto_in6.scope_id());
+ break;
+ }
+ case posix_server::Sockaddr::SockaddrCase::SOCKADDR_NOT_SET:
+ default:
+ return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Unknown Sockaddr");
+ }
+ return ::grpc::Status::OK;
+}
+
class PosixImpl final : public posix_server::Posix::Service {
::grpc::Status Accept(grpc_impl::ServerContext *context,
const ::posix_server::AcceptRequest *request,
@@ -79,42 +118,13 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
"Missing address");
}
- sockaddr_storage addr;
- switch (request->addr().sockaddr_case()) {
- case posix_server::Sockaddr::SockaddrCase::kIn: {
- auto request_in = request->addr().in();
- if (request_in.addr().size() != 4) {
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "IPv4 address must be 4 bytes");
- }
- auto addr_in = reinterpret_cast<sockaddr_in *>(&addr);
- addr_in->sin_family = request_in.family();
- addr_in->sin_port = htons(request_in.port());
- request_in.addr().copy(
- reinterpret_cast<char *>(&addr_in->sin_addr.s_addr), 4);
- break;
- }
- case posix_server::Sockaddr::SockaddrCase::kIn6: {
- auto request_in6 = request->addr().in6();
- if (request_in6.addr().size() != 16) {
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "IPv6 address must be 16 bytes");
- }
- auto addr_in6 = reinterpret_cast<sockaddr_in6 *>(&addr);
- addr_in6->sin6_family = request_in6.family();
- addr_in6->sin6_port = htons(request_in6.port());
- addr_in6->sin6_flowinfo = htonl(request_in6.flowinfo());
- request_in6.addr().copy(
- reinterpret_cast<char *>(&addr_in6->sin6_addr.s6_addr), 16);
- addr_in6->sin6_scope_id = htonl(request_in6.scope_id());
- break;
- }
- case posix_server::Sockaddr::SockaddrCase::SOCKADDR_NOT_SET:
- default:
- return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
- "Unknown Sockaddr");
+ sockaddr_storage addr;
+ auto err = proto_to_sockaddr(request->addr(), &addr);
+ if (!err.ok()) {
+ return err;
}
+
response->set_ret(bind(request->sockfd(),
reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
response->set_errno_(errno);
@@ -129,6 +139,25 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
+ ::grpc::Status Connect(grpc_impl::ServerContext *context,
+ const ::posix_server::ConnectRequest *request,
+ ::posix_server::ConnectResponse *response) override {
+ if (!request->has_addr()) {
+ return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Missing address");
+ }
+ sockaddr_storage addr;
+ auto err = proto_to_sockaddr(request->addr(), &addr);
+ if (!err.ok()) {
+ return err;
+ }
+
+ response->set_ret(connect(
+ request->sockfd(), reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
+ response->set_errno_(errno);
+ return ::grpc::Status::OK;
+ }
+
::grpc::Status GetSockName(
grpc_impl::ServerContext *context,
const ::posix_server::GetSockNameRequest *request,
@@ -141,6 +170,48 @@ class PosixImpl final : public posix_server::Posix::Service {
return sockaddr_to_proto(addr, addrlen, response->mutable_addr());
}
+ ::grpc::Status GetSockOpt(
+ grpc_impl::ServerContext *context,
+ const ::posix_server::GetSockOptRequest *request,
+ ::posix_server::GetSockOptResponse *response) override {
+ switch (request->type()) {
+ case ::posix_server::GetSockOptRequest::BYTES: {
+ socklen_t optlen = request->optlen();
+ std::vector<char> buf(optlen);
+ response->set_ret(::getsockopt(request->sockfd(), request->level(),
+ request->optname(), buf.data(),
+ &optlen));
+ if (optlen >= 0) {
+ response->mutable_optval()->set_bytesval(buf.data(), optlen);
+ }
+ break;
+ }
+ case ::posix_server::GetSockOptRequest::INT: {
+ int intval = 0;
+ socklen_t optlen = sizeof(intval);
+ response->set_ret(::getsockopt(request->sockfd(), request->level(),
+ request->optname(), &intval, &optlen));
+ response->mutable_optval()->set_intval(intval);
+ break;
+ }
+ case ::posix_server::GetSockOptRequest::TIME: {
+ timeval tv;
+ socklen_t optlen = sizeof(tv);
+ response->set_ret(::getsockopt(request->sockfd(), request->level(),
+ request->optname(), &tv, &optlen));
+ response->mutable_optval()->mutable_timeval()->set_seconds(tv.tv_sec);
+ response->mutable_optval()->mutable_timeval()->set_microseconds(
+ tv.tv_usec);
+ break;
+ }
+ default:
+ return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Unknown SockOpt Type");
+ }
+ response->set_errno_(errno);
+ return ::grpc::Status::OK;
+ }
+
::grpc::Status Listen(grpc_impl::ServerContext *context,
const ::posix_server::ListenRequest *request,
::posix_server::ListenResponse *response) override {
@@ -158,37 +229,56 @@ class PosixImpl final : public posix_server::Posix::Service {
return ::grpc::Status::OK;
}
- ::grpc::Status SetSockOpt(
- grpc_impl::ServerContext *context,
- const ::posix_server::SetSockOptRequest *request,
- ::posix_server::SetSockOptResponse *response) override {
- response->set_ret(setsockopt(request->sockfd(), request->level(),
- request->optname(), request->optval().c_str(),
- request->optval().size()));
- response->set_errno_(errno);
- return ::grpc::Status::OK;
- }
+ ::grpc::Status SendTo(::grpc::ServerContext *context,
+ const ::posix_server::SendToRequest *request,
+ ::posix_server::SendToResponse *response) override {
+ if (!request->has_dest_addr()) {
+ return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Missing address");
+ }
+ sockaddr_storage addr;
+ auto err = proto_to_sockaddr(request->dest_addr(), &addr);
+ if (!err.ok()) {
+ return err;
+ }
- ::grpc::Status SetSockOptInt(
- ::grpc::ServerContext *context,
- const ::posix_server::SetSockOptIntRequest *request,
- ::posix_server::SetSockOptIntResponse *response) override {
- int opt = request->intval();
- response->set_ret(::setsockopt(request->sockfd(), request->level(),
- request->optname(), &opt, sizeof(opt)));
+ response->set_ret(::sendto(
+ request->sockfd(), request->buf().data(), request->buf().size(),
+ request->flags(), reinterpret_cast<sockaddr *>(&addr), sizeof(addr)));
response->set_errno_(errno);
return ::grpc::Status::OK;
}
- ::grpc::Status SetSockOptTimeval(
- ::grpc::ServerContext *context,
- const ::posix_server::SetSockOptTimevalRequest *request,
- ::posix_server::SetSockOptTimevalResponse *response) override {
- timeval tv = {.tv_sec = static_cast<__time_t>(request->timeval().seconds()),
- .tv_usec = static_cast<__suseconds_t>(
- request->timeval().microseconds())};
- response->set_ret(setsockopt(request->sockfd(), request->level(),
- request->optname(), &tv, sizeof(tv)));
+ ::grpc::Status SetSockOpt(
+ grpc_impl::ServerContext *context,
+ const ::posix_server::SetSockOptRequest *request,
+ ::posix_server::SetSockOptResponse *response) override {
+ switch (request->optval().val_case()) {
+ case ::posix_server::SockOptVal::kBytesval:
+ response->set_ret(setsockopt(request->sockfd(), request->level(),
+ request->optname(),
+ request->optval().bytesval().c_str(),
+ request->optval().bytesval().size()));
+ break;
+ case ::posix_server::SockOptVal::kIntval: {
+ int opt = request->optval().intval();
+ response->set_ret(::setsockopt(request->sockfd(), request->level(),
+ request->optname(), &opt, sizeof(opt)));
+ break;
+ }
+ case ::posix_server::SockOptVal::kTimeval: {
+ timeval tv = {.tv_sec = static_cast<__time_t>(
+ request->optval().timeval().seconds()),
+ .tv_usec = static_cast<__suseconds_t>(
+ request->optval().timeval().microseconds())};
+ response->set_ret(setsockopt(request->sockfd(), request->level(),
+ request->optname(), &tv, sizeof(tv)));
+ break;
+ }
+ default:
+ return ::grpc::Status(grpc::StatusCode::INVALID_ARGUMENT,
+ "Unknown SockOpt Type");
+ }
response->set_errno_(errno);
return ::grpc::Status::OK;
}
@@ -208,8 +298,10 @@ class PosixImpl final : public posix_server::Posix::Service {
std::vector<char> buf(request->len());
response->set_ret(
recv(request->sockfd(), buf.data(), buf.size(), request->flags()));
+ if (response->ret() >= 0) {
+ response->set_buf(buf.data(), response->ret());
+ }
response->set_errno_(errno);
- response->set_buf(buf.data(), response->ret());
return ::grpc::Status::OK;
}
};
diff --git a/test/packetimpact/proto/posix_server.proto b/test/packetimpact/proto/posix_server.proto
index 4035e1ee6..9dca563f1 100644
--- a/test/packetimpact/proto/posix_server.proto
+++ b/test/packetimpact/proto/posix_server.proto
@@ -42,6 +42,14 @@ message Timeval {
int64 microseconds = 2;
}
+message SockOptVal {
+ oneof val {
+ bytes bytesval = 1;
+ int32 intval = 2;
+ Timeval timeval = 3;
+ }
+}
+
// Request and Response pairs for each Posix service RPC call, sorted.
message AcceptRequest {
@@ -73,6 +81,16 @@ message CloseResponse {
int32 errno_ = 2; // "errno" may fail to compile in c++.
}
+message ConnectRequest {
+ int32 sockfd = 1;
+ Sockaddr addr = 2;
+}
+
+message ConnectResponse {
+ int32 ret = 1;
+ int32 errno_ = 2; // "errno" may fail to compile in c++.
+}
+
message GetSockNameRequest {
int32 sockfd = 1;
}
@@ -83,6 +101,26 @@ message GetSockNameResponse {
Sockaddr addr = 3;
}
+message GetSockOptRequest {
+ int32 sockfd = 1;
+ int32 level = 2;
+ int32 optname = 3;
+ int32 optlen = 4;
+ enum SockOptType {
+ UNSPECIFIED = 0;
+ BYTES = 1;
+ INT = 2;
+ TIME = 3;
+ }
+ SockOptType type = 5;
+}
+
+message GetSockOptResponse {
+ int32 ret = 1;
+ int32 errno_ = 2; // "errno" may fail to compile in c++.
+ SockOptVal optval = 3;
+}
+
message ListenRequest {
int32 sockfd = 1;
int32 backlog = 2;
@@ -104,38 +142,26 @@ message SendResponse {
int32 errno_ = 2;
}
-message SetSockOptRequest {
+message SendToRequest {
int32 sockfd = 1;
- int32 level = 2;
- int32 optname = 3;
- bytes optval = 4;
+ bytes buf = 2;
+ int32 flags = 3;
+ Sockaddr dest_addr = 4;
}
-message SetSockOptResponse {
+message SendToResponse {
int32 ret = 1;
int32 errno_ = 2; // "errno" may fail to compile in c++.
}
-message SetSockOptIntRequest {
- int32 sockfd = 1;
- int32 level = 2;
- int32 optname = 3;
- int32 intval = 4;
-}
-
-message SetSockOptIntResponse {
- int32 ret = 1;
- int32 errno_ = 2;
-}
-
-message SetSockOptTimevalRequest {
+message SetSockOptRequest {
int32 sockfd = 1;
int32 level = 2;
int32 optname = 3;
- Timeval timeval = 4;
+ SockOptVal optval = 4;
}
-message SetSockOptTimevalResponse {
+message SetSockOptResponse {
int32 ret = 1;
int32 errno_ = 2; // "errno" may fail to compile in c++.
}
@@ -170,22 +196,20 @@ service Posix {
rpc Bind(BindRequest) returns (BindResponse);
// Call close() on the DUT.
rpc Close(CloseRequest) returns (CloseResponse);
+ // Call connect() on the DUT.
+ rpc Connect(ConnectRequest) returns (ConnectResponse);
// Call getsockname() on the DUT.
rpc GetSockName(GetSockNameRequest) returns (GetSockNameResponse);
+ // Call getsockopt() on the DUT.
+ rpc GetSockOpt(GetSockOptRequest) returns (GetSockOptResponse);
// Call listen() on the DUT.
rpc Listen(ListenRequest) returns (ListenResponse);
// Call send() on the DUT.
rpc Send(SendRequest) returns (SendResponse);
- // Call setsockopt() on the DUT. You should prefer one of the other
- // SetSockOpt* functions with a more structured optval or else you may get the
- // encoding wrong, such as making a bad assumption about the server's word
- // sizes or endianness.
+ // Call sendto() on the DUT.
+ rpc SendTo(SendToRequest) returns (SendToResponse);
+ // Call setsockopt() on the DUT.
rpc SetSockOpt(SetSockOptRequest) returns (SetSockOptResponse);
- // Call setsockopt() on the DUT with an int optval.
- rpc SetSockOptInt(SetSockOptIntRequest) returns (SetSockOptIntResponse);
- // Call setsockopt() on the DUT with a Timeval optval.
- rpc SetSockOptTimeval(SetSockOptTimevalRequest)
- returns (SetSockOptTimevalResponse);
// Call socket() on the DUT.
rpc Socket(SocketRequest) returns (SocketResponse);
// Call recv() on the DUT.
diff --git a/test/packetimpact/testbench/connections.go b/test/packetimpact/testbench/connections.go
index 2280bd4ee..56ac3fa54 100644
--- a/test/packetimpact/testbench/connections.go
+++ b/test/packetimpact/testbench/connections.go
@@ -39,20 +39,28 @@ var remoteIPv6 = flag.String("remote_ipv6", "", "remote IPv6 address for test pa
var localMAC = flag.String("local_mac", "", "local mac address for test packets")
var remoteMAC = flag.String("remote_mac", "", "remote mac address for test packets")
-// pickPort makes a new socket and returns the socket FD and port. The domain
-// should be AF_INET or AF_INET6. The caller must close the FD when done with
+func portFromSockaddr(sa unix.Sockaddr) (uint16, error) {
+ switch sa := sa.(type) {
+ case *unix.SockaddrInet4:
+ return uint16(sa.Port), nil
+ case *unix.SockaddrInet6:
+ return uint16(sa.Port), nil
+ }
+ return 0, fmt.Errorf("sockaddr type %T does not contain port", sa)
+}
+
+// pickPort makes a new socket and returns the socket FD and port. The domain should be AF_INET or AF_INET6. The caller must close the FD when done with
// the port if there is no error.
-func pickPort(domain, typ int) (fd int, port uint16, err error) {
+func pickPort(domain, typ int) (fd int, sa unix.Sockaddr, err error) {
fd, err = unix.Socket(domain, typ, 0)
if err != nil {
- return -1, 0, err
+ return -1, nil, err
}
defer func() {
if err != nil {
err = multierr.Append(err, unix.Close(fd))
}
}()
- var sa unix.Sockaddr
switch domain {
case unix.AF_INET:
var sa4 unix.SockaddrInet4
@@ -63,31 +71,16 @@ func pickPort(domain, typ int) (fd int, port uint16, err error) {
copy(sa6.Addr[:], net.ParseIP(*localIPv6).To16())
sa = &sa6
default:
- return -1, 0, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain)
+ return -1, nil, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain)
}
if err = unix.Bind(fd, sa); err != nil {
- return -1, 0, err
+ return -1, nil, err
}
- newSockAddr, err := unix.Getsockname(fd)
+ sa, err = unix.Getsockname(fd)
if err != nil {
- return -1, 0, err
- }
- switch domain {
- case unix.AF_INET:
- newSockAddrInet4, ok := newSockAddr.(*unix.SockaddrInet4)
- if !ok {
- return -1, 0, fmt.Errorf("can't cast Getsockname result %T to SockaddrInet4", newSockAddr)
- }
- return fd, uint16(newSockAddrInet4.Port), nil
- case unix.AF_INET6:
- newSockAddrInet6, ok := newSockAddr.(*unix.SockaddrInet6)
- if !ok {
- return -1, 0, fmt.Errorf("can't cast Getsockname result %T to SockaddrInet6", newSockAddr)
- }
- return fd, uint16(newSockAddrInet6.Port), nil
- default:
- return -1, 0, fmt.Errorf("invalid domain %d, it should be one of unix.AF_INET or unix.AF_INET6", domain)
+ return -1, nil, err
}
+ return fd, sa, nil
}
// layerState stores the state of a layer of a connection.
@@ -282,7 +275,11 @@ func SeqNumValue(v seqnum.Value) *seqnum.Value {
// newTCPState creates a new TCPState.
func newTCPState(domain int, out, in TCP) (*tcpState, error) {
- portPickerFD, localPort, err := pickPort(domain, unix.SOCK_STREAM)
+ portPickerFD, localAddr, err := pickPort(domain, unix.SOCK_STREAM)
+ if err != nil {
+ return nil, err
+ }
+ localPort, err := portFromSockaddr(localAddr)
if err != nil {
return nil, err
}
@@ -385,10 +382,14 @@ type udpState struct {
var _ layerState = (*udpState)(nil)
// newUDPState creates a new udpState.
-func newUDPState(domain int, out, in UDP) (*udpState, error) {
- portPickerFD, localPort, err := pickPort(domain, unix.SOCK_DGRAM)
+func newUDPState(domain int, out, in UDP) (*udpState, unix.Sockaddr, error) {
+ portPickerFD, localAddr, err := pickPort(domain, unix.SOCK_DGRAM)
if err != nil {
- return nil, err
+ return nil, nil, err
+ }
+ localPort, err := portFromSockaddr(localAddr)
+ if err != nil {
+ return nil, nil, err
}
s := udpState{
out: UDP{SrcPort: &localPort},
@@ -396,12 +397,12 @@ func newUDPState(domain int, out, in UDP) (*udpState, error) {
portPickerFD: portPickerFD,
}
if err := s.out.merge(&out); err != nil {
- return nil, err
+ return nil, nil, err
}
if err := s.in.merge(&in); err != nil {
- return nil, err
+ return nil, nil, err
}
- return &s, nil
+ return &s, localAddr, nil
}
func (s *udpState) outgoing() Layer {
@@ -436,6 +437,7 @@ type Connection struct {
layerStates []layerState
injector Injector
sniffer Sniffer
+ localAddr unix.Sockaddr
t *testing.T
}
@@ -499,7 +501,7 @@ func (conn *Connection) CreateFrame(layer Layer, additionalLayers ...Layer) Laye
func (conn *Connection) SendFrame(frame Layers) {
outBytes, err := frame.ToBytes()
if err != nil {
- conn.t.Fatalf("can't build outgoing TCP packet: %s", err)
+ conn.t.Fatalf("can't build outgoing packet: %s", err)
}
conn.injector.Send(outBytes)
@@ -545,8 +547,9 @@ func (e *layersError) Error() string {
return e.got.diff(e.want)
}
-// Expect a frame with the final layerStates layer matching the provided Layer
-// within the timeout specified. If it doesn't arrive in time, it returns nil.
+// Expect expects a frame with the final layerStates layer matching the
+// provided Layer within the timeout specified. If it doesn't arrive in time,
+// an error is returned.
func (conn *Connection) Expect(layer Layer, timeout time.Duration) (Layer, error) {
// Make a frame that will ignore all but the final layer.
layers := make([]Layer, len(conn.layerStates))
@@ -671,8 +674,8 @@ func (conn *TCPIPv4) Close() {
(*Connection)(conn).Close()
}
-// Expect a frame with the TCP layer matching the provided TCP within the
-// timeout specified. If it doesn't arrive in time, it returns nil.
+// Expect expects a frame with the TCP layer matching the provided TCP within
+// the timeout specified. If it doesn't arrive in time, an error is returned.
func (conn *TCPIPv4) Expect(tcp TCP, timeout time.Duration) (*TCP, error) {
layer, err := (*Connection)(conn).Expect(&tcp, timeout)
if layer == nil {
@@ -756,7 +759,7 @@ func (conn *IPv6Conn) Close() {
}
// ExpectFrame expects a frame that matches the provided Layers within the
-// timeout specified. If it doesn't arrive in time, it returns nil.
+// timeout specified. If it doesn't arrive in time, an error is returned.
func (conn *IPv6Conn) ExpectFrame(frame Layers, timeout time.Duration) (Layers, error) {
return (*Connection)(conn).ExpectFrame(frame, timeout)
}
@@ -780,7 +783,7 @@ func NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 {
if err != nil {
t.Fatalf("can't make ipv4State: %s", err)
}
- tcpState, err := newUDPState(unix.AF_INET, outgoingUDP, incomingUDP)
+ udpState, localAddr, err := newUDPState(unix.AF_INET, outgoingUDP, incomingUDP)
if err != nil {
t.Fatalf("can't make udpState: %s", err)
}
@@ -794,24 +797,61 @@ func NewUDPIPv4(t *testing.T, outgoingUDP, incomingUDP UDP) UDPIPv4 {
}
return UDPIPv4{
- layerStates: []layerState{etherState, ipv4State, tcpState},
+ layerStates: []layerState{etherState, ipv4State, udpState},
injector: injector,
sniffer: sniffer,
+ localAddr: localAddr,
t: t,
}
}
+// LocalAddr gets the local socket address of this connection.
+func (conn *UDPIPv4) LocalAddr() unix.Sockaddr {
+ return conn.localAddr
+}
+
// CreateFrame builds a frame for the connection with layer overriding defaults
// of the innermost layer and additionalLayers added after it.
func (conn *UDPIPv4) CreateFrame(layer Layer, additionalLayers ...Layer) Layers {
return (*Connection)(conn).CreateFrame(layer, additionalLayers...)
}
+// Send a packet with reasonable defaults. Potentially override the UDP layer in
+// the connection with the provided layer and add additionLayers.
+func (conn *UDPIPv4) Send(udp UDP, additionalLayers ...Layer) {
+ (*Connection)(conn).Send(&udp, additionalLayers...)
+}
+
// SendFrame sends a frame on the wire and updates the state of all layers.
func (conn *UDPIPv4) SendFrame(frame Layers) {
(*Connection)(conn).SendFrame(frame)
}
+// SendIP sends a packet with additionalLayers following the IP layer in the
+// connection.
+func (conn *UDPIPv4) SendIP(additionalLayers ...Layer) {
+ var layersToSend Layers
+ for _, s := range conn.layerStates[:len(conn.layerStates)-1] {
+ layersToSend = append(layersToSend, s.outgoing())
+ }
+ layersToSend = append(layersToSend, additionalLayers...)
+ conn.SendFrame(layersToSend)
+}
+
+// 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(udp UDP, timeout time.Duration) (*UDP, error) {
+ layer, err := (*Connection)(conn).Expect(&udp, timeout)
+ if layer == nil {
+ return nil, err
+ }
+ gotUDP, ok := layer.(*UDP)
+ if !ok {
+ conn.t.Fatalf("expected %s to be UDP", layer)
+ }
+ return gotUDP, err
+}
+
// Close frees associated resources held by the UDPIPv4 connection.
func (conn *UDPIPv4) Close() {
(*Connection)(conn).Close()
diff --git a/test/packetimpact/testbench/dut.go b/test/packetimpact/testbench/dut.go
index 3f340c6bc..f68d9d62b 100644
--- a/test/packetimpact/testbench/dut.go
+++ b/test/packetimpact/testbench/dut.go
@@ -237,6 +237,33 @@ func (dut *DUT) CloseWithErrno(ctx context.Context, fd int32) (int32, error) {
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
+// Connect calls connect 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 ConnectWithErrno.
+func (dut *DUT) Connect(fd int32, sa unix.Sockaddr) {
+ dut.t.Helper()
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, err := dut.ConnectWithErrno(ctx, fd, sa)
+ if ret != 0 {
+ dut.t.Fatalf("failed to connect socket: %s", err)
+ }
+}
+
+// ConnectWithErrno calls bind on the DUT.
+func (dut *DUT) ConnectWithErrno(ctx context.Context, fd int32, sa unix.Sockaddr) (int32, error) {
+ dut.t.Helper()
+ req := pb.ConnectRequest{
+ Sockfd: fd,
+ Addr: dut.sockaddrToProto(sa),
+ }
+ resp, err := dut.posixServer.Connect(ctx, &req)
+ if err != nil {
+ dut.t.Fatalf("failed to call Connect: %s", err)
+ }
+ return resp.GetRet(), syscall.Errno(resp.GetErrno_())
+}
+
// GetSockName calls getsockname 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 GetSockNameWithErrno.
@@ -264,6 +291,109 @@ func (dut *DUT) GetSockNameWithErrno(ctx context.Context, sockfd int32) (int32,
return resp.GetRet(), dut.protoToSockaddr(resp.GetAddr()), syscall.Errno(resp.GetErrno_())
}
+func (dut *DUT) getSockOpt(ctx context.Context, sockfd, level, optname, optlen int32, typ pb.GetSockOptRequest_SockOptType) (int32, *pb.SockOptVal, error) {
+ dut.t.Helper()
+ req := pb.GetSockOptRequest{
+ Sockfd: sockfd,
+ Level: level,
+ Optname: optname,
+ Optlen: optlen,
+ Type: typ,
+ }
+ resp, err := dut.posixServer.GetSockOpt(ctx, &req)
+ if err != nil {
+ dut.t.Fatalf("failed to call GetSockOpt: %s", err)
+ }
+ optval := resp.GetOptval()
+ if optval == nil {
+ dut.t.Fatalf("GetSockOpt response does not contain a value")
+ }
+ return resp.GetRet(), optval, syscall.Errno(resp.GetErrno_())
+}
+
+// GetSockOpt calls getsockopt 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 GetSockOptWithErrno. Because endianess and the width of values
+// might differ between the testbench and DUT architectures, prefer to use a
+// more specific GetSockOptXxx function.
+func (dut *DUT) GetSockOpt(sockfd, level, optname, optlen int32) []byte {
+ dut.t.Helper()
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, optval, err := dut.GetSockOptWithErrno(ctx, sockfd, level, optname, optlen)
+ if ret != 0 {
+ dut.t.Fatalf("failed to GetSockOpt: %s", err)
+ }
+ return optval
+}
+
+// GetSockOptWithErrno calls getsockopt on the DUT. Because endianess and the
+// width of values might differ between the testbench and DUT architectures,
+// prefer to use a more specific GetSockOptXxxWithErrno function.
+func (dut *DUT) GetSockOptWithErrno(ctx context.Context, sockfd, level, optname, optlen int32) (int32, []byte, error) {
+ dut.t.Helper()
+ ret, optval, errno := dut.getSockOpt(ctx, sockfd, level, optname, optlen, pb.GetSockOptRequest_BYTES)
+ bytesval, ok := optval.Val.(*pb.SockOptVal_Bytesval)
+ if !ok {
+ dut.t.Fatalf("GetSockOpt got value type: %T, want bytes", optval)
+ }
+ return ret, bytesval.Bytesval, errno
+}
+
+// GetSockOptInt calls getsockopt on the DUT and causes a fatal test failure
+// if it doesn't succeed. If more control over the int optval or error handling
+// is needed, use GetSockOptIntWithErrno.
+func (dut *DUT) GetSockOptInt(sockfd, level, optname int32) int32 {
+ dut.t.Helper()
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, intval, err := dut.GetSockOptIntWithErrno(ctx, sockfd, level, optname)
+ if ret != 0 {
+ dut.t.Fatalf("failed to GetSockOptInt: %s", err)
+ }
+ return intval
+}
+
+// GetSockOptIntWithErrno calls getsockopt with an integer optval.
+func (dut *DUT) GetSockOptIntWithErrno(ctx context.Context, sockfd, level, optname int32) (int32, int32, error) {
+ dut.t.Helper()
+ ret, optval, errno := dut.getSockOpt(ctx, sockfd, level, optname, 0, pb.GetSockOptRequest_INT)
+ intval, ok := optval.Val.(*pb.SockOptVal_Intval)
+ if !ok {
+ dut.t.Fatalf("GetSockOpt got value type: %T, want int", optval)
+ }
+ return ret, intval.Intval, errno
+}
+
+// GetSockOptTimeval calls getsockopt 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 GetSockOptTimevalWithErrno.
+func (dut *DUT) GetSockOptTimeval(sockfd, level, optname int32) unix.Timeval {
+ dut.t.Helper()
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, timeval, err := dut.GetSockOptTimevalWithErrno(ctx, sockfd, level, optname)
+ if ret != 0 {
+ dut.t.Fatalf("failed to GetSockOptTimeval: %s", err)
+ }
+ return timeval
+}
+
+// GetSockOptTimevalWithErrno calls getsockopt and returns a timeval.
+func (dut *DUT) GetSockOptTimevalWithErrno(ctx context.Context, sockfd, level, optname int32) (int32, unix.Timeval, error) {
+ dut.t.Helper()
+ ret, optval, errno := dut.getSockOpt(ctx, sockfd, level, optname, 0, pb.GetSockOptRequest_TIME)
+ tv, ok := optval.Val.(*pb.SockOptVal_Timeval)
+ if !ok {
+ dut.t.Fatalf("GetSockOpt got value type: %T, want timeval", optval)
+ }
+ timeval := unix.Timeval{
+ Sec: tv.Timeval.Seconds,
+ Usec: tv.Timeval.Microseconds,
+ }
+ return ret, timeval, errno
+}
+
// Listen calls listen 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
// ListenWithErrno.
@@ -320,6 +450,51 @@ func (dut *DUT) SendWithErrno(ctx context.Context, sockfd int32, buf []byte, fla
return resp.GetRet(), syscall.Errno(resp.GetErrno_())
}
+// SendTo calls sendto 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
+// SendToWithErrno.
+func (dut *DUT) SendTo(sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) int32 {
+ dut.t.Helper()
+ ctx, cancel := context.WithTimeout(context.Background(), *rpcTimeout)
+ defer cancel()
+ ret, err := dut.SendToWithErrno(ctx, sockfd, buf, flags, destAddr)
+ if ret == -1 {
+ dut.t.Fatalf("failed to sendto: %s", err)
+ }
+ return ret
+}
+
+// SendToWithErrno calls sendto on the DUT.
+func (dut *DUT) SendToWithErrno(ctx context.Context, sockfd int32, buf []byte, flags int32, destAddr unix.Sockaddr) (int32, error) {
+ dut.t.Helper()
+ req := pb.SendToRequest{
+ Sockfd: sockfd,
+ Buf: buf,
+ Flags: flags,
+ DestAddr: dut.sockaddrToProto(destAddr),
+ }
+ resp, err := dut.posixServer.SendTo(ctx, &req)
+ if err != nil {
+ dut.t.Fatalf("faled to call SendTo: %s", err)
+ }
+ return resp.GetRet(), syscall.Errno(resp.GetErrno_())
+}
+
+func (dut *DUT) setSockOpt(ctx context.Context, sockfd, level, optname int32, optval *pb.SockOptVal) (int32, error) {
+ dut.t.Helper()
+ req := pb.SetSockOptRequest{
+ Sockfd: sockfd,
+ Level: level,
+ Optname: optname,
+ Optval: optval,
+ }
+ resp, err := dut.posixServer.SetSockOpt(ctx, &req)
+ if err != nil {
+ dut.t.Fatalf("failed to call SetSockOpt: %s", err)
+ }
+ return resp.GetRet(), syscall.Errno(resp.GetErrno_())
+}
+
// SetSockOpt calls setsockopt 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 SetSockOptWithErrno. Because endianess and the width of values
@@ -340,17 +515,7 @@ func (dut *DUT) SetSockOpt(sockfd, level, optname int32, optval []byte) {
// prefer to use a more specific SetSockOptXxxWithErrno function.
func (dut *DUT) SetSockOptWithErrno(ctx context.Context, sockfd, level, optname int32, optval []byte) (int32, error) {
dut.t.Helper()
- req := pb.SetSockOptRequest{
- Sockfd: sockfd,
- Level: level,
- Optname: optname,
- Optval: optval,
- }
- resp, err := dut.posixServer.SetSockOpt(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call SetSockOpt: %s", err)
- }
- return resp.GetRet(), syscall.Errno(resp.GetErrno_())
+ return dut.setSockOpt(ctx, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Bytesval{optval}})
}
// SetSockOptInt calls setsockopt on the DUT and causes a fatal test failure
@@ -369,17 +534,7 @@ func (dut *DUT) SetSockOptInt(sockfd, level, optname, optval int32) {
// SetSockOptIntWithErrno calls setsockopt with an integer optval.
func (dut *DUT) SetSockOptIntWithErrno(ctx context.Context, sockfd, level, optname, optval int32) (int32, error) {
dut.t.Helper()
- req := pb.SetSockOptIntRequest{
- Sockfd: sockfd,
- Level: level,
- Optname: optname,
- Intval: optval,
- }
- resp, err := dut.posixServer.SetSockOptInt(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call SetSockOptInt: %s", err)
- }
- return resp.GetRet(), syscall.Errno(resp.GetErrno_())
+ return dut.setSockOpt(ctx, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Intval{optval}})
}
// SetSockOptTimeval calls setsockopt on the DUT and causes a fatal test failure
@@ -403,17 +558,7 @@ func (dut *DUT) SetSockOptTimevalWithErrno(ctx context.Context, sockfd, level, o
Seconds: int64(tv.Sec),
Microseconds: int64(tv.Usec),
}
- req := pb.SetSockOptTimevalRequest{
- Sockfd: sockfd,
- Level: level,
- Optname: optname,
- Timeval: &timeval,
- }
- resp, err := dut.posixServer.SetSockOptTimeval(ctx, &req)
- if err != nil {
- dut.t.Fatalf("failed to call SetSockOptTimeval: %s", err)
- }
- return resp.GetRet(), syscall.Errno(resp.GetErrno_())
+ return dut.setSockOpt(ctx, sockfd, level, optname, &pb.SockOptVal{Val: &pb.SockOptVal_Timeval{&timeval}})
}
// Socket calls socket on the DUT and returns the file descriptor. If socket
diff --git a/test/packetimpact/testbench/layers.go b/test/packetimpact/testbench/layers.go
index 817f5c261..165f62d3b 100644
--- a/test/packetimpact/testbench/layers.go
+++ b/test/packetimpact/testbench/layers.go
@@ -58,7 +58,7 @@ type Layer interface {
next() Layer
// prev gets a pointer to the Layer encapsulating this one.
- prev() Layer
+ Prev() Layer
// setNext sets the pointer to the encapsulated Layer.
setNext(Layer)
@@ -80,7 +80,8 @@ func (lb *LayerBase) next() Layer {
return lb.nextLayer
}
-func (lb *LayerBase) prev() Layer {
+// Prev returns the previous layer.
+func (lb *LayerBase) Prev() Layer {
return lb.prevLayer
}
@@ -340,6 +341,8 @@ func (l *IPv4) ToBytes() ([]byte, error) {
fields.Protocol = uint8(header.TCPProtocolNumber)
case *UDP:
fields.Protocol = uint8(header.UDPProtocolNumber)
+ case *ICMPv4:
+ fields.Protocol = uint8(header.ICMPv4ProtocolNumber)
default:
// TODO(b/150301488): Support more protocols as needed.
return nil, fmt.Errorf("ipv4 header's next layer is unrecognized: %#v", n)
@@ -403,6 +406,8 @@ func parseIPv4(b []byte) (Layer, layerParser) {
nextParser = parseTCP
case header.UDPProtocolNumber:
nextParser = parseUDP
+ case header.ICMPv4ProtocolNumber:
+ nextParser = parseICMPv4
default:
// Assume that the rest is a payload.
nextParser = parsePayload
@@ -562,7 +567,7 @@ func (l *ICMPv6) ToBytes() ([]byte, error) {
if l.Checksum != nil {
h.SetChecksum(*l.Checksum)
} else {
- ipv6 := l.prev().(*IPv6)
+ ipv6 := l.Prev().(*IPv6)
h.SetChecksum(header.ICMPv6Checksum(h, *ipv6.SrcAddr, *ipv6.DstAddr, buffer.VectorisedView{}))
}
return h, nil
@@ -606,6 +611,72 @@ func (l *ICMPv6) merge(other Layer) error {
return mergeLayer(l, other)
}
+// ICMPv4Type is a helper routine that allocates a new header.ICMPv4Type value
+// to store t and returns a pointer to it.
+func ICMPv4Type(t header.ICMPv4Type) *header.ICMPv4Type {
+ return &t
+}
+
+// ICMPv4 can construct and match an ICMPv4 encapsulation.
+type ICMPv4 struct {
+ LayerBase
+ Type *header.ICMPv4Type
+ Code *uint8
+ Checksum *uint16
+}
+
+func (l *ICMPv4) String() string {
+ return stringLayer(l)
+}
+
+// ToBytes implements Layer.ToBytes.
+func (l *ICMPv4) ToBytes() ([]byte, error) {
+ b := make([]byte, header.ICMPv4MinimumSize)
+ h := header.ICMPv4(b)
+ if l.Type != nil {
+ h.SetType(*l.Type)
+ }
+ if l.Code != nil {
+ h.SetCode(byte(*l.Code))
+ }
+ if l.Checksum != nil {
+ h.SetChecksum(*l.Checksum)
+ return h, nil
+ }
+ payload, err := payload(l)
+ if err != nil {
+ return nil, err
+ }
+ h.SetChecksum(header.ICMPv4Checksum(h, payload))
+ return h, nil
+}
+
+// parseICMPv4 parses the bytes as an ICMPv4 header, returning a Layer and a
+// parser for the encapsulated payload.
+func parseICMPv4(b []byte) (Layer, layerParser) {
+ h := header.ICMPv4(b)
+ icmpv4 := ICMPv4{
+ Type: ICMPv4Type(h.Type()),
+ Code: Uint8(h.Code()),
+ Checksum: Uint16(h.Checksum()),
+ }
+ return &icmpv4, parsePayload
+}
+
+func (l *ICMPv4) match(other Layer) bool {
+ return equalLayer(l, other)
+}
+
+func (l *ICMPv4) length() int {
+ return header.ICMPv4MinimumSize
+}
+
+// merge overrides the values in l with the values from other but only in fields
+// where the value is not nil.
+func (l *ICMPv4) merge(other Layer) error {
+ return mergeLayer(l, other)
+}
+
// TCP can construct and match a TCP encapsulation.
type TCP struct {
LayerBase
@@ -676,25 +747,34 @@ func totalLength(l Layer) int {
return totalLength
}
+// payload returns a buffer.VectorisedView of l's payload.
+func payload(l Layer) (buffer.VectorisedView, error) {
+ var payloadBytes buffer.VectorisedView
+ for current := l.next(); current != nil; current = current.next() {
+ payload, err := current.ToBytes()
+ if err != nil {
+ return buffer.VectorisedView{}, fmt.Errorf("can't get bytes for next header: %s", payload)
+ }
+ payloadBytes.AppendView(payload)
+ }
+ return payloadBytes, nil
+}
+
// layerChecksum calculates the checksum of the Layer header, including the
-// peusdeochecksum of the layer before it and all the bytes after it..
+// peusdeochecksum of the layer before it and all the bytes after it.
func layerChecksum(l Layer, protoNumber tcpip.TransportProtocolNumber) (uint16, error) {
totalLength := uint16(totalLength(l))
var xsum uint16
- switch s := l.prev().(type) {
+ switch s := l.Prev().(type) {
case *IPv4:
xsum = header.PseudoHeaderChecksum(protoNumber, *s.SrcAddr, *s.DstAddr, totalLength)
default:
// TODO(b/150301488): Support more protocols, like IPv6.
return 0, fmt.Errorf("can't get src and dst addr from previous layer: %#v", s)
}
- var payloadBytes buffer.VectorisedView
- for current := l.next(); current != nil; current = current.next() {
- payload, err := current.ToBytes()
- if err != nil {
- return 0, fmt.Errorf("can't get bytes for next header: %s", payload)
- }
- payloadBytes.AppendView(payload)
+ payloadBytes, err := payload(l)
+ if err != nil {
+ return 0, err
}
xsum = header.ChecksumVV(payloadBytes, xsum)
return xsum, nil
diff --git a/test/packetimpact/tests/BUILD b/test/packetimpact/tests/BUILD
index 42f87e3f3..e4ced444b 100644
--- a/test/packetimpact/tests/BUILD
+++ b/test/packetimpact/tests/BUILD
@@ -29,10 +29,51 @@ packetimpact_go_test(
)
packetimpact_go_test(
+ name = "udp_icmp_error_propagation",
+ srcs = ["udp_icmp_error_propagation_test.go"],
+ # TODO(b/153926291): Fix netstack then remove the line below.
+ netstack = False,
+ deps = [
+ "//pkg/tcpip",
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
name = "tcp_window_shrink",
srcs = ["tcp_window_shrink_test.go"],
- # TODO(b/153202472): Fix netstack then remove the line below.
- netstack = False,
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "tcp_zero_window_probe",
+ srcs = ["tcp_zero_window_probe_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "tcp_zero_window_probe_retransmit",
+ srcs = ["tcp_zero_window_probe_retransmit_test.go"],
+ deps = [
+ "//pkg/tcpip/header",
+ "//test/packetimpact/testbench",
+ "@org_golang_x_sys//unix:go_default_library",
+ ],
+)
+
+packetimpact_go_test(
+ name = "tcp_zero_window_probe_usertimeout",
+ srcs = ["tcp_zero_window_probe_usertimeout_test.go"],
deps = [
"//pkg/tcpip/header",
"//test/packetimpact/testbench",
diff --git a/test/packetimpact/tests/tcp_close_wait_ack_test.go b/test/packetimpact/tests/tcp_close_wait_ack_test.go
index eb4cc7a65..153ce285b 100644
--- a/test/packetimpact/tests/tcp_close_wait_ack_test.go
+++ b/test/packetimpact/tests/tcp_close_wait_ack_test.go
@@ -28,7 +28,7 @@ import (
func TestCloseWaitAck(t *testing.T) {
for _, tt := range []struct {
description string
- makeTestingTCP func(conn *tb.TCPIPv4, seqNumOffset seqnum.Size) tb.TCP
+ makeTestingTCP func(conn *tb.TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) tb.TCP
seqNumOffset seqnum.Size
expectAck bool
}{
@@ -52,12 +52,14 @@ func TestCloseWaitAck(t *testing.T) {
// Send a FIN to DUT to intiate the active close
conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagFin)})
- if _, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second); err != nil {
+ gotTCP, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second)
+ if err != nil {
t.Fatalf("expected an ACK for our fin and DUT should enter CLOSE_WAIT: %s", err)
}
+ windowSize := seqnum.Size(*gotTCP.WindowSize)
// Send a segment with OTW Seq / unacc ACK and expect an ACK back
- conn.Send(tt.makeTestingTCP(&conn, tt.seqNumOffset), &tb.Payload{Bytes: []byte("Sample Data")})
+ conn.Send(tt.makeTestingTCP(&conn, tt.seqNumOffset, windowSize), &tb.Payload{Bytes: []byte("Sample Data")})
gotAck, err := conn.Expect(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, time.Second)
if tt.expectAck && err != nil {
t.Fatalf("expected an ack but got none: %s", err)
@@ -85,9 +87,8 @@ func TestCloseWaitAck(t *testing.T) {
// This generates an segment with seqnum = RCV.NXT + RCV.WND + seqNumOffset, the
// generated segment is only acceptable when seqNumOffset is 0, otherwise an ACK
// is expected from the receiver.
-func GenerateOTWSeqSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size) tb.TCP {
- windowSize := seqnum.Size(*conn.SynAck().WindowSize)
- lastAcceptable := conn.LocalSeqNum().Add(windowSize - 1)
+func GenerateOTWSeqSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) tb.TCP {
+ lastAcceptable := conn.LocalSeqNum().Add(windowSize)
otwSeq := uint32(lastAcceptable.Add(seqNumOffset))
return tb.TCP{SeqNum: tb.Uint32(otwSeq), Flags: tb.Uint8(header.TCPFlagAck)}
}
@@ -95,7 +96,7 @@ func GenerateOTWSeqSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size) tb.TCP {
// This generates an segment with acknum = SND.NXT + seqNumOffset, the generated
// segment is only acceptable when seqNumOffset is 0, otherwise an ACK is
// expected from the receiver.
-func GenerateUnaccACKSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size) tb.TCP {
+func GenerateUnaccACKSegment(conn *tb.TCPIPv4, seqNumOffset seqnum.Size, windowSize seqnum.Size) tb.TCP {
lastAcceptable := conn.RemoteSeqNum()
unaccAck := uint32(lastAcceptable.Add(seqNumOffset))
return tb.TCP{AckNum: tb.Uint32(unaccAck), Flags: tb.Uint8(header.TCPFlagAck)}
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
new file mode 100644
index 000000000..864e5a634
--- /dev/null
+++ b/test/packetimpact/tests/tcp_zero_window_probe_retransmit_test.go
@@ -0,0 +1,99 @@
+// 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_zero_window_probe_retransmit_test
+
+import (
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+// TestZeroWindowProbeRetransmit tests retransmits of zero window probes
+// to be sent at exponentially inreasing time intervals.
+func TestZeroWindowProbeRetransmit(t *testing.T) {
+ dut := tb.NewDUT(t)
+ defer dut.TearDown()
+ listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(listenFd)
+ conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
+ defer conn.Close()
+
+ conn.Handshake()
+ acceptFd, _ := dut.Accept(listenFd)
+ defer dut.Close(acceptFd)
+
+ dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+
+ sampleData := []byte("Sample Data")
+ samplePayload := &tb.Payload{Bytes: sampleData}
+
+ // Send and receive sample data to the dut.
+ dut.Send(acceptFd, sampleData, 0)
+ if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ }
+ conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
+ if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil {
+ t.Fatalf("expected a packet with sequence number %s", err)
+ }
+
+ // Check for the dut to keep the connection alive as long as the zero window
+ // probes are acknowledged. Check if the zero window probes are sent at
+ // exponentially increasing intervals. The timeout intervals are function
+ // of the recorded first zero probe transmission duration.
+ //
+ // Advertize zero receive window again.
+ conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
+ probeSeq := tb.Uint32(uint32(*conn.RemoteSeqNum() - 1))
+ ackProbe := tb.Uint32(uint32(*conn.RemoteSeqNum()))
+
+ startProbeDuration := time.Second
+ current := startProbeDuration
+ first := time.Now()
+ // Ask the dut to send out data.
+ dut.Send(acceptFd, sampleData, 0)
+ // 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++ {
+ 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(&tb.TCP{SeqNum: probeSeq}, nil, 2*current); err != nil {
+ t.Fatalf("expected a probe with sequence number %v: loop %d", probeSeq, i)
+ }
+ if i == 0 {
+ startProbeDuration = time.Now().Sub(first)
+ current = 2 * startProbeDuration
+ continue
+ }
+ // Check if the probes came at exponentially increasing intervals.
+ if p := time.Since(start); p < current-startProbeDuration {
+ t.Fatalf("zero probe came sooner interval %d probe %d\n", p, i)
+ }
+ // Acknowledge the zero-window probes from the dut.
+ conn.Send(tb.TCP{AckNum: ackProbe, Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
+ current *= 2
+ }
+ // Advertize non-zero window.
+ conn.Send(tb.TCP{AckNum: ackProbe, Flags: tb.Uint8(header.TCPFlagAck)})
+ // Expect the dut to recover and transmit data.
+ if _, err := conn.ExpectData(&tb.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ }
+}
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_test.go b/test/packetimpact/tests/tcp_zero_window_probe_test.go
new file mode 100644
index 000000000..4fa3d0cd4
--- /dev/null
+++ b/test/packetimpact/tests/tcp_zero_window_probe_test.go
@@ -0,0 +1,107 @@
+// 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_zero_window_probe_test
+
+import (
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+// TestZeroWindowProbe tests few cases of zero window probing over the
+// same connection.
+func TestZeroWindowProbe(t *testing.T) {
+ dut := tb.NewDUT(t)
+ defer dut.TearDown()
+ listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(listenFd)
+ conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
+ defer conn.Close()
+
+ conn.Handshake()
+ acceptFd, _ := dut.Accept(listenFd)
+ defer dut.Close(acceptFd)
+
+ dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+
+ sampleData := []byte("Sample Data")
+ samplePayload := &tb.Payload{Bytes: sampleData}
+
+ start := time.Now()
+ // Send and receive sample data to the dut.
+ dut.Send(acceptFd, sampleData, 0)
+ if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ }
+ sendTime := time.Now().Sub(start)
+ conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
+ if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil {
+ t.Fatalf("expected a packet with sequence number %s", err)
+ }
+
+ // Test 1: Check for receive of a zero window probe, record the duration for
+ // probe to be sent.
+ //
+ // Advertize zero window to the dut.
+ conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
+
+ // Expected sequence number of the zero window probe.
+ probeSeq := tb.Uint32(uint32(*conn.RemoteSeqNum() - 1))
+ // Expected ack number of the ACK for the probe.
+ ackProbe := tb.Uint32(uint32(*conn.RemoteSeqNum()))
+
+ // Expect there are no zero-window probes sent until there is data to be sent out
+ // from the dut.
+ if _, err := conn.ExpectData(&tb.TCP{SeqNum: probeSeq}, nil, 2*time.Second); err == nil {
+ t.Fatalf("unexpected a packet with sequence number %v: %s", probeSeq, err)
+ }
+
+ start = time.Now()
+ // Ask the dut to send out data.
+ dut.Send(acceptFd, sampleData, 0)
+ // Expect zero-window probe from the dut.
+ if _, err := conn.ExpectData(&tb.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil {
+ t.Fatalf("expected a packet with sequence number %v: %s", probeSeq, err)
+ }
+ // Expect the probe to be sent after some time. Compare against the previous
+ // time recorded when the dut immediately sends out data on receiving the
+ // send command.
+ if startProbeDuration := time.Now().Sub(start); startProbeDuration <= sendTime {
+ t.Fatalf("expected the first probe to be sent out after retransmission interval, got %v want > %v\n", startProbeDuration, sendTime)
+ }
+
+ // Test 2: Check if the dut recovers on advertizing non-zero receive window.
+ // and sends out the sample payload after the send window opens.
+ //
+ // Advertize non-zero window to the dut and ack the zero window probe.
+ conn.Send(tb.TCP{AckNum: ackProbe, Flags: tb.Uint8(header.TCPFlagAck)})
+ // Expect the dut to recover and transmit data.
+ if _, err := conn.ExpectData(&tb.TCP{SeqNum: ackProbe}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ }
+
+ // Test 3: Sanity check for dut's processing of a similar probe it sent.
+ // Check if the dut responds as we do for a similar probe sent to it.
+ // Basically with sequence number to one byte behind the unacknowledged
+ // sequence number.
+ p := tb.Uint32(uint32(*conn.LocalSeqNum()))
+ conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), SeqNum: tb.Uint32(uint32(*conn.LocalSeqNum() - 1))})
+ if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), AckNum: p}, nil, time.Second); err != nil {
+ t.Fatalf("expected a packet with ack number: %d: %s", p, err)
+ }
+}
diff --git a/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go
new file mode 100644
index 000000000..7d81c276c
--- /dev/null
+++ b/test/packetimpact/tests/tcp_zero_window_probe_usertimeout_test.go
@@ -0,0 +1,93 @@
+// 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_zero_window_probe_usertimeout_test
+
+import (
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+// TestZeroWindowProbeUserTimeout sanity tests user timeout when we are
+// retransmitting zero window probes.
+func TestZeroWindowProbeUserTimeout(t *testing.T) {
+ dut := tb.NewDUT(t)
+ defer dut.TearDown()
+ listenFd, remotePort := dut.CreateListener(unix.SOCK_STREAM, unix.IPPROTO_TCP, 1)
+ defer dut.Close(listenFd)
+ conn := tb.NewTCPIPv4(t, tb.TCP{DstPort: &remotePort}, tb.TCP{SrcPort: &remotePort})
+ defer conn.Close()
+
+ conn.Handshake()
+ acceptFd, _ := dut.Accept(listenFd)
+ defer dut.Close(acceptFd)
+
+ dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_NODELAY, 1)
+
+ sampleData := []byte("Sample Data")
+ samplePayload := &tb.Payload{Bytes: sampleData}
+
+ // Send and receive sample data to the dut.
+ dut.Send(acceptFd, sampleData, 0)
+ if _, err := conn.ExpectData(&tb.TCP{}, samplePayload, time.Second); err != nil {
+ t.Fatalf("expected a packet with payload %v: %s", samplePayload, err)
+ }
+ conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck | header.TCPFlagPsh)}, samplePayload)
+ if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)}, nil, time.Second); err != nil {
+ t.Fatalf("expected a packet with sequence number %s", err)
+ }
+
+ // Test 1: Check for receive of a zero window probe, record the duration for
+ // probe to be sent.
+ //
+ // Advertize zero window to the dut.
+ conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
+
+ // Expected sequence number of the zero window probe.
+ probeSeq := tb.Uint32(uint32(*conn.RemoteSeqNum() - 1))
+ start := time.Now()
+ // Ask the dut to send out data.
+ dut.Send(acceptFd, sampleData, 0)
+ // Expect zero-window probe from the dut.
+ if _, err := conn.ExpectData(&tb.TCP{SeqNum: probeSeq}, nil, time.Second); err != nil {
+ t.Fatalf("expected a packet with sequence number %v: %s", probeSeq, err)
+ }
+ // Record the duration for first probe, the dut sends the zero window probe after
+ // a retransmission time interval.
+ startProbeDuration := time.Now().Sub(start)
+
+ // Test 2: Check if the dut times out the connection by honoring usertimeout
+ // when the dut is sending zero-window probes.
+ //
+ // Reduce the retransmit timeout.
+ dut.SetSockOptInt(acceptFd, unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int32(startProbeDuration.Milliseconds()))
+ // Advertize zero window again.
+ conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck), WindowSize: tb.Uint16(0)})
+ // Ask the dut to send out data that would trigger zero window probe retransmissions.
+ dut.Send(acceptFd, sampleData, 0)
+
+ // Wait for the connection to timeout after multiple zero-window probe retransmissions.
+ time.Sleep(8 * startProbeDuration)
+
+ // Expect the connection to have timed out and closed which would cause the dut
+ // to reply with a RST to the ACK we send.
+ conn.Send(tb.TCP{Flags: tb.Uint8(header.TCPFlagAck)})
+ if _, err := conn.ExpectData(&tb.TCP{Flags: tb.Uint8(header.TCPFlagRst)}, nil, time.Second); err != nil {
+ t.Fatalf("expected a TCP RST")
+ }
+}
diff --git a/test/packetimpact/tests/udp_icmp_error_propagation_test.go b/test/packetimpact/tests/udp_icmp_error_propagation_test.go
new file mode 100644
index 000000000..c47af9a3e
--- /dev/null
+++ b/test/packetimpact/tests/udp_icmp_error_propagation_test.go
@@ -0,0 +1,355 @@
+// 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_icmp_error_propagation_test
+
+import (
+ "context"
+ "fmt"
+ "net"
+ "sync"
+ "syscall"
+ "testing"
+ "time"
+
+ "golang.org/x/sys/unix"
+ "gvisor.dev/gvisor/pkg/tcpip/header"
+ tb "gvisor.dev/gvisor/test/packetimpact/testbench"
+)
+
+type connectionMode bool
+
+func (c connectionMode) String() string {
+ if c {
+ return "Connected"
+ }
+ return "Connectionless"
+}
+
+type icmpError int
+
+const (
+ portUnreachable icmpError = iota
+ timeToLiveExceeded
+)
+
+func (e icmpError) String() string {
+ switch e {
+ case portUnreachable:
+ return "PortUnreachable"
+ case timeToLiveExceeded:
+ return "TimeToLiveExpired"
+ }
+ return "Unknown ICMP error"
+}
+
+func (e icmpError) ToICMPv4() *tb.ICMPv4 {
+ switch e {
+ case portUnreachable:
+ return &tb.ICMPv4{Type: tb.ICMPv4Type(header.ICMPv4DstUnreachable), Code: tb.Uint8(header.ICMPv4PortUnreachable)}
+ case timeToLiveExceeded:
+ return &tb.ICMPv4{Type: tb.ICMPv4Type(header.ICMPv4TimeExceeded), Code: tb.Uint8(header.ICMPv4TTLExceeded)}
+ }
+ return nil
+}
+
+type errorDetection struct {
+ name string
+ useValidConn bool
+ f func(context.Context, testData) error
+}
+
+type testData struct {
+ dut *tb.DUT
+ conn *tb.UDPIPv4
+ remoteFD int32
+ remotePort uint16
+ cleanFD int32
+ cleanPort uint16
+ wantErrno syscall.Errno
+}
+
+// wantErrno computes the errno to expect given the connection mode of a UDP
+// socket and the ICMP error it will receive.
+func wantErrno(c connectionMode, icmpErr icmpError) syscall.Errno {
+ if c && icmpErr == portUnreachable {
+ return syscall.Errno(unix.ECONNREFUSED)
+ }
+ return syscall.Errno(0)
+}
+
+// sendICMPError sends an ICMP error message in response to a UDP datagram.
+func sendICMPError(conn *tb.UDPIPv4, icmpErr icmpError, udp *tb.UDP) error {
+ if icmpErr == timeToLiveExceeded {
+ ip, ok := udp.Prev().(*tb.IPv4)
+ if !ok {
+ return fmt.Errorf("expected %s to be IPv4", udp.Prev())
+ }
+ *ip.TTL = 1
+ // Let serialization recalculate the checksum since we set the TTL
+ // to 1.
+ ip.Checksum = nil
+
+ // Note that the ICMP payload is valid in this case because the UDP
+ // payload is empty. If the UDP payload were not empty, the packet
+ // length during serialization may not be calculated correctly,
+ // resulting in a mal-formed packet.
+ conn.SendIP(icmpErr.ToICMPv4(), ip, udp)
+ } else {
+ conn.SendIP(icmpErr.ToICMPv4(), udp.Prev(), udp)
+ }
+ return nil
+}
+
+// testRecv tests observing the ICMP error through the recv syscall. A packet
+// is sent to the DUT, and if wantErrno is non-zero, then the first recv should
+// fail and the second should succeed. Otherwise if wantErrno is zero then the
+// first recv should succeed immediately.
+func testRecv(ctx context.Context, d testData) error {
+ // Check that receiving on the clean socket works.
+ d.conn.Send(tb.UDP{DstPort: &d.cleanPort})
+ d.dut.Recv(d.cleanFD, 100, 0)
+
+ d.conn.Send(tb.UDP{})
+
+ if d.wantErrno != syscall.Errno(0) {
+ ctx, cancel := context.WithTimeout(ctx, time.Second)
+ defer cancel()
+ ret, _, err := d.dut.RecvWithErrno(ctx, d.remoteFD, 100, 0)
+ if ret != -1 {
+ return fmt.Errorf("recv after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno)
+ }
+ if err != d.wantErrno {
+ return fmt.Errorf("recv after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno)
+ }
+ }
+
+ d.dut.Recv(d.remoteFD, 100, 0)
+ return nil
+}
+
+// testSendTo tests observing the ICMP error through the send syscall. If
+// wantErrno is non-zero, the first send should fail and a subsequent send
+// should suceed; while if wantErrno is zero then the first send should just
+// succeed.
+func testSendTo(ctx context.Context, d testData) error {
+ // Check that sending on the clean socket works.
+ d.dut.SendTo(d.cleanFD, nil, 0, d.conn.LocalAddr())
+ if _, err := d.conn.Expect(tb.UDP{SrcPort: &d.cleanPort}, time.Second); err != nil {
+ return fmt.Errorf("did not receive UDP packet from clean socket on DUT: %s", err)
+ }
+
+ if d.wantErrno != syscall.Errno(0) {
+ ctx, cancel := context.WithTimeout(ctx, time.Second)
+ defer cancel()
+ ret, err := d.dut.SendToWithErrno(ctx, d.remoteFD, nil, 0, d.conn.LocalAddr())
+
+ if ret != -1 {
+ return fmt.Errorf("sendto after ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", d.wantErrno)
+ }
+ if err != d.wantErrno {
+ return fmt.Errorf("sendto after ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, d.wantErrno)
+ }
+ }
+
+ d.dut.SendTo(d.remoteFD, nil, 0, d.conn.LocalAddr())
+ if _, err := d.conn.Expect(tb.UDP{}, time.Second); err != nil {
+ return fmt.Errorf("did not receive UDP packet as expected: %s", err)
+ }
+ return nil
+}
+
+func testSockOpt(_ context.Context, d testData) error {
+ // Check that there's no pending error on the clean socket.
+ if errno := syscall.Errno(d.dut.GetSockOptInt(d.cleanFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != syscall.Errno(0) {
+ return fmt.Errorf("unexpected error (%[1]d) %[1]v on clean socket", errno)
+ }
+
+ if errno := syscall.Errno(d.dut.GetSockOptInt(d.remoteFD, unix.SOL_SOCKET, unix.SO_ERROR)); errno != d.wantErrno {
+ return fmt.Errorf("SO_ERROR sockopt after ICMP error is (%[1]d) %[1]v, expected (%[2]d) %[2]v", errno, d.wantErrno)
+ }
+
+ // Check that after clearing socket error, sending doesn't fail.
+ d.dut.SendTo(d.remoteFD, nil, 0, d.conn.LocalAddr())
+ if _, err := d.conn.Expect(tb.UDP{}, time.Second); err != nil {
+ return fmt.Errorf("did not receive UDP packet as expected: %s", err)
+ }
+ return nil
+}
+
+// TestUDPICMPErrorPropagation tests that ICMP error messages in response to
+// UDP datagrams are processed correctly. RFC 1122 section 4.1.3.3 states that:
+// "UDP MUST pass to the application layer all ICMP error messages that it
+// receives from the IP layer."
+//
+// The test cases are parametrized in 3 dimensions: 1. the UDP socket is either
+// put into connection mode or left connectionless, 2. the ICMP message type
+// and code, and 3. the method by which the ICMP error is observed on the
+// socket: sendto, recv, or getsockopt(SO_ERROR).
+//
+// Linux's udp(7) man page states: "All fatal errors will be passed to the user
+// as an error return even when the socket is not connected. This includes
+// asynchronous errors received from the network." In practice, the only
+// combination of parameters to the test that causes an error to be observable
+// on the UDP socket is receiving a port unreachable message on a connected
+// socket.
+func TestUDPICMPErrorPropagation(t *testing.T) {
+ for _, connect := range []connectionMode{true, false} {
+ for _, icmpErr := range []icmpError{portUnreachable, timeToLiveExceeded} {
+ wantErrno := wantErrno(connect, icmpErr)
+
+ for _, errDetect := range []errorDetection{
+ errorDetection{"SendTo", false, testSendTo},
+ // Send to an address that's different from the one that caused an ICMP
+ // error to be returned.
+ errorDetection{"SendToValid", true, testSendTo},
+ errorDetection{"Recv", false, testRecv},
+ errorDetection{"SockOpt", false, testSockOpt},
+ } {
+ t.Run(fmt.Sprintf("%s/%s/%s", connect, icmpErr, errDetect.name), func(t *testing.T) {
+ dut := tb.NewDUT(t)
+ defer dut.TearDown()
+
+ remoteFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
+ defer dut.Close(remoteFD)
+
+ // Create a second, clean socket on the DUT to ensure that the ICMP
+ // error messages only affect the sockets they are intended for.
+ cleanFD, cleanPort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
+ defer dut.Close(cleanFD)
+
+ conn := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort})
+ defer conn.Close()
+
+ if connect {
+ dut.Connect(remoteFD, conn.LocalAddr())
+ dut.Connect(cleanFD, conn.LocalAddr())
+ }
+
+ dut.SendTo(remoteFD, nil, 0, conn.LocalAddr())
+ udp, err := conn.Expect(tb.UDP{}, time.Second)
+ if err != nil {
+ t.Fatalf("did not receive message from DUT: %s", err)
+ }
+
+ if err := sendICMPError(&conn, icmpErr, udp); err != nil {
+ t.Fatal(err)
+ }
+
+ errDetectConn := &conn
+ if errDetect.useValidConn {
+ // connClean is a UDP socket on the test runner that was not
+ // involved in the generation of the ICMP error. As such,
+ // interactions between it and the the DUT should be independent of
+ // the ICMP error at least at the port level.
+ connClean := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort})
+ defer connClean.Close()
+
+ errDetectConn = &connClean
+ }
+
+ if err := errDetect.f(context.Background(), testData{&dut, errDetectConn, remoteFD, remotePort, cleanFD, cleanPort, wantErrno}); err != nil {
+ t.Fatal(err)
+ }
+ })
+ }
+ }
+ }
+}
+
+// TestICMPErrorDuringUDPRecv tests behavior when a UDP socket is in the middle
+// of a blocking recv and receives an ICMP error.
+func TestICMPErrorDuringUDPRecv(t *testing.T) {
+ for _, connect := range []connectionMode{true, false} {
+ for _, icmpErr := range []icmpError{portUnreachable, timeToLiveExceeded} {
+ wantErrno := wantErrno(connect, icmpErr)
+
+ t.Run(fmt.Sprintf("%s/%s", connect, icmpErr), func(t *testing.T) {
+ dut := tb.NewDUT(t)
+ defer dut.TearDown()
+
+ remoteFD, remotePort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
+ defer dut.Close(remoteFD)
+
+ // Create a second, clean socket on the DUT to ensure that the ICMP
+ // error messages only affect the sockets they are intended for.
+ cleanFD, cleanPort := dut.CreateBoundSocket(unix.SOCK_DGRAM, unix.IPPROTO_UDP, net.ParseIP("0.0.0.0"))
+ defer dut.Close(cleanFD)
+
+ conn := tb.NewUDPIPv4(t, tb.UDP{DstPort: &remotePort}, tb.UDP{SrcPort: &remotePort})
+ defer conn.Close()
+
+ if connect {
+ dut.Connect(remoteFD, conn.LocalAddr())
+ dut.Connect(cleanFD, conn.LocalAddr())
+ }
+
+ dut.SendTo(remoteFD, nil, 0, conn.LocalAddr())
+ udp, err := conn.Expect(tb.UDP{}, time.Second)
+ if err != nil {
+ t.Fatalf("did not receive message from DUT: %s", err)
+ }
+
+ var wg sync.WaitGroup
+ wg.Add(2)
+ go func() {
+ if wantErrno != syscall.Errno(0) {
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ ret, _, err := dut.RecvWithErrno(ctx, remoteFD, 100, 0)
+ if ret != -1 {
+ t.Fatalf("recv during ICMP error succeeded unexpectedly, expected (%[1]d) %[1]v", wantErrno)
+ }
+ if err != wantErrno {
+ t.Fatalf("recv during ICMP error resulted in error (%[1]d) %[1]v, expected (%[2]d) %[2]v", err, wantErrno)
+ }
+ }
+
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ if ret, _, err := dut.RecvWithErrno(ctx, remoteFD, 100, 0); ret == -1 {
+ t.Fatalf("recv after ICMP error failed with (%[1]d) %[1]", err)
+ }
+ wg.Done()
+ }()
+
+ go func() {
+ ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+ defer cancel()
+
+ if ret, _, err := dut.RecvWithErrno(ctx, cleanFD, 100, 0); ret == -1 {
+ t.Fatalf("recv on clean socket failed with (%[1]d) %[1]", err)
+ }
+ wg.Done()
+ }()
+
+ // TODO(b/155684889) This sleep is to allow time for the DUT to
+ // actually call recv since we want the ICMP error to arrive during the
+ // blocking recv, and should be replaced when a better synchronization
+ // alternative is available.
+ time.Sleep(2 * time.Second)
+
+ if err := sendICMPError(&conn, icmpErr, udp); err != nil {
+ t.Fatal(err)
+ }
+
+ conn.Send(tb.UDP{DstPort: &cleanPort})
+ conn.Send(tb.UDP{})
+ wg.Wait()
+ })
+ }
+ }
+}
diff --git a/test/runtimes/proctor/BUILD b/test/runtimes/proctor/BUILD
index 50a26d182..da1e331e1 100644
--- a/test/runtimes/proctor/BUILD
+++ b/test/runtimes/proctor/BUILD
@@ -21,6 +21,7 @@ go_test(
size = "small",
srcs = ["proctor_test.go"],
library = ":proctor",
+ nocgo = 1,
deps = [
"//pkg/test/testutil",
],
diff --git a/test/syscalls/linux/BUILD b/test/syscalls/linux/BUILD
index d9095c95f..837e56042 100644
--- a/test/syscalls/linux/BUILD
+++ b/test/syscalls/linux/BUILD
@@ -365,6 +365,7 @@ cc_binary(
":socket_test_util",
"//test/util:file_descriptor",
gtest,
+ "//test/util:temp_umask",
"//test/util:test_main",
"//test/util:test_util",
],
diff --git a/test/syscalls/linux/eventfd.cc b/test/syscalls/linux/eventfd.cc
index 927001eee..548b05a64 100644
--- a/test/syscalls/linux/eventfd.cc
+++ b/test/syscalls/linux/eventfd.cc
@@ -100,6 +100,22 @@ TEST(EventfdTest, SmallRead) {
ASSERT_THAT(read(efd.get(), &l, 4), SyscallFailsWithErrno(EINVAL));
}
+TEST(EventfdTest, PreadIllegalSeek) {
+ FileDescriptor efd =
+ ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
+
+ uint64_t l = 0;
+ ASSERT_THAT(pread(efd.get(), &l, 4, 0), SyscallFailsWithErrno(ESPIPE));
+}
+
+TEST(EventfdTest, PwriteIllegalSeek) {
+ FileDescriptor efd =
+ ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
+
+ uint64_t l = 0;
+ ASSERT_THAT(pwrite(efd.get(), &l, 4, 0), SyscallFailsWithErrno(ESPIPE));
+}
+
TEST(EventfdTest, BigWrite) {
FileDescriptor efd =
ASSERT_NO_ERRNO_AND_VALUE(NewEventFD(0, EFD_NONBLOCK | EFD_SEMAPHORE));
diff --git a/test/syscalls/linux/pipe.cc b/test/syscalls/linux/pipe.cc
index 67228b66b..34291850d 100644
--- a/test/syscalls/linux/pipe.cc
+++ b/test/syscalls/linux/pipe.cc
@@ -631,11 +631,14 @@ INSTANTIATE_TEST_SUITE_P(
"namednonblocking",
[](int fds[2], bool* is_blocking, bool* is_namedpipe) {
// Create a new file-based pipe (non-blocking).
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
- SKIP_IF(mkfifo(file.path().c_str(), 0644) != 0);
- fds[0] = open(file.path().c_str(), O_NONBLOCK | O_RDONLY);
- fds[1] = open(file.path().c_str(), O_NONBLOCK | O_WRONLY);
+ std::string path;
+ {
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ path = file.path();
+ }
+ SKIP_IF(mkfifo(path.c_str(), 0644) != 0);
+ fds[0] = open(path.c_str(), O_NONBLOCK | O_RDONLY);
+ fds[1] = open(path.c_str(), O_NONBLOCK | O_WRONLY);
MaybeSave();
*is_blocking = false;
*is_namedpipe = true;
@@ -645,13 +648,15 @@ INSTANTIATE_TEST_SUITE_P(
"namedblocking",
[](int fds[2], bool* is_blocking, bool* is_namedpipe) {
// Create a new file-based pipe (blocking).
- auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
- ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
- SKIP_IF(mkfifo(file.path().c_str(), 0644) != 0);
- ScopedThread t([&file, &fds]() {
- fds[1] = open(file.path().c_str(), O_WRONLY);
- });
- fds[0] = open(file.path().c_str(), O_RDONLY);
+ std::string path;
+ {
+ auto file = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFile());
+ path = file.path();
+ }
+ SKIP_IF(mkfifo(path.c_str(), 0644) != 0);
+ ScopedThread t(
+ [&path, &fds]() { fds[1] = open(path.c_str(), O_WRONLY); });
+ fds[0] = open(path.c_str(), O_RDONLY);
t.Join();
MaybeSave();
*is_blocking = true;
diff --git a/test/syscalls/linux/proc.cc b/test/syscalls/linux/proc.cc
index 79a625ebc..63642880a 100644
--- a/test/syscalls/linux/proc.cc
+++ b/test/syscalls/linux/proc.cc
@@ -1939,43 +1939,66 @@ TEST(ProcSelfMounts, RequiredFieldsArePresent) {
}
void CheckDuplicatesRecursively(std::string path) {
- errno = 0;
- DIR* dir = opendir(path.c_str());
- if (dir == nullptr) {
- // Ignore any directories we can't read or missing directories as the
- // directory could have been deleted/mutated from the time the parent
- // directory contents were read.
- return;
- }
- auto dir_closer = Cleanup([&dir]() { closedir(dir); });
- std::unordered_set<std::string> children;
- while (true) {
- // Readdir(3): If the end of the directory stream is reached, NULL is
- // returned and errno is not changed. If an error occurs, NULL is returned
- // and errno is set appropriately. To distinguish end of stream and from an
- // error, set errno to zero before calling readdir() and then check the
- // value of errno if NULL is returned.
+ std::vector<std::string> child_dirs;
+
+ // There is the known issue of the linux procfs, that two consequent calls of
+ // readdir can return the same entry twice if between these calls one or more
+ // entries have been removed from this directory.
+ int max_attempts = 5;
+ for (int i = 0; i < max_attempts; i++) {
+ child_dirs.clear();
errno = 0;
- struct dirent* dp = readdir(dir);
- if (dp == nullptr) {
- ASSERT_EQ(errno, 0) << path;
- break; // We're done.
+ bool success = true;
+ DIR* dir = opendir(path.c_str());
+ if (dir == nullptr) {
+ // Ignore any directories we can't read or missing directories as the
+ // directory could have been deleted/mutated from the time the parent
+ // directory contents were read.
+ return;
}
-
- if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
- continue;
+ auto dir_closer = Cleanup([&dir]() { closedir(dir); });
+ std::unordered_set<std::string> children;
+ while (true) {
+ // Readdir(3): If the end of the directory stream is reached, NULL is
+ // returned and errno is not changed. If an error occurs, NULL is
+ // returned and errno is set appropriately. To distinguish end of stream
+ // and from an error, set errno to zero before calling readdir() and then
+ // check the value of errno if NULL is returned.
+ errno = 0;
+ struct dirent* dp = readdir(dir);
+ if (dp == nullptr) {
+ ASSERT_EQ(errno, 0) << path;
+ break; // We're done.
+ }
+
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0) {
+ continue;
+ }
+
+ // Ignore a duplicate entry if it isn't the last attempt.
+ if (i == max_attempts - 1) {
+ ASSERT_EQ(children.find(std::string(dp->d_name)), children.end())
+ << absl::StrCat(path, "/", dp->d_name);
+ } else if (children.find(std::string(dp->d_name)) != children.end()) {
+ std::cerr << "Duplicate entry: " << i << ":"
+ << absl::StrCat(path, "/", dp->d_name) << std::endl;
+ success = false;
+ break;
+ }
+ children.insert(std::string(dp->d_name));
+
+ ASSERT_NE(dp->d_type, DT_UNKNOWN);
+
+ if (dp->d_type == DT_DIR) {
+ child_dirs.push_back(std::string(dp->d_name));
+ }
}
-
- ASSERT_EQ(children.find(std::string(dp->d_name)), children.end())
- << dp->d_name;
- children.insert(std::string(dp->d_name));
-
- ASSERT_NE(dp->d_type, DT_UNKNOWN);
-
- if (dp->d_type != DT_DIR) {
- continue;
+ if (success) {
+ break;
}
- CheckDuplicatesRecursively(absl::StrCat(path, "/", dp->d_name));
+ }
+ for (auto dname = child_dirs.begin(); dname != child_dirs.end(); dname++) {
+ CheckDuplicatesRecursively(absl::StrCat(path, "/", *dname));
}
}
diff --git a/test/syscalls/linux/socket.cc b/test/syscalls/linux/socket.cc
index 3a07ac8d2..703d594a2 100644
--- a/test/syscalls/linux/socket.cc
+++ b/test/syscalls/linux/socket.cc
@@ -13,11 +13,14 @@
// limitations under the License.
#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
#include <unistd.h>
#include "gtest/gtest.h"
#include "test/syscalls/linux/socket_test_util.h"
#include "test/util/file_descriptor.h"
+#include "test/util/temp_umask.h"
#include "test/util/test_util.h"
namespace gvisor {
@@ -58,11 +61,69 @@ TEST(SocketTest, ProtocolInet) {
}
}
+TEST(SocketTest, UnixSocketFileMode) {
+ // TODO(gvisor.dev/issue/1624): Re-enable this test once VFS1 is deleted. It
+ // should pass in VFS2.
+ SKIP_IF(IsRunningOnGvisor());
+
+ FileDescriptor bound =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX));
+
+ // The permissions of the file created with bind(2) should be defined by the
+ // permissions of the bound socket and the umask.
+ mode_t sock_perm = 0765, mask = 0123;
+ ASSERT_THAT(fchmod(bound.get(), sock_perm), SyscallSucceeds());
+ TempUmask m(mask);
+
+ struct sockaddr_un addr =
+ ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(/*abstract=*/false, AF_UNIX));
+ ASSERT_THAT(bind(bound.get(), reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(addr)),
+ SyscallSucceeds());
+
+ struct stat statbuf = {};
+ ASSERT_THAT(stat(addr.sun_path, &statbuf), SyscallSucceeds());
+ EXPECT_EQ(statbuf.st_mode, S_IFSOCK | sock_perm & ~mask);
+}
+
+TEST(SocketTest, UnixConnectNeedsWritePerm) {
+ // TODO(gvisor.dev/issue/1624): Re-enable this test once VFS1 is deleted. It
+ // should succeed in VFS2.
+ SKIP_IF(IsRunningOnGvisor());
+
+ FileDescriptor bound =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX));
+
+ struct sockaddr_un addr =
+ ASSERT_NO_ERRNO_AND_VALUE(UniqueUnixAddr(/*abstract=*/false, AF_UNIX));
+ ASSERT_THAT(bind(bound.get(), reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(addr)),
+ SyscallSucceeds());
+ ASSERT_THAT(listen(bound.get(), 1), SyscallSucceeds());
+
+ // Connect should fail without write perms.
+ ASSERT_THAT(chmod(addr.sun_path, 0500), SyscallSucceeds());
+ FileDescriptor client =
+ ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_UNIX, SOCK_STREAM, PF_UNIX));
+ EXPECT_THAT(connect(client.get(), reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(addr)),
+ SyscallFailsWithErrno(EACCES));
+
+ // Connect should succeed with write perms.
+ ASSERT_THAT(chmod(addr.sun_path, 0200), SyscallSucceeds());
+ EXPECT_THAT(connect(client.get(), reinterpret_cast<struct sockaddr*>(&addr),
+ sizeof(addr)),
+ SyscallSucceeds());
+}
+
using SocketOpenTest = ::testing::TestWithParam<int>;
// UDS cannot be opened.
TEST_P(SocketOpenTest, Unix) {
// FIXME(b/142001530): Open incorrectly succeeds on gVisor.
+ //
+ // TODO(gvisor.dev/issue/1624): Re-enable this test once VFS1 is deleted. It
+ // should succeed in VFS2.
SKIP_IF(IsRunningOnGvisor());
FileDescriptor bound =
diff --git a/test/syscalls/linux/socket_inet_loopback.cc b/test/syscalls/linux/socket_inet_loopback.cc
index 9400ffaeb..fa890ec98 100644
--- a/test/syscalls/linux/socket_inet_loopback.cc
+++ b/test/syscalls/linux/socket_inet_loopback.cc
@@ -162,7 +162,7 @@ TEST_P(DualStackSocketTest, AddressOperations) {
ASSERT_NO_ERRNO(SetAddrPort(
addr.family(), const_cast<sockaddr_storage*>(&addr.addr), 1337));
- EXPECT_THAT(connect(fd.get(), addr_in, addr.addr_len),
+ EXPECT_THAT(RetryEINTR(connect)(fd.get(), addr_in, addr.addr_len),
SyscallSucceeds())
<< GetAddrStr(addr_in);
bound = true;
@@ -353,8 +353,9 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdownListen) {
for (int i = 0; i < kBacklog; i++) {
auto client = ASSERT_NO_ERRNO_AND_VALUE(
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(connect(client.get(), reinterpret_cast<sockaddr*>(&conn_addr),
- connector.addr_len),
+ ASSERT_THAT(RetryEINTR(connect)(client.get(),
+ reinterpret_cast<sockaddr*>(&conn_addr),
+ connector.addr_len),
SyscallSucceeds());
}
for (int i = 0; i < kBacklog; i++) {
@@ -397,8 +398,9 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdown) {
for (int i = 0; i < kFDs; i++) {
auto client = ASSERT_NO_ERRNO_AND_VALUE(
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(connect(client.get(), reinterpret_cast<sockaddr*>(&conn_addr),
- connector.addr_len),
+ ASSERT_THAT(RetryEINTR(connect)(client.get(),
+ reinterpret_cast<sockaddr*>(&conn_addr),
+ connector.addr_len),
SyscallSucceeds());
ASSERT_THAT(accept(listen_fd.get(), nullptr, nullptr), SyscallSucceeds());
}
@@ -425,8 +427,9 @@ TEST_P(SocketInetLoopbackTest, TCPListenShutdown) {
for (int i = 0; i < kFDs; i++) {
auto client = ASSERT_NO_ERRNO_AND_VALUE(
Socket(connector.family(), SOCK_STREAM, IPPROTO_TCP));
- ASSERT_THAT(connect(client.get(), reinterpret_cast<sockaddr*>(&conn_addr),
- connector.addr_len),
+ ASSERT_THAT(RetryEINTR(connect)(client.get(),
+ reinterpret_cast<sockaddr*>(&conn_addr),
+ connector.addr_len),
SyscallFailsWithErrno(ECONNREFUSED));
}
}
@@ -1824,10 +1827,10 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReserved) {
// Connect to bind an ephemeral port.
const FileDescriptor connected_fd =
ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(
- connect(connected_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- bound_addr_len),
- SyscallSucceeds());
+ ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(),
+ reinterpret_cast<sockaddr*>(&bound_addr),
+ bound_addr_len),
+ SyscallSucceeds());
// Get the ephemeral port.
sockaddr_storage connected_addr = {};
@@ -1930,8 +1933,9 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V6EphemeralPortReservedReuseAddr) {
ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR,
&kSockOptOn, sizeof(kSockOptOn)),
SyscallSucceeds());
- ASSERT_THAT(connect(connected_fd.get(),
- reinterpret_cast<sockaddr*>(&bound_addr), bound_addr_len),
+ ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(),
+ reinterpret_cast<sockaddr*>(&bound_addr),
+ bound_addr_len),
SyscallSucceeds());
// Get the ephemeral port.
@@ -1991,10 +1995,10 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4MappedEphemeralPortReserved) {
// Connect to bind an ephemeral port.
const FileDescriptor connected_fd =
ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(
- connect(connected_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- bound_addr_len),
- SyscallSucceeds());
+ ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(),
+ reinterpret_cast<sockaddr*>(&bound_addr),
+ bound_addr_len),
+ SyscallSucceeds());
// Get the ephemeral port.
sockaddr_storage connected_addr = {};
@@ -2121,8 +2125,9 @@ TEST_P(SocketMultiProtocolInetLoopbackTest,
ASSERT_THAT(setsockopt(connected_fd.get(), SOL_SOCKET, SO_REUSEADDR,
&kSockOptOn, sizeof(kSockOptOn)),
SyscallSucceeds());
- ASSERT_THAT(connect(connected_fd.get(),
- reinterpret_cast<sockaddr*>(&bound_addr), bound_addr_len),
+ ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(),
+ reinterpret_cast<sockaddr*>(&bound_addr),
+ bound_addr_len),
SyscallSucceeds());
// Get the ephemeral port.
@@ -2182,10 +2187,10 @@ TEST_P(SocketMultiProtocolInetLoopbackTest, V4EphemeralPortReserved) {
// Connect to bind an ephemeral port.
const FileDescriptor connected_fd =
ASSERT_NO_ERRNO_AND_VALUE(Socket(test_addr.family(), param.type, 0));
- ASSERT_THAT(
- connect(connected_fd.get(), reinterpret_cast<sockaddr*>(&bound_addr),
- bound_addr_len),
- SyscallSucceeds());
+ ASSERT_THAT(RetryEINTR(connect)(connected_fd.get(),
+ reinterpret_cast<sockaddr*>(&bound_addr),
+ bound_addr_len),
+ SyscallSucceeds());
// Get the ephemeral port.
sockaddr_storage connected_addr = {};