mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
2592 lines
90 KiB
C++
2592 lines
90 KiB
C++
// Copyright 2018 The gVisor Authors.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include <arpa/inet.h>
|
|
#include <fcntl.h>
|
|
#include <netinet/icmp6.h>
|
|
#include <netinet/ip_icmp.h>
|
|
#include <sched.h>
|
|
|
|
#include <algorithm>
|
|
#include <cerrno>
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <ctime>
|
|
#include <iterator>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "gmock/gmock.h"
|
|
|
|
#ifdef __linux__
|
|
#include <linux/errqueue.h>
|
|
#include <linux/filter.h>
|
|
#endif // __linux__
|
|
#include <netinet/in.h>
|
|
#include <poll.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/types.h>
|
|
|
|
#include "absl/strings/str_format.h"
|
|
#ifndef SIOCGSTAMP
|
|
#include <linux/sockios.h>
|
|
#endif
|
|
|
|
#include "gtest/gtest.h"
|
|
#include "absl/base/macros.h"
|
|
#include "absl/time/clock.h"
|
|
#include "absl/time/time.h"
|
|
#include "test/syscalls/linux/ip_socket_test_util.h"
|
|
#include "test/syscalls/linux/unix_domain_socket_test_util.h"
|
|
#include "test/util/file_descriptor.h"
|
|
#include "test/util/posix_error.h"
|
|
#include "test/util/socket_util.h"
|
|
#include "test/util/test_util.h"
|
|
#include "test/util/thread_util.h"
|
|
|
|
namespace gvisor {
|
|
namespace testing {
|
|
|
|
namespace {
|
|
|
|
int IcmpTimeoutMillis() {
|
|
// Fuchsia's CI infra is susceptible to timing jumps. Set a negative timeout
|
|
// so that poll will block indefinitely, which effectively delegates the
|
|
// timeout to infra.
|
|
return GvisorPlatform() == Platform::kFuchsia ? -1 : 1000;
|
|
}
|
|
|
|
// Fixture for tests parameterized by the address family to use (AF_INET and
|
|
// AF_INET6) when creating sockets.
|
|
class UdpSocketTest : public ::testing::TestWithParam<int> {
|
|
protected:
|
|
// Creates two sockets that will be used by test cases.
|
|
void SetUp() override;
|
|
|
|
// Binds the socket bind_ to the loopback and updates bind_addr_.
|
|
PosixError BindLoopback();
|
|
|
|
// Binds the socket bind_ to Any and updates bind_addr_.
|
|
PosixError BindAny();
|
|
|
|
// Binds given socket to address addr and updates.
|
|
PosixError BindSocket(int socket, struct sockaddr* addr);
|
|
|
|
// Return initialized Any address to port 0.
|
|
struct sockaddr_storage InetAnyAddr();
|
|
|
|
// Return initialized Loopback address to port 0.
|
|
struct sockaddr_storage InetLoopbackAddr();
|
|
|
|
// Disconnects socket sockfd.
|
|
void Disconnect(int sockfd);
|
|
|
|
// Socket used by Bind methods
|
|
FileDescriptor bind_;
|
|
|
|
// Second socket used for tests.
|
|
FileDescriptor sock_;
|
|
|
|
// Address for bind_ socket.
|
|
struct sockaddr* bind_addr_;
|
|
|
|
// Initialized to the length based on GetParam().
|
|
socklen_t addrlen_;
|
|
|
|
// Storage for bind_addr_.
|
|
struct sockaddr_storage bind_addr_storage_;
|
|
|
|
private:
|
|
// Helper to initialize addrlen_ for the test case.
|
|
socklen_t GetAddrLength();
|
|
};
|
|
|
|
// Blocks until POLLIN is signaled on fd.
|
|
void BlockUntilPollin(int fd) {
|
|
constexpr int kInfiniteTimeout = -1;
|
|
pollfd pfd = {
|
|
.fd = fd,
|
|
.events = POLLIN,
|
|
};
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, kInfiniteTimeout),
|
|
SyscallSucceedsWithValue(1));
|
|
ASSERT_EQ(pfd.revents, POLLIN);
|
|
}
|
|
|
|
// Gets a pointer to the port component of the given address.
|
|
uint16_t* Port(struct sockaddr_storage* addr) {
|
|
switch (addr->ss_family) {
|
|
case AF_INET: {
|
|
auto sin = reinterpret_cast<struct sockaddr_in*>(addr);
|
|
return &sin->sin_port;
|
|
}
|
|
case AF_INET6: {
|
|
auto sin6 = reinterpret_cast<struct sockaddr_in6*>(addr);
|
|
return &sin6->sin6_port;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Sets addr port to "port".
|
|
void SetPort(struct sockaddr_storage* addr, uint16_t port) {
|
|
switch (addr->ss_family) {
|
|
case AF_INET: {
|
|
auto sin = reinterpret_cast<struct sockaddr_in*>(addr);
|
|
sin->sin_port = port;
|
|
break;
|
|
}
|
|
case AF_INET6: {
|
|
auto sin6 = reinterpret_cast<struct sockaddr_in6*>(addr);
|
|
sin6->sin6_port = port;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void UdpSocketTest::SetUp() {
|
|
addrlen_ = GetAddrLength();
|
|
|
|
bind_ =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_DGRAM, IPPROTO_UDP));
|
|
memset(&bind_addr_storage_, 0, sizeof(bind_addr_storage_));
|
|
bind_addr_ = AsSockAddr(&bind_addr_storage_);
|
|
|
|
sock_ =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_DGRAM, IPPROTO_UDP));
|
|
}
|
|
|
|
PosixError UdpSocketTest::BindLoopback() {
|
|
bind_addr_storage_ = InetLoopbackAddr();
|
|
struct sockaddr* bind_addr_ = AsSockAddr(&bind_addr_storage_);
|
|
return BindSocket(bind_.get(), bind_addr_);
|
|
}
|
|
|
|
PosixError UdpSocketTest::BindAny() {
|
|
bind_addr_storage_ = InetAnyAddr();
|
|
struct sockaddr* bind_addr_ = AsSockAddr(&bind_addr_storage_);
|
|
return BindSocket(bind_.get(), bind_addr_);
|
|
}
|
|
|
|
PosixError UdpSocketTest::BindSocket(int socket, struct sockaddr* addr) {
|
|
socklen_t len = sizeof(bind_addr_storage_);
|
|
|
|
// Bind, then check that we get the right address.
|
|
RETURN_ERROR_IF_SYSCALL_FAIL(bind(socket, addr, addrlen_));
|
|
|
|
RETURN_ERROR_IF_SYSCALL_FAIL(getsockname(socket, addr, &len));
|
|
|
|
if (addrlen_ != len) {
|
|
return PosixError(
|
|
EINVAL,
|
|
absl::StrFormat("getsockname len: %u expected: %u", len, addrlen_));
|
|
}
|
|
return PosixError(0);
|
|
}
|
|
|
|
socklen_t UdpSocketTest::GetAddrLength() {
|
|
struct sockaddr_storage addr;
|
|
if (GetParam() == AF_INET) {
|
|
auto sin = reinterpret_cast<struct sockaddr_in*>(&addr);
|
|
return sizeof(*sin);
|
|
}
|
|
|
|
auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr);
|
|
return sizeof(*sin6);
|
|
}
|
|
|
|
sockaddr_storage UdpSocketTest::InetAnyAddr() {
|
|
struct sockaddr_storage addr;
|
|
memset(&addr, 0, sizeof(addr));
|
|
AsSockAddr(&addr)->sa_family = GetParam();
|
|
|
|
if (GetParam() == AF_INET) {
|
|
auto sin = reinterpret_cast<struct sockaddr_in*>(&addr);
|
|
sin->sin_addr.s_addr = htonl(INADDR_ANY);
|
|
sin->sin_port = htons(0);
|
|
return addr;
|
|
}
|
|
|
|
auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&addr);
|
|
sin6->sin6_addr = IN6ADDR_ANY_INIT;
|
|
sin6->sin6_port = htons(0);
|
|
return addr;
|
|
}
|
|
|
|
sockaddr_storage UdpSocketTest::InetLoopbackAddr() {
|
|
return gvisor::testing::InetLoopbackAddr(GetParam());
|
|
}
|
|
|
|
void UdpSocketTest::Disconnect(int sockfd) {
|
|
sockaddr_storage addr_storage = InetAnyAddr();
|
|
sockaddr* addr = AsSockAddr(&addr_storage);
|
|
socklen_t addrlen = sizeof(addr_storage);
|
|
|
|
addr->sa_family = AF_UNSPEC;
|
|
ASSERT_THAT(connect(sockfd, addr, addrlen), SyscallSucceeds());
|
|
|
|
// Check that after disconnect the socket is bound to the ANY address.
|
|
EXPECT_THAT(getsockname(sockfd, addr, &addrlen), SyscallSucceeds());
|
|
if (GetParam() == AF_INET) {
|
|
auto addr_out = reinterpret_cast<struct sockaddr_in*>(addr);
|
|
EXPECT_EQ(addrlen, sizeof(*addr_out));
|
|
EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_ANY));
|
|
} else {
|
|
auto addr_out = reinterpret_cast<struct sockaddr_in6*>(addr);
|
|
EXPECT_EQ(addrlen, sizeof(*addr_out));
|
|
struct in6_addr loopback = IN6ADDR_ANY_INIT;
|
|
|
|
EXPECT_EQ(memcmp(&addr_out->sin6_addr, &loopback, sizeof(in6_addr)), 0);
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, Creation) {
|
|
FileDescriptor sock =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_DGRAM, IPPROTO_UDP));
|
|
EXPECT_THAT(close(sock.release()), SyscallSucceeds());
|
|
|
|
sock = ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_DGRAM, 0));
|
|
EXPECT_THAT(close(sock.release()), SyscallSucceeds());
|
|
|
|
ASSERT_THAT(socket(GetParam(), SOCK_STREAM, IPPROTO_UDP), SyscallFails());
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, Getsockname) {
|
|
// Check that we're not bound.
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getsockname(bind_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
struct sockaddr_storage any = InetAnyAddr();
|
|
EXPECT_EQ(memcmp(&addr, AsSockAddr(&any), addrlen_), 0);
|
|
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
EXPECT_THAT(getsockname(bind_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_EQ(memcmp(&addr, bind_addr_, addrlen_), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, Getpeername) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Check that we're not connected.
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getpeername(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallFailsWithErrno(ENOTCONN));
|
|
|
|
// Connect, then check that we get the right address.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
addrlen = sizeof(addr);
|
|
EXPECT_THAT(getpeername(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_EQ(memcmp(&addr, bind_addr_, addrlen_), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SendNotConnected) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Do send & write, they must fail.
|
|
char buf[512] = {};
|
|
EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
|
|
SyscallFailsWithErrno(EDESTADDRREQ));
|
|
|
|
EXPECT_THAT(write(sock_.get(), buf, sizeof(buf)),
|
|
SyscallFailsWithErrno(EDESTADDRREQ));
|
|
|
|
// Use sendto.
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Check that we're bound now.
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getsockname(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_NE(*Port(&addr), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ConnectBinds) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Connect the socket.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
// Check that we're bound now.
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getsockname(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_NE(*Port(&addr), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReceiveNotBound) {
|
|
char buf[512] = {};
|
|
EXPECT_THAT(recv(sock_.get(), buf, sizeof(buf), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, Bind) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Try to bind again.
|
|
EXPECT_THAT(bind(bind_.get(), bind_addr_, addrlen_),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
|
|
// Check that we're still bound to the original address.
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getsockname(bind_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_EQ(memcmp(&addr, bind_addr_, addrlen_), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, BindInUse) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Try to bind again.
|
|
EXPECT_THAT(bind(sock_.get(), bind_addr_, addrlen_),
|
|
SyscallFailsWithErrno(EADDRINUSE));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ConnectWriteToInvalidPort) {
|
|
// Discover a free unused port by creating a new UDP socket, binding it
|
|
// recording the just bound port and closing it. This is not guaranteed as it
|
|
// can still race with other port UDP sockets trying to bind a port at the
|
|
// same time.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
socklen_t addrlen = sizeof(addr_storage);
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
FileDescriptor s =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_DGRAM, IPPROTO_UDP));
|
|
ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds());
|
|
ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds());
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_NE(*Port(&addr_storage), 0);
|
|
// Connect to the same address. This won't send data yet but will ensure that
|
|
// the local port chosen for the connecting socket is different from the
|
|
// remote port, since it is still in use.
|
|
ASSERT_THAT(connect(sock_.get(), addr, addrlen_), SyscallSucceeds());
|
|
|
|
// Now free the destination port and then send a packet to it. This should
|
|
// generate an ECONNREFUSED error.
|
|
ASSERT_THAT(close(s.release()), SyscallSucceeds());
|
|
char buf[512] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
// Send from sock_ to an unbound port.
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Poll to make sure we get the ICMP error back.
|
|
struct pollfd pfd = {sock_.get(), POLLERR, 0};
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, IcmpTimeoutMillis()),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
// Now verify that we got an ICMP error back of ECONNREFUSED.
|
|
int err;
|
|
socklen_t optlen = sizeof(err);
|
|
ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(err, ECONNREFUSED);
|
|
ASSERT_EQ(optlen, sizeof(err));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ConnectSimultaneousWriteToInvalidPort) {
|
|
// Discover a free unused port by creating a new UDP socket, binding it
|
|
// recording the just bound port and closing it. This is not guaranteed as it
|
|
// can still race with other port UDP sockets trying to bind a port at the
|
|
// same time.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
socklen_t addrlen = sizeof(addr_storage);
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
FileDescriptor s =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_DGRAM, IPPROTO_UDP));
|
|
ASSERT_THAT(bind(s.get(), addr, addrlen), SyscallSucceeds());
|
|
ASSERT_THAT(getsockname(s.get(), addr, &addrlen), SyscallSucceeds());
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_NE(*Port(&addr_storage), 0);
|
|
ASSERT_THAT(close(s.release()), SyscallSucceeds());
|
|
|
|
// Now connect to the port that we just released.
|
|
//
|
|
// Duplicate the socket file descriptor to prevent a TSan data race.
|
|
// TSan flags concurrent socket operations on the identical host FD
|
|
// (from the concurrent sendto() in the main thread) as a data race,
|
|
// so use a separate FD pointing to the same underlying socket.
|
|
int dup_fd = dup(sock_.get());
|
|
ASSERT_GE(dup_fd, 0);
|
|
ScopedThread t([&, dup_fd] {
|
|
ASSERT_THAT(connect(dup_fd, addr, addrlen_), SyscallSucceeds());
|
|
close(dup_fd);
|
|
});
|
|
|
|
char buf[512] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
// Send from sock_ to an unbound port.
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
t.Join();
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReceiveAfterConnect) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
// Send from sock_ to bind_
|
|
char buf[512] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Receive the data.
|
|
char received[sizeof(buf)] = {};
|
|
EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0),
|
|
SyscallSucceedsWithValue(sizeof(received)));
|
|
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReceiveAfterDisconnect) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
// Connect sock_ to bound address.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getsockname(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
|
|
// Send from sock to bind_.
|
|
char buf[512] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
ASSERT_THAT(
|
|
sendto(bind_.get(), buf, sizeof(buf), 0, AsSockAddr(&addr), addrlen),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Receive the data.
|
|
char received[sizeof(buf)] = {};
|
|
EXPECT_THAT(recv(sock_.get(), received, sizeof(received), 0),
|
|
SyscallSucceedsWithValue(sizeof(received)));
|
|
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
|
|
|
|
// Disconnect sock_.
|
|
struct sockaddr unspec = {};
|
|
unspec.sa_family = AF_UNSPEC;
|
|
ASSERT_THAT(connect(sock_.get(), &unspec, sizeof(unspec.sa_family)),
|
|
SyscallSucceeds());
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, Connect) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
// Check that we're connected to the right peer.
|
|
struct sockaddr_storage peer;
|
|
socklen_t peerlen = sizeof(peer);
|
|
EXPECT_THAT(getpeername(sock_.get(), AsSockAddr(&peer), &peerlen),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(peerlen, addrlen_);
|
|
EXPECT_EQ(memcmp(&peer, bind_addr_, addrlen_), 0);
|
|
|
|
// Try to bind after connect.
|
|
struct sockaddr_storage any = InetAnyAddr();
|
|
EXPECT_THAT(bind(sock_.get(), AsSockAddr(&any), addrlen_),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
|
|
struct sockaddr_storage bind2_storage = InetLoopbackAddr();
|
|
struct sockaddr* bind2_addr = AsSockAddr(&bind2_storage);
|
|
FileDescriptor bind2 =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(GetParam(), SOCK_DGRAM, IPPROTO_UDP));
|
|
ASSERT_NO_ERRNO(BindSocket(bind2.get(), bind2_addr));
|
|
|
|
// Try to connect again.
|
|
EXPECT_THAT(connect(sock_.get(), bind2_addr, addrlen_), SyscallSucceeds());
|
|
|
|
// Check that peer name changed.
|
|
peerlen = sizeof(peer);
|
|
EXPECT_THAT(getpeername(sock_.get(), AsSockAddr(&peer), &peerlen),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(peerlen, addrlen_);
|
|
EXPECT_EQ(memcmp(&peer, bind2_addr, addrlen_), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ConnectAnyZero) {
|
|
struct sockaddr_storage any = InetAnyAddr();
|
|
EXPECT_THAT(connect(sock_.get(), AsSockAddr(&any), addrlen_),
|
|
SyscallSucceeds());
|
|
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getpeername(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallFailsWithErrno(ENOTCONN));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ConnectAnyWithPort) {
|
|
ASSERT_NO_ERRNO(BindAny());
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getpeername(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, DisconnectAfterConnectAny) {
|
|
struct sockaddr_storage any = InetAnyAddr();
|
|
EXPECT_THAT(connect(sock_.get(), AsSockAddr(&any), addrlen_),
|
|
SyscallSucceeds());
|
|
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getpeername(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallFailsWithErrno(ENOTCONN));
|
|
|
|
Disconnect(sock_.get());
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, DisconnectAfterConnectAnyWithPort) {
|
|
ASSERT_NO_ERRNO(BindAny());
|
|
EXPECT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
ASSERT_THAT(getpeername(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_EQ(*Port(&bind_addr_storage_), *Port(&addr));
|
|
|
|
Disconnect(sock_.get());
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, DisconnectAfterBind) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Bind to the next available port near bind_.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
bool bound = false;
|
|
uint16_t base_port = ntohs(*Port(&bind_addr_storage_));
|
|
for (int i = 1; i <= 10; ++i) {
|
|
SetPort(&addr_storage, base_port - i);
|
|
if (BindSocket(sock_.get(), addr).ok()) {
|
|
bound = true;
|
|
break;
|
|
}
|
|
}
|
|
ASSERT_TRUE(bound);
|
|
|
|
// Connect the socket.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
struct sockaddr_storage unspec = {};
|
|
unspec.ss_family = AF_UNSPEC;
|
|
EXPECT_THAT(
|
|
connect(sock_.get(), AsSockAddr(&unspec), sizeof(unspec.ss_family)),
|
|
SyscallSucceeds());
|
|
|
|
// Check that we're still bound.
|
|
socklen_t addrlen = sizeof(unspec);
|
|
EXPECT_THAT(getsockname(sock_.get(), AsSockAddr(&unspec), &addrlen),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_EQ(memcmp(addr, &unspec, addrlen_), 0);
|
|
|
|
addrlen = sizeof(addr);
|
|
EXPECT_THAT(getpeername(sock_.get(), addr, &addrlen),
|
|
SyscallFailsWithErrno(ENOTCONN));
|
|
}
|
|
|
|
void ConnectThenDisconnect(const FileDescriptor& sock,
|
|
const sockaddr* bind_addr,
|
|
const socklen_t expected_addrlen) {
|
|
// Connect the bound socket.
|
|
ASSERT_THAT(connect(sock.get(), bind_addr, expected_addrlen),
|
|
SyscallSucceeds());
|
|
|
|
// Disconnect.
|
|
{
|
|
sockaddr_storage unspec = {.ss_family = AF_UNSPEC};
|
|
ASSERT_THAT(connect(sock.get(), AsSockAddr(&unspec), sizeof(unspec)),
|
|
SyscallSucceeds());
|
|
}
|
|
{
|
|
// Check that we're not in a bound state.
|
|
sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
ASSERT_THAT(getsockname(sock.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(addrlen, expected_addrlen);
|
|
// Everything should be the zero value except the address family.
|
|
sockaddr_storage expected = {
|
|
.ss_family = bind_addr->sa_family,
|
|
};
|
|
EXPECT_EQ(memcmp(&expected, &addr, expected_addrlen), 0);
|
|
}
|
|
|
|
{
|
|
// We are not connected so we have no peer.
|
|
sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getpeername(sock.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallFailsWithErrno(ENOTCONN));
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, DisconnectAfterBindToUnspecAndConnect) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
sockaddr_storage unspec = {.ss_family = AF_UNSPEC};
|
|
int bind_res = bind(sock_.get(), AsSockAddr(&unspec), sizeof(unspec));
|
|
if ((!IsRunningOnGvisor() || IsRunningWithHostinet()) &&
|
|
GetParam() == AF_INET) {
|
|
// Linux allows this for undocumented compatibility reasons:
|
|
// https://github.com/torvalds/linux/commit/29c486df6a208432b370bd4be99ae1369ede28d8.
|
|
//
|
|
// TODO(https://gvisor.dev/issue/6575): Match Linux's behaviour.
|
|
ASSERT_THAT(bind_res, SyscallSucceeds());
|
|
} else {
|
|
ASSERT_THAT(bind_res, SyscallFailsWithErrno(EAFNOSUPPORT));
|
|
}
|
|
|
|
ASSERT_NO_FATAL_FAILURE(ConnectThenDisconnect(sock_, bind_addr_, addrlen_));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, DisconnectAfterConnectWithoutBind) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
ASSERT_NO_FATAL_FAILURE(ConnectThenDisconnect(sock_, bind_addr_, addrlen_));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, BindToAnyConnnectToLocalhost) {
|
|
ASSERT_NO_ERRNO(BindAny());
|
|
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
SetPort(&addr_storage, *Port(&bind_addr_storage_) - 1);
|
|
socklen_t addrlen = sizeof(addr);
|
|
|
|
// Connect the socket.
|
|
ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
|
|
|
|
EXPECT_THAT(getsockname(bind_.get(), addr, &addrlen), SyscallSucceeds());
|
|
|
|
// If the socket is bound to ANY and connected to a loopback address,
|
|
// getsockname() has to return the loopback address.
|
|
if (GetParam() == AF_INET) {
|
|
auto addr_out = reinterpret_cast<struct sockaddr_in*>(addr);
|
|
EXPECT_EQ(addrlen, sizeof(*addr_out));
|
|
EXPECT_EQ(addr_out->sin_addr.s_addr, htonl(INADDR_LOOPBACK));
|
|
} else {
|
|
auto addr_out = reinterpret_cast<struct sockaddr_in6*>(addr);
|
|
struct in6_addr loopback = IN6ADDR_LOOPBACK_INIT;
|
|
EXPECT_EQ(addrlen, sizeof(*addr_out));
|
|
EXPECT_EQ(memcmp(&addr_out->sin6_addr, &loopback, sizeof(in6_addr)), 0);
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, DisconnectAfterBindToAny) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
struct sockaddr_storage any_storage = InetAnyAddr();
|
|
struct sockaddr* any = AsSockAddr(&any_storage);
|
|
SetPort(&any_storage, *Port(&bind_addr_storage_) - 1);
|
|
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), any));
|
|
|
|
// Connect the socket.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
Disconnect(sock_.get());
|
|
|
|
// Check that we're still bound.
|
|
struct sockaddr_storage addr;
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getsockname(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_EQ(memcmp(&addr, any, addrlen), 0);
|
|
|
|
addrlen = sizeof(addr);
|
|
EXPECT_THAT(getpeername(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallFailsWithErrno(ENOTCONN));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, Disconnect) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
struct sockaddr_storage any_storage = InetAnyAddr();
|
|
struct sockaddr* any = AsSockAddr(&any_storage);
|
|
SetPort(&any_storage, *Port(&bind_addr_storage_) - 1);
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), any));
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
// Try to connect again.
|
|
EXPECT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
// Check that we're connected to the right peer.
|
|
struct sockaddr_storage peer;
|
|
socklen_t peerlen = sizeof(peer);
|
|
EXPECT_THAT(getpeername(sock_.get(), AsSockAddr(&peer), &peerlen),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(peerlen, addrlen_);
|
|
EXPECT_EQ(memcmp(&peer, bind_addr_, addrlen_), 0);
|
|
|
|
// Try to disconnect.
|
|
struct sockaddr_storage addr = {};
|
|
addr.ss_family = AF_UNSPEC;
|
|
EXPECT_THAT(connect(sock_.get(), AsSockAddr(&addr), sizeof(addr.ss_family)),
|
|
SyscallSucceeds());
|
|
|
|
peerlen = sizeof(peer);
|
|
EXPECT_THAT(getpeername(sock_.get(), AsSockAddr(&peer), &peerlen),
|
|
SyscallFailsWithErrno(ENOTCONN));
|
|
|
|
// Check that we're still bound.
|
|
socklen_t addrlen = sizeof(addr);
|
|
EXPECT_THAT(getsockname(sock_.get(), AsSockAddr(&addr), &addrlen),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(addrlen, addrlen_);
|
|
EXPECT_EQ(*Port(&addr), *Port(&any_storage));
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ConnectBadAddress) {
|
|
struct sockaddr addr = {};
|
|
addr.sa_family = GetParam();
|
|
ASSERT_THAT(connect(sock_.get(), &addr, sizeof(addr.sa_family)),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SendToAddressOtherThanConnected) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
struct sockaddr_storage addr_storage = InetAnyAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
SetPort(&addr_storage, *Port(&bind_addr_storage_) - 1);
|
|
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
// Send to a different destination than we're connected to.
|
|
char buf[512] = {};
|
|
EXPECT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, addr, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ConnectAndSendNoReceiver) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
// Connect to loopback:bind_addr_ which should *hopefully* not be bound by an
|
|
// UDP socket. There is no easy way to ensure that the UDP port is not bound
|
|
// by another conncurrently running test. *This is potentially flaky*.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
// Close the socket after connecting to the bound address to make sure `sock_`
|
|
// doesn't get auto-bound to the same port.
|
|
ASSERT_THAT(close(bind_.release()), SyscallSucceeds());
|
|
|
|
char buf[512] = {};
|
|
EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Poll to make sure we get the ICMP error back before issuing more writes.
|
|
struct pollfd pfd = {sock_.get(), POLLERR, 0};
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, IcmpTimeoutMillis()),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
// Next write should fail with ECONNREFUSED due to the ICMP error generated in
|
|
// response to the previous write.
|
|
ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
|
|
SyscallFailsWithErrno(ECONNREFUSED));
|
|
|
|
// The next write should succeed again since the last write call would have
|
|
// retrieved and cleared the socket error.
|
|
ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0), SyscallSucceeds());
|
|
|
|
// Poll to make sure we get the ICMP error back before issuing more writes.
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, IcmpTimeoutMillis()),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
// Next write should fail with ECONNREFUSED due to the ICMP error generated in
|
|
// response to the previous write.
|
|
ASSERT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
|
|
SyscallFailsWithErrno(ECONNREFUSED));
|
|
}
|
|
|
|
#ifdef __linux__
|
|
TEST_P(UdpSocketTest, RecvErrorConnRefusedOtherAFSockOpt) {
|
|
int got;
|
|
socklen_t got_len = sizeof(got);
|
|
if (GetParam() == AF_INET) {
|
|
EXPECT_THAT(setsockopt(sock_.get(), SOL_IPV6, IPV6_RECVERR, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallFailsWithErrno(ENOPROTOOPT));
|
|
EXPECT_THAT(getsockopt(sock_.get(), SOL_IPV6, IPV6_RECVERR, &got, &got_len),
|
|
SyscallFailsWithErrno(ENOTSUP));
|
|
ASSERT_THAT(got_len, sizeof(got));
|
|
return;
|
|
}
|
|
ASSERT_THAT(setsockopt(sock_.get(), SOL_IP, IP_RECVERR, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
{
|
|
EXPECT_THAT(getsockopt(sock_.get(), SOL_IP, IP_RECVERR, &got, &got_len),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(got_len, sizeof(got));
|
|
EXPECT_THAT(got, kSockOptOn);
|
|
}
|
|
|
|
// We will simulate an ICMP error and verify that we don't receive that error
|
|
// via recvmsg(MSG_ERRQUEUE) since we set another address family's RECVERR
|
|
// flag.
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
// Close the bind socket to release the port so that we get an ICMP error
|
|
// when sending packets to it.
|
|
ASSERT_THAT(close(bind_.release()), SyscallSucceeds());
|
|
|
|
// Send to an unbound port which should trigger a port unreachable error.
|
|
char buf[1] = {};
|
|
EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Should not have the error since we did not set the right socket option.
|
|
msghdr msg = {};
|
|
EXPECT_THAT(recvmsg(sock_.get(), &msg, MSG_ERRQUEUE),
|
|
SyscallFailsWithErrno(EAGAIN));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, RecvErrorConnRefused) {
|
|
// We will simulate an ICMP error and verify that we do receive that error via
|
|
// recvmsg(MSG_ERRQUEUE).
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
// Close the socket to release the port so that we get an ICMP error.
|
|
ASSERT_THAT(close(bind_.release()), SyscallSucceeds());
|
|
|
|
// Set IP_RECVERR socket option to enable error queueing.
|
|
int v = kSockOptOn;
|
|
socklen_t optlen = sizeof(v);
|
|
int opt_level = SOL_IP;
|
|
int opt_type = IP_RECVERR;
|
|
if (GetParam() == AF_INET6) {
|
|
opt_level = SOL_IPV6;
|
|
opt_type = IPV6_RECVERR;
|
|
}
|
|
ASSERT_THAT(setsockopt(sock_.get(), opt_level, opt_type, &v, optlen),
|
|
SyscallSucceeds());
|
|
{
|
|
int got;
|
|
socklen_t got_len = sizeof(got);
|
|
EXPECT_THAT(getsockopt(sock_.get(), opt_level, opt_type, &got, &got_len),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(got_len, sizeof(got));
|
|
EXPECT_THAT(got, kSockOptOn);
|
|
}
|
|
|
|
// Connect to loopback:bind_addr_ which should *hopefully* not be bound by an
|
|
// UDP socket. There is no easy way to ensure that the UDP port is not bound
|
|
// by another conncurrently running test. *This is potentially flaky*.
|
|
const int kBufLen = 300;
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
char buf[kBufLen] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
// Send from sock_ to an unbound port. This should cause ECONNREFUSED.
|
|
EXPECT_THAT(send(sock_.get(), buf, sizeof(buf), 0),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Dequeue error using recvmsg(MSG_ERRQUEUE).
|
|
char got[kBufLen] = {};
|
|
struct iovec iov;
|
|
iov.iov_base = reinterpret_cast<void*>(got);
|
|
iov.iov_len = kBufLen;
|
|
|
|
size_t control_buf_len = CMSG_SPACE(sizeof(sock_extended_err) + addrlen_);
|
|
std::vector<char> control_buf(control_buf_len);
|
|
struct sockaddr_storage remote;
|
|
memset(&remote, 0, sizeof(remote));
|
|
struct msghdr msg = {};
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_flags = 0;
|
|
msg.msg_control = control_buf.data();
|
|
msg.msg_controllen = control_buf_len;
|
|
msg.msg_name = reinterpret_cast<void*>(&remote);
|
|
msg.msg_namelen = addrlen_;
|
|
struct pollfd pfd = {.fd = sock_.get(), .events = POLLIN};
|
|
int ready = poll(&pfd, 1, 1000);
|
|
ASSERT_TRUE(ready > 0 && (pfd.revents & POLLERR));
|
|
ASSERT_THAT(recvmsg(sock_.get(), &msg, MSG_ERRQUEUE),
|
|
SyscallSucceedsWithValue(kBufLen));
|
|
|
|
// Check the contents of msg.
|
|
EXPECT_EQ(memcmp(got, buf, sizeof(buf)), 0); // iovec check
|
|
EXPECT_NE(msg.msg_flags & MSG_ERRQUEUE, 0);
|
|
EXPECT_EQ(memcmp(&remote, bind_addr_, addrlen_), 0);
|
|
|
|
// Check the contents of the control message.
|
|
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
|
ASSERT_NE(cmsg, nullptr);
|
|
EXPECT_EQ(CMSG_NXTHDR(&msg, cmsg), nullptr);
|
|
EXPECT_EQ(cmsg->cmsg_level, opt_level);
|
|
EXPECT_EQ(cmsg->cmsg_type, opt_type);
|
|
|
|
// Check the contents of socket error.
|
|
struct sock_extended_err* sock_err =
|
|
(struct sock_extended_err*)CMSG_DATA(cmsg);
|
|
EXPECT_EQ(sock_err->ee_errno, ECONNREFUSED);
|
|
if (GetParam() == AF_INET) {
|
|
EXPECT_EQ(sock_err->ee_origin, SO_EE_ORIGIN_ICMP);
|
|
EXPECT_EQ(sock_err->ee_type, ICMP_DEST_UNREACH);
|
|
EXPECT_EQ(sock_err->ee_code, ICMP_PORT_UNREACH);
|
|
} else {
|
|
EXPECT_EQ(sock_err->ee_origin, SO_EE_ORIGIN_ICMP6);
|
|
EXPECT_EQ(sock_err->ee_type, ICMP6_DST_UNREACH);
|
|
EXPECT_EQ(sock_err->ee_code, ICMP6_DST_UNREACH_NOPORT);
|
|
}
|
|
|
|
// Now verify that the socket error was cleared by recvmsg(MSG_ERRQUEUE).
|
|
int err;
|
|
optlen = sizeof(err);
|
|
ASSERT_THAT(getsockopt(sock_.get(), SOL_SOCKET, SO_ERROR, &err, &optlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(err, 0);
|
|
ASSERT_EQ(optlen, sizeof(err));
|
|
}
|
|
#endif // __linux__
|
|
|
|
TEST_P(UdpSocketTest, ZerolengthWriteAllowed) {
|
|
// TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes.
|
|
SKIP_IF(IsRunningWithHostinet());
|
|
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Bind `sock_` to loopback.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), addr));
|
|
|
|
// Connect `bind_` to `sock_`.
|
|
ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
|
|
|
|
char buf[3] = {};
|
|
// Send zero length packet from bind_ to sock_.
|
|
ASSERT_THAT(write(bind_.get(), buf, 0), SyscallSucceedsWithValue(0));
|
|
|
|
struct pollfd pfd = {sock_.get(), POLLIN, 0};
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout*/ 1000),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
// Receive the packet.
|
|
char received[3] = {};
|
|
EXPECT_THAT(read(sock_.get(), received, sizeof(received)),
|
|
SyscallSucceedsWithValue(0));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ZerolengthWriteAllowedNonBlockRead) {
|
|
// TODO(gvisor.dev/issue/1202): Hostinet does not support zero length writes.
|
|
SKIP_IF(IsRunningWithHostinet());
|
|
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Bind `sock_` to loopback.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), addr));
|
|
|
|
// Connect `bind_` to `sock_`.
|
|
ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
|
|
|
|
// Set sock to non-blocking.
|
|
int opts = 0;
|
|
ASSERT_THAT(opts = fcntl(sock_.get(), F_GETFL), SyscallSucceeds());
|
|
ASSERT_THAT(fcntl(sock_.get(), F_SETFL, opts | O_NONBLOCK),
|
|
SyscallSucceeds());
|
|
|
|
char buf[3] = {};
|
|
// Send zero length packet from bind_ to sock_.
|
|
ASSERT_THAT(write(bind_.get(), buf, 0), SyscallSucceedsWithValue(0));
|
|
|
|
struct pollfd pfd = {sock_.get(), POLLIN, 0};
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
// Receive the packet.
|
|
char received[3] = {};
|
|
EXPECT_THAT(read(sock_.get(), received, sizeof(received)),
|
|
SyscallSucceedsWithValue(0));
|
|
EXPECT_THAT(read(sock_.get(), received, sizeof(received)),
|
|
SyscallFailsWithErrno(EAGAIN));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SendAndReceiveNotConnected) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Send some data to bind_.
|
|
char buf[512] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Receive the data.
|
|
char received[sizeof(buf)] = {};
|
|
EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0),
|
|
SyscallSucceedsWithValue(sizeof(received)));
|
|
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SendAndReceiveConnected) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Bind `sock_` to loopback.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), addr));
|
|
|
|
// Connect `bind_` to `sock_`.
|
|
ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
|
|
|
|
// Send some data from sock to bind_.
|
|
char buf[512] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Receive the data.
|
|
char received[sizeof(buf)] = {};
|
|
EXPECT_THAT(recv(bind_.get(), received, sizeof(received), 0),
|
|
SyscallSucceedsWithValue(sizeof(received)));
|
|
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReceiveFromNotConnected) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Bind `sock_` to loopback.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), addr));
|
|
|
|
// Connect `bind_` to itself.
|
|
ASSERT_THAT(connect(bind_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
// Send some data from sock to bind_.
|
|
char buf[512] = {};
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Check that the data isn't received because it was sent from a different
|
|
// address than we're connected.
|
|
EXPECT_THAT(recv(bind_.get(), buf, sizeof(buf), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReceiveBeforeConnect) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Bind `sock_` to loopback.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), addr));
|
|
|
|
// Send some data from sock to bind_.
|
|
char buf[512] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Wait for the data to arrive.
|
|
ASSERT_NO_FATAL_FAILURE(BlockUntilPollin(bind_.get()));
|
|
|
|
// Connect `bind_` to itself.
|
|
ASSERT_THAT(connect(bind_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
// Receive the data. It works because it was sent before the connect.
|
|
char received[sizeof(buf)] = {};
|
|
EXPECT_THAT(
|
|
RecvTimeout(bind_.get(), received, sizeof(received), 1 /*timeout*/),
|
|
IsPosixErrorOkAndHolds(sizeof(received)));
|
|
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
|
|
|
|
// Send again. This time it should not be received.
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
EXPECT_THAT(recv(bind_.get(), buf, sizeof(buf), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReceiveFrom) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Bind `sock_` to loopback.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), addr));
|
|
|
|
// Connect `bind_` to `sock_`.
|
|
ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
|
|
|
|
// Send some data from sock to bind_.
|
|
char buf[512] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// Receive the data and sender address.
|
|
char received[sizeof(buf)] = {};
|
|
struct sockaddr_storage addr2;
|
|
socklen_t addr2len = sizeof(addr2);
|
|
EXPECT_THAT(recvfrom(bind_.get(), received, sizeof(received), 0,
|
|
AsSockAddr(&addr2), &addr2len),
|
|
SyscallSucceedsWithValue(sizeof(received)));
|
|
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
|
|
EXPECT_EQ(addr2len, addrlen_);
|
|
EXPECT_EQ(memcmp(addr, &addr2, addrlen_), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, Listen) {
|
|
ASSERT_THAT(listen(sock_.get(), SOMAXCONN),
|
|
SyscallFailsWithErrno(EOPNOTSUPP));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, Accept) {
|
|
ASSERT_THAT(accept(sock_.get(), nullptr, nullptr),
|
|
SyscallFailsWithErrno(EOPNOTSUPP));
|
|
}
|
|
|
|
// This test validates that a read shutdown with pending data allows the read
|
|
// to proceed with the data before returning EAGAIN.
|
|
TEST_P(UdpSocketTest, ReadShutdownNonblockPendingData) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Bind `sock_` to loopback.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), addr));
|
|
|
|
// Connect `bind_` to `sock_`.
|
|
ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
|
|
|
|
// Connect `sock_` to `bind_addr_`.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
// Verify that we get EWOULDBLOCK when there is nothing to read.
|
|
char received[512] = {};
|
|
EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
|
|
const char* buf = "abc";
|
|
EXPECT_THAT(write(sock_.get(), buf, 3), SyscallSucceedsWithValue(3));
|
|
|
|
int opts = 0;
|
|
ASSERT_THAT(opts = fcntl(bind_.get(), F_GETFL), SyscallSucceeds());
|
|
ASSERT_THAT(fcntl(bind_.get(), F_SETFL, opts | O_NONBLOCK),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(opts = fcntl(bind_.get(), F_GETFL), SyscallSucceeds());
|
|
ASSERT_NE(opts & O_NONBLOCK, 0);
|
|
|
|
// Wait for the data to arrive.
|
|
ASSERT_NO_FATAL_FAILURE(BlockUntilPollin(bind_.get()));
|
|
|
|
EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallSucceeds());
|
|
|
|
// We should get the data even though read has been shutdown.
|
|
EXPECT_THAT(RecvTimeout(bind_.get(), received, 2 /*buf_size*/, 1 /*timeout*/),
|
|
IsPosixErrorOkAndHolds(2));
|
|
|
|
// Because we read less than the entire packet length, since it's a packet
|
|
// based socket any subsequent reads should return EWOULDBLOCK.
|
|
EXPECT_THAT(recv(bind_.get(), received, 1, 0),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
}
|
|
|
|
// This test is validating that even after a socket is shutdown if it's
|
|
// reconnected it will reset the shutdown state.
|
|
TEST_P(UdpSocketTest, ReadShutdownSameSocketResetsShutdownState) {
|
|
char received[512] = {};
|
|
EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
|
|
EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
|
|
|
|
EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
|
|
// Connect the socket, then try to shutdown again.
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Connect `bind_` to itself since we know the port number is valid.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
SetPort(&addr_storage, *Port(&bind_addr_storage_));
|
|
ASSERT_THAT(connect(bind_.get(), addr, addrlen_), SyscallSucceeds());
|
|
|
|
EXPECT_THAT(recv(bind_.get(), received, sizeof(received), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReadShutdown) {
|
|
// TODO(gvisor.dev/issue/1202): Calling recv() after shutdown without
|
|
// MSG_DONTWAIT blocks indefinitely.
|
|
SKIP_IF(IsRunningWithHostinet());
|
|
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
char received[512] = {};
|
|
EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
|
|
EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallFailsWithErrno(ENOTCONN));
|
|
|
|
EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
|
|
// Connect the socket, then try to shutdown again.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
|
|
EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallSucceeds());
|
|
|
|
EXPECT_THAT(recv(sock_.get(), received, sizeof(received), 0),
|
|
SyscallSucceedsWithValue(0));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReadShutdownDifferentThread) {
|
|
// TODO(gvisor.dev/issue/1202): Calling recv() after shutdown without
|
|
// MSG_DONTWAIT blocks indefinitely.
|
|
SKIP_IF(IsRunningWithHostinet());
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
char received[512] = {};
|
|
EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
|
|
// Connect the socket, then shutdown from another thread.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
EXPECT_THAT(recv(sock_.get(), received, sizeof(received), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
|
|
ScopedThread t([&] {
|
|
absl::SleepFor(absl::Milliseconds(200));
|
|
EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallSucceeds());
|
|
});
|
|
EXPECT_THAT(RetryEINTR(recv)(sock_.get(), received, sizeof(received), 0),
|
|
SyscallSucceedsWithValue(0));
|
|
t.Join();
|
|
|
|
EXPECT_THAT(RetryEINTR(recv)(sock_.get(), received, sizeof(received), 0),
|
|
SyscallSucceedsWithValue(0));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, WriteShutdown) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
EXPECT_THAT(shutdown(sock_.get(), SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
EXPECT_THAT(shutdown(sock_.get(), SHUT_WR), SyscallSucceeds());
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SynchronousReceive) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Send some data to bind_ from another thread.
|
|
char buf[512] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
// Receive the data prior to actually starting the other thread.
|
|
char received[512] = {};
|
|
EXPECT_THAT(
|
|
RetryEINTR(recv)(bind_.get(), received, sizeof(received), MSG_DONTWAIT),
|
|
SyscallFailsWithErrno(EWOULDBLOCK));
|
|
|
|
// Start the thread.
|
|
ScopedThread t([&] {
|
|
absl::SleepFor(absl::Milliseconds(200));
|
|
ASSERT_THAT(sendto(sock_.get(), buf, sizeof(buf), 0, this->bind_addr_,
|
|
this->addrlen_),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
});
|
|
|
|
EXPECT_THAT(RetryEINTR(recv)(bind_.get(), received, sizeof(received), 0),
|
|
SyscallSucceedsWithValue(512));
|
|
EXPECT_EQ(memcmp(buf, received, sizeof(buf)), 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, BoundaryPreserved_SendRecv) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Send 3 packets from sock to bind_.
|
|
constexpr int psize = 100;
|
|
char buf[3 * psize] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
for (int i = 0; i < 3; ++i) {
|
|
ASSERT_THAT(
|
|
sendto(sock_.get(), buf + i * psize, psize, 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(psize));
|
|
}
|
|
|
|
// Receive the data as 3 separate packets.
|
|
std::vector<std::string> rcvd_pkts;
|
|
for (int i = 0; i < 3; ++i) {
|
|
char recv_buf[psize] = {};
|
|
ASSERT_THAT(recv(bind_.get(), recv_buf, psize, 0),
|
|
SyscallSucceedsWithValue(psize));
|
|
rcvd_pkts.push_back(std::string(recv_buf, psize));
|
|
}
|
|
// Note: the packets might be received in a different order.
|
|
for (int i = 0; i < 3; ++i) {
|
|
std::string sent_packet(buf + i * psize, psize);
|
|
EXPECT_THAT(rcvd_pkts, ::testing::Contains(sent_packet));
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, BoundaryPreserved_WritevReadv) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Direct writes from sock to bind_.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
// Send 2 packets from sock to bind_, where each packet's data consists of
|
|
// 2 discontiguous iovecs.
|
|
constexpr size_t kPieceSize = 100;
|
|
char buf[4 * kPieceSize] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
struct iovec iov[2];
|
|
for (int j = 0; j < 2; j++) {
|
|
iov[j].iov_base = reinterpret_cast<void*>(
|
|
reinterpret_cast<uintptr_t>(buf) + (i + 2 * j) * kPieceSize);
|
|
iov[j].iov_len = kPieceSize;
|
|
}
|
|
ASSERT_THAT(writev(sock_.get(), iov, 2),
|
|
SyscallSucceedsWithValue(2 * kPieceSize));
|
|
}
|
|
|
|
// Receive the data as 2 separate packets.
|
|
char received[6 * kPieceSize] = {};
|
|
memset(received, 0, sizeof(received));
|
|
for (int i = 0; i < 2; i++) {
|
|
struct iovec iov[3];
|
|
for (int j = 0; j < 3; j++) {
|
|
iov[j].iov_base = reinterpret_cast<void*>(
|
|
reinterpret_cast<uintptr_t>(received) + (i + 2 * j) * kPieceSize);
|
|
iov[j].iov_len = kPieceSize;
|
|
}
|
|
ASSERT_THAT(readv(bind_.get(), iov, 3),
|
|
SyscallSucceedsWithValue(2 * kPieceSize));
|
|
}
|
|
|
|
std::string sent_pkt0 = std::string(buf, kPieceSize) +
|
|
std::string(buf + 2 * kPieceSize, kPieceSize);
|
|
std::string sent_pkt1 = std::string(buf + kPieceSize, kPieceSize) +
|
|
std::string(buf + 3 * kPieceSize, kPieceSize);
|
|
std::string rcvd_pkt0 = std::string(received, kPieceSize) +
|
|
std::string(received + 2 * kPieceSize, kPieceSize);
|
|
std::string rcvd_pkt1 = std::string(received + kPieceSize, kPieceSize) +
|
|
std::string(received + 3 * kPieceSize, kPieceSize);
|
|
EXPECT_THAT(std::vector<std::string>({rcvd_pkt0, rcvd_pkt1}),
|
|
::testing::UnorderedElementsAre(sent_pkt0, sent_pkt1));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, BoundaryPreserved_SendMsgRecvMsg) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Send 2 packets from sock to bind_, where each packet's data consists of
|
|
// 2 discontiguous iovecs.
|
|
constexpr size_t kPieceSize = 100;
|
|
char buf[4 * kPieceSize] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
for (int i = 0; i < 2; i++) {
|
|
struct iovec iov[2];
|
|
for (int j = 0; j < 2; j++) {
|
|
iov[j].iov_base = reinterpret_cast<void*>(
|
|
reinterpret_cast<uintptr_t>(buf) + (i + 2 * j) * kPieceSize);
|
|
iov[j].iov_len = kPieceSize;
|
|
}
|
|
struct msghdr msg = {};
|
|
msg.msg_name = bind_addr_;
|
|
msg.msg_namelen = addrlen_;
|
|
msg.msg_iov = iov;
|
|
msg.msg_iovlen = 2;
|
|
ASSERT_THAT(sendmsg(sock_.get(), &msg, 0),
|
|
SyscallSucceedsWithValue(2 * kPieceSize));
|
|
}
|
|
|
|
// Receive the data as 2 separate packets.
|
|
char received[6 * kPieceSize] = {};
|
|
memset(received, 0, sizeof(received));
|
|
for (int i = 0; i < 2; i++) {
|
|
struct iovec iov[3];
|
|
for (int j = 0; j < 3; j++) {
|
|
iov[j].iov_base = reinterpret_cast<void*>(
|
|
reinterpret_cast<uintptr_t>(received) + (i + 2 * j) * kPieceSize);
|
|
iov[j].iov_len = kPieceSize;
|
|
}
|
|
struct msghdr msg = {};
|
|
msg.msg_iov = iov;
|
|
msg.msg_iovlen = 3;
|
|
ASSERT_THAT(recvmsg(bind_.get(), &msg, 0),
|
|
SyscallSucceedsWithValue(2 * kPieceSize));
|
|
}
|
|
|
|
std::string sent_pkt0 = std::string(buf, kPieceSize) +
|
|
std::string(buf + 2 * kPieceSize, kPieceSize);
|
|
std::string sent_pkt1 = std::string(buf + kPieceSize, kPieceSize) +
|
|
std::string(buf + 3 * kPieceSize, kPieceSize);
|
|
std::string rcvd_pkt0 = std::string(received, kPieceSize) +
|
|
std::string(received + 2 * kPieceSize, kPieceSize);
|
|
std::string rcvd_pkt1 = std::string(received + kPieceSize, kPieceSize) +
|
|
std::string(received + 3 * kPieceSize, kPieceSize);
|
|
EXPECT_THAT(std::vector<std::string>({rcvd_pkt0, rcvd_pkt1}),
|
|
::testing::UnorderedElementsAre(sent_pkt0, sent_pkt1));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, FIONREADShutdown) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
int n = -1;
|
|
EXPECT_THAT(ioctl(sock_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
|
|
// A UDP socket must be connected before it can be shutdown.
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
n = -1;
|
|
EXPECT_THAT(ioctl(sock_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
|
|
EXPECT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallSucceeds());
|
|
|
|
n = -1;
|
|
EXPECT_THAT(ioctl(sock_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, FIONREADWriteShutdown) {
|
|
int n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// A UDP socket must be connected before it can be shutdown.
|
|
ASSERT_THAT(connect(bind_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
|
|
const char str[] = "abc";
|
|
ASSERT_THAT(send(bind_.get(), str, sizeof(str), 0),
|
|
SyscallSucceedsWithValue(sizeof(str)));
|
|
|
|
struct pollfd pfd = {bind_.get(), POLLIN, 0};
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, sizeof(str));
|
|
|
|
EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallSucceeds());
|
|
|
|
n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, sizeof(str));
|
|
}
|
|
|
|
// NOTE: Do not use `FIONREAD` as test name because it will be replaced by the
|
|
// corresponding macro and become `0x541B`.
|
|
TEST_P(UdpSocketTest, Fionread) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Check that the bound socket with an empty buffer reports an empty first
|
|
// packet.
|
|
int n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
|
|
// Send 3 packets from sock to bind_.
|
|
constexpr int psize = 100;
|
|
char buf[3 * psize] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
struct pollfd pfd = {bind_.get(), POLLIN, 0};
|
|
for (int i = 0; i < 3; ++i) {
|
|
ASSERT_THAT(
|
|
sendto(sock_.get(), buf + i * psize, psize, 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(psize));
|
|
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
// Check that regardless of how many packets are in the queue, the size
|
|
// reported is that of a single packet.
|
|
n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, psize);
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, FIONREADZeroLengthPacket) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Check that the bound socket with an empty buffer reports an empty first
|
|
// packet.
|
|
int n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
|
|
// Send 3 packets from sock to bind_.
|
|
constexpr int psize = 100;
|
|
char buf[3 * psize] = {};
|
|
RandomizeBuffer(buf, sizeof(buf));
|
|
|
|
struct pollfd pfd = {bind_.get(), POLLIN, 0};
|
|
for (int i = 0; i < 3; ++i) {
|
|
ASSERT_THAT(
|
|
sendto(sock_.get(), buf + i * psize, 0, 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(0));
|
|
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
// Check that regardless of how many packets are in the queue, the size
|
|
// reported is that of a single packet.
|
|
n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, FIONREADZeroLengthWriteShutdown) {
|
|
int n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// A UDP socket must be connected before it can be shutdown.
|
|
ASSERT_THAT(connect(bind_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
|
|
const char str[] = "abc";
|
|
ASSERT_THAT(send(bind_.get(), str, 0, 0), SyscallSucceedsWithValue(0));
|
|
|
|
n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
|
|
EXPECT_THAT(shutdown(bind_.get(), SHUT_RD), SyscallSucceeds());
|
|
|
|
n = -1;
|
|
EXPECT_THAT(ioctl(bind_.get(), FIONREAD, &n), SyscallSucceedsWithValue(0));
|
|
EXPECT_EQ(n, 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SoNoCheckOffByDefault) {
|
|
int v = -1;
|
|
socklen_t optlen = sizeof(v);
|
|
ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, &optlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(v, kSockOptOff);
|
|
ASSERT_EQ(optlen, sizeof(v));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SoNoCheck) {
|
|
int v = kSockOptOn;
|
|
socklen_t optlen = sizeof(v);
|
|
ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, optlen),
|
|
SyscallSucceeds());
|
|
v = -1;
|
|
ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, &optlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(v, kSockOptOn);
|
|
ASSERT_EQ(optlen, sizeof(v));
|
|
|
|
v = kSockOptOff;
|
|
ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, optlen),
|
|
SyscallSucceeds());
|
|
v = -1;
|
|
ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_NO_CHECK, &v, &optlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(v, kSockOptOff);
|
|
ASSERT_EQ(optlen, sizeof(v));
|
|
}
|
|
|
|
#ifdef __linux__
|
|
TEST_P(UdpSocketTest, ErrorQueue) {
|
|
char cmsgbuf[CMSG_SPACE(sizeof(sock_extended_err))] = {};
|
|
msghdr msg = {};
|
|
memset(&msg, 0, sizeof(msg));
|
|
iovec iov;
|
|
memset(&iov, 0, sizeof(iov));
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = cmsgbuf;
|
|
msg.msg_controllen = sizeof(cmsgbuf);
|
|
|
|
// recv*(MSG_ERRQUEUE) never blocks, even without MSG_DONTWAIT.
|
|
EXPECT_THAT(RetryEINTR(recvmsg)(bind_.get(), &msg, MSG_ERRQUEUE),
|
|
SyscallFailsWithErrno(EAGAIN));
|
|
}
|
|
#endif // __linux__
|
|
|
|
TEST_P(UdpSocketTest, SoTimestampOffByDefault) {
|
|
int v = -1;
|
|
socklen_t optlen = sizeof(v);
|
|
ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_TIMESTAMP, &v, &optlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(v, kSockOptOff);
|
|
ASSERT_EQ(optlen, sizeof(v));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SoTimestamp) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
int v = 1;
|
|
ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_TIMESTAMP, &v, sizeof(v)),
|
|
SyscallSucceeds());
|
|
|
|
char buf[3] = {};
|
|
// Send zero length packet from sock to bind_.
|
|
ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, sizeof(buf)),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
struct pollfd pfd = {bind_.get(), POLLIN, 0};
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
char cmsgbuf[CMSG_SPACE(sizeof(struct timeval))] = {};
|
|
msghdr msg = {};
|
|
memset(&msg, 0, sizeof(msg));
|
|
iovec iov;
|
|
memset(&iov, 0, sizeof(iov));
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = cmsgbuf;
|
|
msg.msg_controllen = sizeof(cmsgbuf);
|
|
|
|
ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &msg, 0),
|
|
SyscallSucceedsWithValue(0));
|
|
|
|
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
|
ASSERT_NE(cmsg, nullptr);
|
|
ASSERT_EQ(cmsg->cmsg_level, SOL_SOCKET);
|
|
ASSERT_EQ(cmsg->cmsg_type, SO_TIMESTAMP);
|
|
ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(struct timeval)));
|
|
|
|
struct timeval tv = {};
|
|
memcpy(&tv, CMSG_DATA(cmsg), sizeof(struct timeval));
|
|
|
|
ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0);
|
|
|
|
// TODO(gvisor.dev/issue/1202): ioctl(SIOCGSTAMP) is not supported by
|
|
// hostinet.
|
|
if (!IsRunningWithHostinet()) {
|
|
// There should be nothing to get via ioctl.
|
|
ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv),
|
|
SyscallFailsWithErrno(ENOENT));
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, WriteShutdownNotConnected) {
|
|
EXPECT_THAT(shutdown(bind_.get(), SHUT_WR), SyscallFailsWithErrno(ENOTCONN));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, TimestampIoctl) {
|
|
// TODO(gvisor.dev/issue/1202): ioctl() is not supported by hostinet.
|
|
SKIP_IF(IsRunningWithHostinet());
|
|
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
char buf[3] = {};
|
|
// Send packet from sock to bind_.
|
|
ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, sizeof(buf)),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
struct pollfd pfd = {bind_.get(), POLLIN, 0};
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
// There should be no control messages.
|
|
char recv_buf[sizeof(buf)] = {};
|
|
ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(bind_.get(), recv_buf, sizeof(recv_buf)));
|
|
|
|
// A nonzero timeval should be available via ioctl.
|
|
struct timeval tv = {};
|
|
ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv), SyscallSucceeds());
|
|
ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, TimestampIoctlNothingRead) {
|
|
// TODO(gvisor.dev/issue/1202): ioctl() is not supported by hostinet.
|
|
SKIP_IF(IsRunningWithHostinet());
|
|
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
struct timeval tv = {};
|
|
ASSERT_THAT(ioctl(sock_.get(), SIOCGSTAMP, &tv),
|
|
SyscallFailsWithErrno(ENOENT));
|
|
}
|
|
|
|
// Test that the timestamp accessed via SIOCGSTAMP is still accessible after
|
|
// SO_TIMESTAMP is enabled and used to retrieve a timestamp.
|
|
TEST_P(UdpSocketTest, TimestampIoctlPersistence) {
|
|
// TODO(gvisor.dev/issue/1202): ioctl() and SO_TIMESTAMP socket option are not
|
|
// supported by hostinet.
|
|
SKIP_IF(IsRunningWithHostinet());
|
|
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
char buf[3] = {};
|
|
// Send packet from sock to bind_.
|
|
ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, sizeof(buf)),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, 0),
|
|
SyscallSucceedsWithValue(0));
|
|
|
|
struct pollfd pfd = {bind_.get(), POLLIN, 0};
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
// There should be no control messages.
|
|
char recv_buf[sizeof(buf)] = {};
|
|
ASSERT_NO_FATAL_FAILURE(RecvNoCmsg(bind_.get(), recv_buf, sizeof(recv_buf)));
|
|
|
|
// A nonzero timeval should be available via ioctl.
|
|
struct timeval tv = {};
|
|
ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv), SyscallSucceeds());
|
|
ASSERT_TRUE(tv.tv_sec != 0 || tv.tv_usec != 0);
|
|
|
|
// Enable SO_TIMESTAMP and send a message.
|
|
int v = 1;
|
|
EXPECT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_TIMESTAMP, &v, sizeof(v)),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(RetryEINTR(write)(sock_.get(), buf, 0),
|
|
SyscallSucceedsWithValue(0));
|
|
|
|
ASSERT_THAT(RetryEINTR(poll)(&pfd, 1, /*timeout=*/1000),
|
|
SyscallSucceedsWithValue(1));
|
|
|
|
// There should be a message for SO_TIMESTAMP.
|
|
char cmsgbuf[CMSG_SPACE(sizeof(struct timeval))] = {};
|
|
msghdr msg = {};
|
|
iovec iov = {};
|
|
msg.msg_iov = &iov;
|
|
msg.msg_iovlen = 1;
|
|
msg.msg_control = cmsgbuf;
|
|
msg.msg_controllen = sizeof(cmsgbuf);
|
|
ASSERT_THAT(RetryEINTR(recvmsg)(bind_.get(), &msg, 0),
|
|
SyscallSucceedsWithValue(0));
|
|
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
|
ASSERT_NE(cmsg, nullptr);
|
|
|
|
// The ioctl should return the exact same values as before.
|
|
struct timeval tv2 = {};
|
|
ASSERT_THAT(ioctl(bind_.get(), SIOCGSTAMP, &tv2), SyscallSucceeds());
|
|
ASSERT_EQ(tv.tv_sec, tv2.tv_sec);
|
|
ASSERT_EQ(tv.tv_usec, tv2.tv_usec);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, RecvBufLimitsEmptyRcvBuf) {
|
|
// Discover minimum buffer size by setting it to zero.
|
|
constexpr int kRcvBufSz = 0;
|
|
ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz,
|
|
sizeof(kRcvBufSz)),
|
|
SyscallSucceeds());
|
|
|
|
int min = 0;
|
|
socklen_t min_len = sizeof(min);
|
|
ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &min, &min_len),
|
|
SyscallSucceeds());
|
|
|
|
// Bind bind_ to loopback.
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
{
|
|
// Send data of size min and verify that it's received.
|
|
std::vector<char> buf(min);
|
|
RandomizeBuffer(buf.data(), buf.size());
|
|
ASSERT_THAT(
|
|
sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(buf.size()));
|
|
std::vector<char> received(buf.size());
|
|
EXPECT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(),
|
|
1 /*timeout*/),
|
|
IsPosixErrorOkAndHolds(received.size()));
|
|
}
|
|
|
|
{
|
|
// Send data of size min + 1 and verify that its received. Both linux and
|
|
// Netstack accept a dgram that exceeds rcvBuf limits if the receive buffer
|
|
// is currently empty.
|
|
std::vector<char> buf(min + 1);
|
|
RandomizeBuffer(buf.data(), buf.size());
|
|
ASSERT_THAT(
|
|
sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_),
|
|
SyscallSucceedsWithValue(buf.size()));
|
|
|
|
std::vector<char> received(buf.size());
|
|
ASSERT_THAT(RecvTimeout(bind_.get(), received.data(), received.size(),
|
|
1 /*timeout*/),
|
|
IsPosixErrorOkAndHolds(received.size()));
|
|
}
|
|
}
|
|
|
|
// Test that receive buffer limits are enforced.
|
|
TEST_P(UdpSocketTest, RecvBufLimits) {
|
|
// Bind s_ to loopback.
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
int min = 0;
|
|
{
|
|
// Discover minimum buffer size by trying to set it to zero.
|
|
constexpr int kRcvBufSz = 0;
|
|
ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &kRcvBufSz,
|
|
sizeof(kRcvBufSz)),
|
|
SyscallSucceeds());
|
|
|
|
socklen_t min_len = sizeof(min);
|
|
ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &min, &min_len),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
// Now set the limit to min * 2.
|
|
int new_rcv_buf_sz = min * 2;
|
|
ASSERT_THAT(setsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &new_rcv_buf_sz,
|
|
sizeof(new_rcv_buf_sz)),
|
|
SyscallSucceeds());
|
|
int rcv_buf_sz = 0;
|
|
{
|
|
socklen_t rcv_buf_len = sizeof(rcv_buf_sz);
|
|
ASSERT_THAT(getsockopt(bind_.get(), SOL_SOCKET, SO_RCVBUF, &rcv_buf_sz,
|
|
&rcv_buf_len),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
constexpr int max_to_send = 10; // Enough to definitely fill min * 2.
|
|
std::vector<char> buf(min);
|
|
RandomizeBuffer(buf.data(), buf.size());
|
|
|
|
// Send to sock_.
|
|
int sent = 0;
|
|
for (int i = 0; i < max_to_send; ++i) {
|
|
size_t sentBytes =
|
|
sendto(sock_.get(), buf.data(), buf.size(), 0, bind_addr_, addrlen_);
|
|
if (sentBytes == buf.size()) {
|
|
sent++;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Receive from bind_.
|
|
int recvd = 0;
|
|
bool received_eagain = false;
|
|
for (int i = 0; i < sent; ++i) {
|
|
std::vector<char> received(buf.size());
|
|
PosixErrorOr<int> ret = RecvTimeout(bind_.get(), received.data(),
|
|
received.size(), 1 /*timeout*/);
|
|
if (ret.ok()) {
|
|
EXPECT_EQ(ret.ValueOrDie(), buf.size());
|
|
EXPECT_EQ(memcmp(buf.data(), received.data(), buf.size()), 0);
|
|
recvd++;
|
|
} else {
|
|
EXPECT_EQ(ret.error().errno_value(), EAGAIN);
|
|
received_eagain = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
EXPECT_GT(recvd, 1);
|
|
EXPECT_LT(recvd, sent);
|
|
EXPECT_TRUE(received_eagain);
|
|
}
|
|
|
|
#ifdef __linux__
|
|
|
|
// TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER.
|
|
// gVisor currently silently ignores attaching a filter.
|
|
TEST_P(UdpSocketTest, SetSocketDetachFilter) {
|
|
// Program generated using sudo tcpdump -i lo udp and port 1234 -dd
|
|
struct sock_filter code[] = {
|
|
{0x28, 0, 0, 0x0000000c}, {0x15, 0, 6, 0x000086dd},
|
|
{0x30, 0, 0, 0x00000014}, {0x15, 0, 15, 0x00000011},
|
|
{0x28, 0, 0, 0x00000036}, {0x15, 12, 0, 0x000004d2},
|
|
{0x28, 0, 0, 0x00000038}, {0x15, 10, 11, 0x000004d2},
|
|
{0x15, 0, 10, 0x00000800}, {0x30, 0, 0, 0x00000017},
|
|
{0x15, 0, 8, 0x00000011}, {0x28, 0, 0, 0x00000014},
|
|
{0x45, 6, 0, 0x00001fff}, {0xb1, 0, 0, 0x0000000e},
|
|
{0x48, 0, 0, 0x0000000e}, {0x15, 2, 0, 0x000004d2},
|
|
{0x48, 0, 0, 0x00000010}, {0x15, 0, 1, 0x000004d2},
|
|
{0x6, 0, 0, 0x00040000}, {0x6, 0, 0, 0x00000000},
|
|
};
|
|
struct sock_fprog bpf = {
|
|
.len = std::size(code),
|
|
.filter = code,
|
|
};
|
|
ASSERT_THAT(
|
|
setsockopt(sock_.get(), SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf)),
|
|
SyscallSucceeds());
|
|
|
|
constexpr int val = 0;
|
|
ASSERT_THAT(
|
|
setsockopt(sock_.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
#endif // __linux__
|
|
|
|
TEST_P(UdpSocketTest, SetSocketDetachFilterNoInstalledFilter) {
|
|
// TODO(gvisor.dev/2746): Support SO_ATTACH_FILTER/SO_DETACH_FILTER.
|
|
SKIP_IF(IsRunningOnGvisor());
|
|
constexpr int val = 0;
|
|
ASSERT_THAT(
|
|
setsockopt(sock_.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, sizeof(val)),
|
|
SyscallFailsWithErrno(ENOENT));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, GetSocketDetachFilter) {
|
|
int val = 0;
|
|
socklen_t val_len = sizeof(val);
|
|
ASSERT_THAT(
|
|
getsockopt(sock_.get(), SOL_SOCKET, SO_DETACH_FILTER, &val, &val_len),
|
|
SyscallFailsWithErrno(ENOPROTOOPT));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SendToZeroPort) {
|
|
char buf[8] = {};
|
|
struct sockaddr_storage addr = InetLoopbackAddr();
|
|
|
|
// Sending to an invalid port should fail.
|
|
SetPort(&addr, 0);
|
|
EXPECT_THAT(
|
|
sendto(sock_.get(), buf, sizeof(buf), 0, AsSockAddr(&addr), sizeof(addr)),
|
|
SyscallFailsWithErrno(EINVAL));
|
|
|
|
SetPort(&addr, 1234);
|
|
EXPECT_THAT(
|
|
sendto(sock_.get(), buf, sizeof(buf), 0, AsSockAddr(&addr), sizeof(addr)),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ConnectToZeroPortUnbound) {
|
|
struct sockaddr_storage addr = InetLoopbackAddr();
|
|
SetPort(&addr, 0);
|
|
ASSERT_THAT(connect(sock_.get(), AsSockAddr(&addr), addrlen_),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ConnectToZeroPortBound) {
|
|
struct sockaddr_storage addr = InetLoopbackAddr();
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), AsSockAddr(&addr)));
|
|
|
|
SetPort(&addr, 0);
|
|
ASSERT_THAT(connect(sock_.get(), AsSockAddr(&addr), addrlen_),
|
|
SyscallSucceeds());
|
|
socklen_t len = sizeof(sockaddr_storage);
|
|
ASSERT_THAT(getsockname(sock_.get(), AsSockAddr(&addr), &len),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(len, addrlen_);
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ConnectToZeroPortConnected) {
|
|
struct sockaddr_storage addr = InetLoopbackAddr();
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), AsSockAddr(&addr)));
|
|
|
|
// Connect to an address with non-zero port should succeed.
|
|
ASSERT_THAT(connect(sock_.get(), AsSockAddr(&addr), addrlen_),
|
|
SyscallSucceeds());
|
|
sockaddr_storage peername;
|
|
socklen_t peerlen = sizeof(peername);
|
|
ASSERT_THAT(getpeername(sock_.get(), AsSockAddr(&peername), &peerlen),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(peerlen, addrlen_);
|
|
ASSERT_EQ(memcmp(&peername, &addr, addrlen_), 0);
|
|
|
|
// However connect() to an address with port 0 will make the following
|
|
// getpeername() fail.
|
|
SetPort(&addr, 0);
|
|
ASSERT_THAT(connect(sock_.get(), AsSockAddr(&addr), addrlen_),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(getpeername(sock_.get(), AsSockAddr(&peername), &peerlen),
|
|
SyscallFailsWithErrno(ENOTCONN));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SetPMTUD) {
|
|
// IP_PMTUDISC_WANT should be default.
|
|
int got = -1;
|
|
socklen_t length = sizeof(got);
|
|
ASSERT_THAT(getsockopt(bind_.get(), SOL_IP, IP_MTU_DISCOVER, &got, &length),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(got, IP_PMTUDISC_WANT);
|
|
|
|
// Set and verify each valid PMTUD strategy.
|
|
int strategies[] = {IP_PMTUDISC_DONT, IP_PMTUDISC_WANT, IP_PMTUDISC_DO,
|
|
IP_PMTUDISC_PROBE};
|
|
for (int strategy : strategies) {
|
|
ASSERT_THAT(
|
|
setsockopt(bind_.get(), SOL_IP, IP_MTU_DISCOVER, &strategy, length),
|
|
SyscallSucceeds());
|
|
ASSERT_THAT(getsockopt(bind_.get(), SOL_IP, IP_MTU_DISCOVER, &got, &length),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(got, strategy);
|
|
}
|
|
|
|
// Invalid value should fail.
|
|
int invalid = 99;
|
|
EXPECT_THAT(
|
|
setsockopt(bind_.get(), SOL_IP, IP_MTU_DISCOVER, &invalid, length),
|
|
SyscallFails());
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketTest,
|
|
::testing::Values(AF_INET, AF_INET6));
|
|
|
|
class UdpSocketControlMessagesTest
|
|
: public ::testing::TestWithParam<gvisor::testing::AddressFamily> {
|
|
protected:
|
|
void SetUp() override {
|
|
switch (GetParam()) {
|
|
case AddressFamily::kIpv4: {
|
|
server_ =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
|
|
TestAddress addr = V4Loopback().WithPort(port_);
|
|
ASSERT_THAT(
|
|
bind(server_.get(), reinterpret_cast<const sockaddr*>(&addr.addr),
|
|
addr.addr_len),
|
|
SyscallSucceeds());
|
|
|
|
client_ =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
|
|
ASSERT_THAT(connect(client_.get(),
|
|
reinterpret_cast<const sockaddr*>(&addr.addr),
|
|
addr.addr_len),
|
|
SyscallSucceeds());
|
|
break;
|
|
}
|
|
|
|
case AddressFamily::kIpv6: {
|
|
server_ = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP));
|
|
TestAddress addr = V6Loopback().WithPort(port_);
|
|
ASSERT_THAT(
|
|
bind(server_.get(), reinterpret_cast<const sockaddr*>(&addr.addr),
|
|
addr.addr_len),
|
|
SyscallSucceeds());
|
|
|
|
client_ = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP));
|
|
ASSERT_THAT(connect(client_.get(),
|
|
reinterpret_cast<const sockaddr*>(&addr.addr),
|
|
addr.addr_len),
|
|
SyscallSucceeds());
|
|
break;
|
|
}
|
|
|
|
case AddressFamily::kDualStack: {
|
|
server_ = ASSERT_NO_ERRNO_AND_VALUE(
|
|
Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP));
|
|
|
|
TestAddress bind_addr = V4MappedLoopback().WithPort(port_);
|
|
ASSERT_THAT(bind(server_.get(),
|
|
reinterpret_cast<const sockaddr*>(&bind_addr.addr),
|
|
bind_addr.addr_len),
|
|
SyscallSucceeds());
|
|
|
|
client_ =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP));
|
|
TestAddress connect_addr = V4Loopback().WithPort(port_);
|
|
ASSERT_THAT(
|
|
connect(client_.get(),
|
|
reinterpret_cast<const sockaddr*>(&connect_addr.addr),
|
|
connect_addr.addr_len),
|
|
SyscallSucceeds());
|
|
break;
|
|
}
|
|
|
|
default:
|
|
FAIL() << "unknown address family: " << static_cast<int>(GetParam());
|
|
}
|
|
}
|
|
|
|
int ClientAddressFamily() const {
|
|
if (GetParam() == AddressFamily::kIpv6) {
|
|
return AF_INET6;
|
|
}
|
|
return AF_INET;
|
|
}
|
|
|
|
int ServerAddressFamily() const {
|
|
if (GetParam() == AddressFamily::kIpv4) {
|
|
return AF_INET;
|
|
}
|
|
return AF_INET6;
|
|
}
|
|
|
|
FileDescriptor server_;
|
|
FileDescriptor client_;
|
|
|
|
private:
|
|
static constexpr uint16_t port_ = 1337;
|
|
};
|
|
|
|
TEST_P(UdpSocketControlMessagesTest, SetAndReceiveTOSOrTClass) {
|
|
// TODO(b/267210840): Test is flaky on hostinet.
|
|
SKIP_IF(IsRunningWithHostinet());
|
|
|
|
// Enable receiving TOS and maybe TClass on the receiver.
|
|
ASSERT_THAT(setsockopt(server_.get(), SOL_IP, IP_RECVTOS, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
if (ServerAddressFamily() == AF_INET6) {
|
|
ASSERT_THAT(setsockopt(server_.get(), SOL_IPV6, IPV6_RECVTCLASS,
|
|
&kSockOptOn, sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
// Set custom TOS and maybe TClass on the sender.
|
|
constexpr int kTOS = IPTOS_LOWDELAY;
|
|
constexpr int kTClass = IPTOS_THROUGHPUT;
|
|
ASSERT_NE(kTOS, kTClass);
|
|
|
|
ASSERT_THAT(setsockopt(client_.get(), SOL_IP, IP_TOS, &kTOS, sizeof(kTOS)),
|
|
SyscallSucceeds());
|
|
if (ClientAddressFamily() == AF_INET6) {
|
|
ASSERT_THAT(setsockopt(client_.get(), SOL_IPV6, IPV6_TCLASS, &kTClass,
|
|
sizeof(kTClass)),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
constexpr size_t kArbitrarySendSize = 1042;
|
|
constexpr char sent_data[kArbitrarySendSize] = {};
|
|
ASSERT_THAT(RetryEINTR(send)(client_.get(), sent_data, sizeof(sent_data), 0),
|
|
SyscallSucceedsWithValue(sizeof(sent_data)));
|
|
|
|
char recv_data[sizeof(sent_data) + 1] = {};
|
|
size_t recv_data_len = sizeof(recv_data);
|
|
|
|
if (ClientAddressFamily() == AF_INET) {
|
|
uint8_t tos;
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
RecvTOS(server_.get(), recv_data, &recv_data_len, &tos));
|
|
EXPECT_EQ(static_cast<int>(tos), kTOS);
|
|
} else {
|
|
int tclass;
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
RecvTClass(server_.get(), recv_data, &recv_data_len, &tclass));
|
|
EXPECT_EQ(tclass, kTClass);
|
|
}
|
|
EXPECT_EQ(recv_data_len, sizeof(sent_data));
|
|
}
|
|
|
|
TEST_P(UdpSocketControlMessagesTest, SendAndReceiveTOSorTClass) {
|
|
// TODO(b/146661005): Setting TOS via sendmsg is not supported by netstack.
|
|
SKIP_IF(IsRunningOnGvisor() && !IsRunningWithHostinet());
|
|
|
|
// Enable receiving TOS and maybe TClass on the receiver.
|
|
ASSERT_THAT(setsockopt(server_.get(), SOL_IP, IP_RECVTOS, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
if (ServerAddressFamily() == AF_INET6) {
|
|
ASSERT_THAT(setsockopt(server_.get(), SOL_IPV6, IPV6_RECVTCLASS,
|
|
&kSockOptOn, sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
constexpr uint8_t kSendCmsgValue = IPTOS_LOWDELAY;
|
|
constexpr size_t kArbitrarySendSize = 1024;
|
|
char sent_data[kArbitrarySendSize] = {};
|
|
char recv_data[sizeof(sent_data) + 1] = {};
|
|
size_t recv_data_len = sizeof(recv_data);
|
|
|
|
if (ClientAddressFamily() == AF_INET) {
|
|
ASSERT_NO_FATAL_FAILURE(SendTOS(client_.get(), sent_data,
|
|
size_t(sizeof(sent_data)), kSendCmsgValue));
|
|
uint8_t tos;
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
RecvTOS(server_.get(), recv_data, &recv_data_len, &tos));
|
|
EXPECT_EQ(static_cast<int>(tos), kSendCmsgValue);
|
|
} else {
|
|
ASSERT_NO_FATAL_FAILURE(SendTClass(
|
|
client_.get(), sent_data, size_t(sizeof(sent_data)), kSendCmsgValue));
|
|
int tclass;
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
RecvTClass(server_.get(), recv_data, &recv_data_len, &tclass));
|
|
EXPECT_EQ(tclass, kSendCmsgValue);
|
|
}
|
|
EXPECT_EQ(recv_data_len, sizeof(sent_data));
|
|
}
|
|
|
|
TEST_P(UdpSocketControlMessagesTest, SetAndReceiveTTLOrHopLimit) {
|
|
// Enable receiving TTL and maybe HOPLIMIT on the receiver.
|
|
ASSERT_THAT(setsockopt(server_.get(), SOL_IP, IP_RECVTTL, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
if (ServerAddressFamily() == AF_INET6) {
|
|
ASSERT_THAT(setsockopt(server_.get(), SOL_IPV6, IPV6_RECVHOPLIMIT,
|
|
&kSockOptOn, sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
// Set custom TTL and maybe HOPLIMIT on the sender.
|
|
constexpr int kTTL = 21;
|
|
constexpr int kHopLimit = 42;
|
|
ASSERT_NE(kTTL, kHopLimit);
|
|
|
|
ASSERT_THAT(setsockopt(client_.get(), SOL_IP, IP_TTL, &kTTL, sizeof(kTTL)),
|
|
SyscallSucceeds());
|
|
if (ClientAddressFamily() == AF_INET6) {
|
|
ASSERT_THAT(setsockopt(client_.get(), SOL_IPV6, IPV6_UNICAST_HOPS,
|
|
&kHopLimit, sizeof(kHopLimit)),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
constexpr size_t kArbitrarySendSize = 1042;
|
|
constexpr char sent_data[kArbitrarySendSize] = {};
|
|
ASSERT_THAT(RetryEINTR(send)(client_.get(), sent_data, sizeof(sent_data), 0),
|
|
SyscallSucceedsWithValue(sizeof(sent_data)));
|
|
|
|
char recv_data[sizeof(sent_data)] = {};
|
|
size_t recv_data_len = sizeof(recv_data);
|
|
|
|
if (ClientAddressFamily() == AF_INET) {
|
|
int ttl;
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
RecvTTL(server_.get(), recv_data, &recv_data_len, &ttl));
|
|
EXPECT_EQ(ttl, kTTL);
|
|
} else {
|
|
int hoplimit;
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
RecvHopLimit(server_.get(), recv_data, &recv_data_len, &hoplimit));
|
|
EXPECT_EQ(hoplimit, kHopLimit);
|
|
}
|
|
EXPECT_EQ(recv_data_len, sizeof(sent_data));
|
|
}
|
|
|
|
TEST_P(UdpSocketControlMessagesTest, SendAndReceiveTTLOrHopLimit) {
|
|
// Enable receiving TTL and maybe HOPLIMIT on the receiver.
|
|
ASSERT_THAT(setsockopt(server_.get(), SOL_IP, IP_RECVTTL, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
if (ServerAddressFamily() == AF_INET6) {
|
|
ASSERT_THAT(setsockopt(server_.get(), SOL_IPV6, IPV6_RECVHOPLIMIT,
|
|
&kSockOptOn, sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
constexpr int kSendCmsgValue = 42;
|
|
constexpr size_t kArbitrarySendSize = 1024;
|
|
char sent_data[kArbitrarySendSize] = {};
|
|
char recv_data[sizeof(sent_data) + 1] = {};
|
|
size_t recv_data_len = sizeof(recv_data);
|
|
|
|
if (ClientAddressFamily() == AF_INET) {
|
|
ASSERT_NO_FATAL_FAILURE(SendTTL(client_.get(), sent_data,
|
|
size_t(sizeof(sent_data)), kSendCmsgValue));
|
|
int ttl;
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
RecvTTL(server_.get(), recv_data, &recv_data_len, &ttl));
|
|
EXPECT_EQ(ttl, kSendCmsgValue);
|
|
} else {
|
|
ASSERT_NO_FATAL_FAILURE(SendHopLimit(
|
|
client_.get(), sent_data, size_t(sizeof(sent_data)), kSendCmsgValue));
|
|
int hoplimit;
|
|
ASSERT_NO_FATAL_FAILURE(
|
|
RecvHopLimit(server_.get(), recv_data, &recv_data_len, &hoplimit));
|
|
EXPECT_EQ(hoplimit, kSendCmsgValue);
|
|
}
|
|
EXPECT_EQ(recv_data_len, sizeof(sent_data));
|
|
}
|
|
|
|
TEST_P(UdpSocketControlMessagesTest, SetAndReceivePktInfo) {
|
|
// Enable receiving IP_PKTINFO and maybe IPV6_PKTINFO on the receiver.
|
|
ASSERT_THAT(setsockopt(server_.get(), SOL_IP, IP_PKTINFO, &kSockOptOn,
|
|
sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
if (ServerAddressFamily() == AF_INET6) {
|
|
ASSERT_THAT(setsockopt(server_.get(), SOL_IPV6, IPV6_RECVPKTINFO,
|
|
&kSockOptOn, sizeof(kSockOptOn)),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
constexpr size_t kArbitrarySendSize = 1042;
|
|
constexpr char sent_data[kArbitrarySendSize] = {};
|
|
ASSERT_THAT(RetryEINTR(send)(client_.get(), sent_data, sizeof(sent_data), 0),
|
|
SyscallSucceedsWithValue(sizeof(sent_data)));
|
|
|
|
char recv_data[sizeof(sent_data) + 1] = {};
|
|
size_t recv_data_len = sizeof(recv_data);
|
|
switch (GetParam()) {
|
|
case AddressFamily::kIpv4: {
|
|
in_pktinfo received_pktinfo;
|
|
ASSERT_NO_FATAL_FAILURE(RecvPktInfo(server_.get(), recv_data,
|
|
&recv_data_len, &received_pktinfo));
|
|
EXPECT_EQ(recv_data_len, sizeof(sent_data));
|
|
EXPECT_EQ(received_pktinfo.ipi_ifindex,
|
|
ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()));
|
|
EXPECT_EQ(ntohl(received_pktinfo.ipi_spec_dst.s_addr), INADDR_LOOPBACK);
|
|
EXPECT_EQ(ntohl(received_pktinfo.ipi_addr.s_addr), INADDR_LOOPBACK);
|
|
break;
|
|
}
|
|
|
|
case AddressFamily::kIpv6: {
|
|
in6_pktinfo received_pktinfo;
|
|
ASSERT_NO_FATAL_FAILURE(RecvIPv6PktInfo(
|
|
server_.get(), recv_data, &recv_data_len, &received_pktinfo));
|
|
EXPECT_EQ(recv_data_len, sizeof(sent_data));
|
|
EXPECT_EQ(received_pktinfo.ipi6_ifindex,
|
|
ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()));
|
|
ASSERT_EQ(memcmp(&received_pktinfo.ipi6_addr, &in6addr_loopback,
|
|
sizeof(in6addr_loopback)),
|
|
0);
|
|
break;
|
|
}
|
|
|
|
case AddressFamily::kDualStack: {
|
|
// TODO(https://gvisor.dev/issue/7144): On dual stack sockets, Linux can
|
|
// receive both the IPv4 and IPv6 packet info. gVisor should do the same.
|
|
iovec iov = {
|
|
iov.iov_base = recv_data,
|
|
iov.iov_len = recv_data_len,
|
|
};
|
|
// Add an extra byte to confirm we only read what we expected.
|
|
char control[CMSG_SPACE(sizeof(in_pktinfo)) +
|
|
CMSG_SPACE(sizeof(in6_pktinfo)) + 1] = {};
|
|
msghdr msg = {
|
|
.msg_iov = &iov,
|
|
.msg_iovlen = 1,
|
|
.msg_control = control,
|
|
.msg_controllen = sizeof(control),
|
|
};
|
|
|
|
ASSERT_THAT(
|
|
recv_data_len = RetryEINTR(recvmsg)(server_.get(), &msg, /*flags=*/0),
|
|
SyscallSucceeds());
|
|
EXPECT_EQ(recv_data_len, sizeof(sent_data));
|
|
size_t expected_controllen = CMSG_SPACE(sizeof(in_pktinfo));
|
|
if (!IsRunningOnGvisor() || IsRunningWithHostinet()) {
|
|
expected_controllen += CMSG_SPACE(sizeof(in6_pktinfo));
|
|
}
|
|
EXPECT_EQ(msg.msg_controllen, expected_controllen);
|
|
|
|
std::pair<in_pktinfo, bool> received_pktinfo;
|
|
std::pair<in6_pktinfo, bool> received_pktinfo6;
|
|
|
|
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg);
|
|
while (cmsg != nullptr) {
|
|
ASSERT_TRUE(cmsg->cmsg_level == SOL_IP || cmsg->cmsg_level == SOL_IPV6);
|
|
if (cmsg->cmsg_level == SOL_IP) {
|
|
ASSERT_FALSE(received_pktinfo.second);
|
|
ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(in_pktinfo)));
|
|
ASSERT_EQ(cmsg->cmsg_type, IP_PKTINFO);
|
|
received_pktinfo.second = true;
|
|
std::copy_n(CMSG_DATA(cmsg), sizeof(received_pktinfo.first),
|
|
reinterpret_cast<uint8_t*>(&received_pktinfo.first));
|
|
} else { // SOL_IPV6
|
|
ASSERT_FALSE(received_pktinfo6.second);
|
|
ASSERT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(in6_pktinfo)));
|
|
ASSERT_EQ(cmsg->cmsg_type, IPV6_PKTINFO);
|
|
received_pktinfo6.second = true;
|
|
std::copy_n(CMSG_DATA(cmsg), sizeof(received_pktinfo6.first),
|
|
reinterpret_cast<uint8_t*>(&received_pktinfo6.first));
|
|
}
|
|
cmsg = CMSG_NXTHDR(&msg, cmsg);
|
|
}
|
|
|
|
ASSERT_TRUE(received_pktinfo.second);
|
|
EXPECT_EQ(received_pktinfo.first.ipi_ifindex,
|
|
ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()));
|
|
EXPECT_EQ(ntohl(received_pktinfo.first.ipi_spec_dst.s_addr),
|
|
INADDR_LOOPBACK);
|
|
EXPECT_EQ(ntohl(received_pktinfo.first.ipi_addr.s_addr), INADDR_LOOPBACK);
|
|
|
|
if (!IsRunningOnGvisor() || IsRunningWithHostinet()) {
|
|
ASSERT_TRUE(received_pktinfo6.second);
|
|
EXPECT_EQ(received_pktinfo6.first.ipi6_ifindex,
|
|
ASSERT_NO_ERRNO_AND_VALUE(GetLoopbackIndex()));
|
|
struct in6_addr expected;
|
|
inet_pton(AF_INET6, "::ffff:127.0.0.1", &expected);
|
|
EXPECT_EQ(memcmp(&received_pktinfo6.first.ipi6_addr, &expected,
|
|
sizeof(expected)),
|
|
0);
|
|
} else {
|
|
ASSERT_FALSE(received_pktinfo6.second);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, SendPacketLargerThanSendBufOnNonBlockingSocket) {
|
|
constexpr int kSendBufSize = 4096;
|
|
ASSERT_THAT(setsockopt(sock_.get(), SOL_SOCKET, SO_SNDBUF, &kSendBufSize,
|
|
sizeof(kSendBufSize)),
|
|
SyscallSucceeds());
|
|
|
|
// Set sock to non-blocking.
|
|
{
|
|
int opts = 0;
|
|
ASSERT_THAT(opts = fcntl(sock_.get(), F_GETFL), SyscallSucceeds());
|
|
ASSERT_THAT(fcntl(sock_.get(), F_SETFL, opts | O_NONBLOCK),
|
|
SyscallSucceeds());
|
|
}
|
|
|
|
{
|
|
sockaddr_storage addr = InetLoopbackAddr();
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), AsSockAddr(&addr)));
|
|
}
|
|
|
|
sockaddr_storage addr;
|
|
socklen_t len = sizeof(sockaddr_storage);
|
|
ASSERT_THAT(getsockname(sock_.get(), AsSockAddr(&addr), &len),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(len, addrlen_);
|
|
|
|
// We are allowed to send packets as large as we want as long as there is
|
|
// space in the send buffer, even if the new packet will result in more bytes
|
|
// being used than available in the send buffer.
|
|
char buf[kSendBufSize + 1] = {};
|
|
ASSERT_THAT(
|
|
sendto(sock_.get(), buf, sizeof(buf), 0, AsSockAddr(&addr), sizeof(addr)),
|
|
SyscallSucceedsWithValue(sizeof(buf)));
|
|
|
|
// The second write may fail with EAGAIN if the previous send is still
|
|
// in-flight.
|
|
ASSERT_THAT(
|
|
sendto(sock_.get(), buf, sizeof(buf), 0, AsSockAddr(&addr), sizeof(addr)),
|
|
AnyOf(SyscallSucceedsWithValue(sizeof(buf)),
|
|
SyscallFailsWithErrno(EAGAIN)));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReadShutdownOnBoundSocket) {
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
|
|
// Bind `sock_` to loopback.
|
|
struct sockaddr_storage addr_storage = InetLoopbackAddr();
|
|
struct sockaddr* addr = AsSockAddr(&addr_storage);
|
|
ASSERT_NO_ERRNO(BindSocket(sock_.get(), addr));
|
|
|
|
int shut_opts[] = {SHUT_RD, SHUT_WR, SHUT_RDWR};
|
|
for (int shut_opt : shut_opts) {
|
|
EXPECT_THAT(shutdown(sock_.get(), shut_opt),
|
|
SyscallFailsWithErrno(ENOTCONN));
|
|
}
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReconnectDoesNotClearReadShutdown) {
|
|
// TODO(gvisor.dev/issue/1202): Reconnecting the UDP socket after shutdown
|
|
// fails on hostinet.
|
|
SKIP_IF(IsRunningWithHostinet());
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
ASSERT_THAT(shutdown(sock_.get(), SHUT_RD), SyscallSucceeds());
|
|
|
|
char received[512] = {};
|
|
EXPECT_THAT(recv(sock_.get(), received, sizeof(received), 0),
|
|
SyscallSucceedsWithValue(0));
|
|
|
|
EXPECT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
EXPECT_THAT(recv(sock_.get(), received, sizeof(received), 0),
|
|
SyscallSucceedsWithValue(0));
|
|
}
|
|
|
|
TEST_P(UdpSocketTest, ReconnectDoesNotClearWriteShutdown) {
|
|
// TODO(gvisor.dev/issue/1202): Reconnecting the UDP socket after shutdown
|
|
// fails on hostinet.
|
|
SKIP_IF(IsRunningWithHostinet());
|
|
ASSERT_NO_ERRNO(BindLoopback());
|
|
ASSERT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
|
|
const char buf = 'A';
|
|
ASSERT_THAT(send(sock_.get(), &buf, 1, 0), SyscallSucceeds());
|
|
|
|
ASSERT_THAT(shutdown(sock_.get(), SHUT_WR), SyscallSucceeds());
|
|
EXPECT_THAT(send(sock_.get(), &buf, 1, 0), SyscallFailsWithErrno(EPIPE));
|
|
|
|
EXPECT_THAT(connect(sock_.get(), bind_addr_, addrlen_), SyscallSucceeds());
|
|
EXPECT_THAT(send(sock_.get(), &buf, 1, 0), SyscallFailsWithErrno(EPIPE));
|
|
}
|
|
|
|
INSTANTIATE_TEST_SUITE_P(AllInetTests, UdpSocketControlMessagesTest,
|
|
::testing::Values(AddressFamily::kIpv4,
|
|
AddressFamily::kIpv6,
|
|
AddressFamily::kDualStack));
|
|
|
|
TEST(UdpInet6SocketTest, ConnectInet4Sockaddr) {
|
|
// glibc getaddrinfo expects the invariant expressed by this test to be held.
|
|
const sockaddr_in connect_sockaddr = {
|
|
.sin_family = AF_INET, .sin_addr = {.s_addr = htonl(INADDR_LOOPBACK)}};
|
|
auto sock_ =
|
|
ASSERT_NO_ERRNO_AND_VALUE(Socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP));
|
|
ASSERT_THAT(
|
|
connect(sock_.get(),
|
|
reinterpret_cast<const struct sockaddr*>(&connect_sockaddr),
|
|
sizeof(sockaddr_in)),
|
|
SyscallSucceeds());
|
|
sockaddr_storage sockname;
|
|
socklen_t len = sizeof(sockaddr_storage);
|
|
ASSERT_THAT(getsockname(sock_.get(), AsSockAddr(&sockname), &len),
|
|
SyscallSucceeds());
|
|
ASSERT_EQ(sockname.ss_family, AF_INET6);
|
|
ASSERT_EQ(len, sizeof(sockaddr_in6));
|
|
auto sin6 = reinterpret_cast<struct sockaddr_in6*>(&sockname);
|
|
char addr_buf[INET6_ADDRSTRLEN] = {};
|
|
const char* addr;
|
|
ASSERT_NE(addr = inet_ntop(sockname.ss_family, &sockname, addr_buf,
|
|
sizeof(addr_buf)),
|
|
nullptr);
|
|
ASSERT_TRUE(IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) << addr;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
} // namespace testing
|
|
} // namespace gvisor
|