blob: 3ea0f5677c58a12c81bb2db7476b3779af6999a0 [file] [log] [blame]
// Copyright 2024 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <arpa/inet.h>
#include <fidl/fuchsia.netemul.sync/cpp/wire.h>
#include <lib/component/incoming/cpp/protocol.h>
#include <netinet/in.h>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include "src/connectivity/network/tests/socket/util.h"
namespace {
constexpr char kBusName[] = "test-bus";
constexpr char kTestName[] = "waiting...";
constexpr char kFilterSetupName[] = "setup-complete";
constexpr std::array<uint8_t, 4> kSendBuf = {1, 2, 3, 4};
TEST(TransparentProxyTest, NonTransparentSocketV4) {
fbl::unique_fd listener;
ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
const sockaddr_in bind_addr = LoopbackSockaddrV4(8001);
ASSERT_EQ(bind(listener.get(), reinterpret_cast<const sockaddr*>(&bind_addr), sizeof(bind_addr)),
0)
<< strerror(errno);
fbl::unique_fd sender;
ASSERT_TRUE(sender = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
const sockaddr_in sender_addr = LoopbackSockaddrV4(0);
ASSERT_EQ(
bind(sender.get(), reinterpret_cast<const sockaddr*>(&sender_addr), sizeof(sender_addr)), 0)
<< strerror(errno);
// Send to a *different* port than the listener socket is bound to.
const sockaddr_in dst_addr = LoopbackSockaddrV4(11111);
ASSERT_EQ(sendto(sender.get(), kSendBuf.data(), sizeof(kSendBuf), 0,
reinterpret_cast<const sockaddr*>(&dst_addr), sizeof(dst_addr)),
static_cast<ssize_t>(sizeof(kSendBuf)))
<< strerror(errno);
// Because the IP_TRANSPARENT option is not enabled on the listener socket, the
// packet will not be transparently proxied.
timeval tv = {
.tv_sec = std::chrono::seconds(kNegativeCheckTimeout).count(),
};
EXPECT_EQ(setsockopt(listener.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), 0)
<< strerror(errno);
std::array<uint8_t, sizeof(kSendBuf)> recv_buf;
EXPECT_EQ(recv(listener.get(), recv_buf.data(), sizeof(recv_buf), 0), -1) << strerror(errno);
EXPECT_EQ(errno, EAGAIN);
}
TEST(TransparentProxyTest, NonTransparentSocketV6) {
fbl::unique_fd listener;
ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
const sockaddr_in6 bind_addr = LoopbackSockaddrV6(8001);
ASSERT_EQ(bind(listener.get(), reinterpret_cast<const sockaddr*>(&bind_addr), sizeof(bind_addr)),
0)
<< strerror(errno);
fbl::unique_fd sender;
ASSERT_TRUE(sender = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
const sockaddr_in6 sender_addr = LoopbackSockaddrV6(0);
ASSERT_EQ(
bind(sender.get(), reinterpret_cast<const sockaddr*>(&sender_addr), sizeof(sender_addr)), 0)
<< strerror(errno);
// Send to a *different* port than the listener socket is bound to.
const sockaddr_in6 dst_addr = LoopbackSockaddrV6(11111);
ASSERT_EQ(sendto(sender.get(), kSendBuf.data(), sizeof(kSendBuf), 0,
reinterpret_cast<const sockaddr*>(&dst_addr), sizeof(dst_addr)),
static_cast<ssize_t>(sizeof(kSendBuf)))
<< strerror(errno);
// Because the IP_TRANSPARENT option is not enabled on the listener socket, the
// packet will not be transparently proxied.
timeval tv = {
.tv_sec = std::chrono::seconds(kNegativeCheckTimeout).count(),
};
EXPECT_EQ(setsockopt(listener.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)), 0)
<< strerror(errno);
std::array<uint8_t, sizeof(kSendBuf)> recv_buf;
EXPECT_EQ(recv(listener.get(), recv_buf.data(), sizeof(recv_buf), 0), -1) << strerror(errno);
EXPECT_EQ(errno, EAGAIN);
}
struct TestCase {
std::string test_name;
in_port_t listen_port;
std::string dst_addr_str;
in_port_t dst_port;
};
std::array<TestCase, 3> Ipv4TestCases() {
return {// This packet will be proxied to socket bound to 8001, but its
// destination address will remain the same (localhost).
TestCase{.test_name = "Proxy to port 8001",
.listen_port = 8001,
.dst_addr_str = "127.0.0.1",
.dst_port = 11111},
// This packet will be proxied to socket bound on localhost, but its
// destination port will remain the same (22222).
TestCase{.test_name = "Proxy to localhost",
.listen_port = 22222,
.dst_addr_str = "192.0.2.1",
.dst_port = 22222},
// This packet will be proxied to socket bound to 8002; both its
// destination address and port have been overridden.
TestCase{.test_name = "Proxy to localhost port 8002",
.listen_port = 8002,
.dst_addr_str = "192.0.2.1",
.dst_port = 33333}};
}
class TransparentProxyV4Test : public testing::TestWithParam<TestCase> {};
TEST_P(TransparentProxyV4Test, TransparentSocket) {
const auto [test_name, listen_port, dst_addr_str, dst_port] = GetParam();
fbl::unique_fd listener;
ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
// Bind a transparent listener socket so that it can receive transparently
// proxied packets; also enable the `IP_RECVORIGDSTADDR` socket option so we can
// observe the original destination of such packets.
constexpr int kEnable = 1;
ASSERT_EQ(setsockopt(listener.get(), SOL_IP, IP_TRANSPARENT, &kEnable, sizeof(kEnable)), 0)
<< strerror(errno);
ASSERT_EQ(setsockopt(listener.get(), SOL_IP, IP_RECVORIGDSTADDR, &kEnable, sizeof(kEnable)), 0)
<< strerror(errno);
const sockaddr_in bind_addr = LoopbackSockaddrV4(listen_port);
ASSERT_EQ(bind(listener.get(), reinterpret_cast<const sockaddr*>(&bind_addr), sizeof(bind_addr)),
0)
<< strerror(errno);
fbl::unique_fd sender;
ASSERT_TRUE(sender = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
const sockaddr_in sender_addr = LoopbackSockaddrV4(0);
ASSERT_EQ(
bind(sender.get(), reinterpret_cast<const sockaddr*>(&sender_addr), sizeof(sender_addr)), 0)
<< strerror(errno);
// Send to a *different* address and/or port than the listener socket is bound to.
sockaddr_in dst_addr = {
.sin_family = AF_INET,
.sin_port = htons(dst_port),
};
ASSERT_EQ(inet_pton(AF_INET, dst_addr_str.c_str(), &dst_addr.sin_addr.s_addr), 1)
<< strerror(errno);
ASSERT_EQ(sendto(sender.get(), kSendBuf.data(), sizeof(kSendBuf), 0,
reinterpret_cast<const sockaddr*>(&dst_addr), sizeof(dst_addr)),
static_cast<ssize_t>(sizeof(kSendBuf)))
<< strerror(errno);
std::array<uint8_t, sizeof(kSendBuf)> recv_buf;
iovec recv_iovec = {
.iov_base = recv_buf.data(),
.iov_len = sizeof(recv_buf),
};
sockaddr_in original_dst_addr;
std::array<uint8_t, CMSG_SPACE(sizeof(original_dst_addr))> recv_control;
msghdr recv_msghdr = {
.msg_iov = &recv_iovec,
.msg_iovlen = 1,
.msg_control = recv_control.data(),
.msg_controllen = sizeof(recv_control),
};
// The message should be received by the listener socket, along with the
// original destination address as a control message.
ASSERT_EQ(recvmsg(listener.get(), &recv_msghdr, 0), static_cast<ssize_t>(sizeof(kSendBuf)))
<< strerror(errno);
EXPECT_EQ(kSendBuf, recv_buf);
cmsghdr* cmsg = CMSG_FIRSTHDR(&recv_msghdr);
ASSERT_NE(cmsg, nullptr);
EXPECT_EQ(cmsg->cmsg_len, CMSG_LEN(sizeof(original_dst_addr)));
EXPECT_EQ(cmsg->cmsg_level, SOL_IP);
EXPECT_EQ(cmsg->cmsg_type, IP_RECVORIGDSTADDR);
memcpy(&original_dst_addr, CMSG_DATA(cmsg), sizeof(original_dst_addr));
EXPECT_EQ(memcmp(&original_dst_addr, &dst_addr, sizeof(dst_addr)), 0);
}
INSTANTIATE_TEST_SUITE_P(TransparentProxyTests, TransparentProxyV4Test,
testing::ValuesIn(Ipv4TestCases()),
[](const testing::TestParamInfo<TestCase>& info) {
std::string test_name(info.param.test_name);
std::replace(test_name.begin(), test_name.end(), ' ', '_');
return test_name;
});
std::array<TestCase, 3> Ipv6TestCases() {
return {// This packet will be proxied to socket bound to 8001, but its
// destination address will remain the same (localhost).
TestCase{.test_name = "Proxy to port 8001",
.listen_port = 8001,
.dst_addr_str = "::1",
.dst_port = 11111},
// This packet will be proxied to socket bound on localhost, but its
// destination port will remain the same (44444).
TestCase{.test_name = "Proxy to localhost",
.listen_port = 44444,
.dst_addr_str = "2001:db8::1",
.dst_port = 44444},
// This packet will be proxied to socket bound to 8002; both its
// destination address and port have been overridden.
TestCase{.test_name = "Proxy to localhost port 8002",
.listen_port = 8002,
.dst_addr_str = "2001:db8::1",
.dst_port = 55555}};
}
class TransparentProxyV6Test : public testing::TestWithParam<TestCase> {};
TEST_P(TransparentProxyV6Test, TransparentSocket) {
const auto [test_name, listen_port, dst_addr_str, dst_port] = GetParam();
fbl::unique_fd listener;
ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
// Bind a transparent listener socket so that it can receive transparently
// proxied packets.
//
// TODO(https://fxbug.dev/337872703): also enable the `IPV6_RECVORIGDSTADDR`
// socket option once it is implemented so we can observe the original
// destination of proxied IPv6 packets.
constexpr int kEnable = 1;
ASSERT_EQ(setsockopt(listener.get(), SOL_IP, IP_TRANSPARENT, &kEnable, sizeof(kEnable)), 0)
<< strerror(errno);
const sockaddr_in6 bind_addr = LoopbackSockaddrV6(listen_port);
ASSERT_EQ(bind(listener.get(), reinterpret_cast<const sockaddr*>(&bind_addr), sizeof(bind_addr)),
0)
<< strerror(errno);
fbl::unique_fd sender;
ASSERT_TRUE(sender = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
const sockaddr_in6 sender_addr = LoopbackSockaddrV6(0);
ASSERT_EQ(
bind(sender.get(), reinterpret_cast<const sockaddr*>(&sender_addr), sizeof(sender_addr)), 0)
<< strerror(errno);
// Send to a *different* address and/or port than the listener socket is bound to.
sockaddr_in6 dst_addr = {
.sin6_family = AF_INET6,
.sin6_port = htons(dst_port),
};
ASSERT_EQ(inet_pton(AF_INET6, dst_addr_str.c_str(), &dst_addr.sin6_addr), 1) << strerror(errno);
ASSERT_EQ(sendto(sender.get(), kSendBuf.data(), sizeof(kSendBuf), 0,
reinterpret_cast<const sockaddr*>(&dst_addr), sizeof(dst_addr)),
static_cast<ssize_t>(sizeof(kSendBuf)))
<< strerror(errno);
std::array<uint8_t, sizeof(kSendBuf)> recv_buf;
iovec recv_iovec = {
.iov_base = recv_buf.data(),
.iov_len = sizeof(recv_buf),
};
msghdr recv_msghdr = {
.msg_iov = &recv_iovec,
.msg_iovlen = 1,
.msg_control = nullptr,
.msg_controllen = 0,
};
// The message should be received by the listener socket.
//
// TODO(https://fxbug.dev/337872703): verify that the original destination
// address is received as a control message.
ASSERT_EQ(recvmsg(listener.get(), &recv_msghdr, 0), static_cast<ssize_t>(sizeof(kSendBuf)))
<< strerror(errno);
EXPECT_EQ(kSendBuf, recv_buf);
}
INSTANTIATE_TEST_SUITE_P(TransparentProxyTests, TransparentProxyV6Test,
testing::ValuesIn(Ipv6TestCases()),
[](const testing::TestParamInfo<TestCase>& info) {
std::string test_name(info.param.test_name);
std::replace(test_name.begin(), test_name.end(), ' ', '_');
return test_name;
});
class TransparentProxyDualStackTest : public testing::TestWithParam<TestCase> {};
TEST_P(TransparentProxyDualStackTest, TransparentSocket) {
const auto [test_name, listen_port, dst_addr_str, dst_port] = GetParam();
fbl::unique_fd listener;
ASSERT_TRUE(listener = fbl::unique_fd(socket(AF_INET6, SOCK_DGRAM, 0))) << strerror(errno);
// Bind a transparent listener socket so that it can receive transparently
// proxied packets.
//
// TODO(https://fxbug.dev/337872703): also enable the `IPV6_RECVORIGDSTADDR`
// socket option once it is implemented so we can observe the original
// destination of proxied IPv6 packets.
constexpr int kEnable = 1;
ASSERT_EQ(setsockopt(listener.get(), SOL_IP, IP_TRANSPARENT, &kEnable, sizeof(kEnable)), 0)
<< strerror(errno);
int v6_only;
socklen_t optlen = sizeof(v6_only);
ASSERT_EQ(getsockopt(listener.get(), SOL_IPV6, IPV6_V6ONLY, &v6_only, &optlen), 0)
<< strerror(errno);
EXPECT_EQ(v6_only, false);
// Bind to the IPv6 "any" address (::) so that we can receive both IPv4 and IPv6
// packets.
const sockaddr_in6 bind_addr = {
.sin6_family = AF_INET6,
.sin6_port = htons(listen_port),
.sin6_addr = IN6ADDR_ANY_INIT,
};
ASSERT_EQ(bind(listener.get(), reinterpret_cast<const sockaddr*>(&bind_addr), sizeof(bind_addr)),
0)
<< strerror(errno);
fbl::unique_fd sender;
ASSERT_TRUE(sender = fbl::unique_fd(socket(AF_INET, SOCK_DGRAM, 0))) << strerror(errno);
const sockaddr_in sender_addr = LoopbackSockaddrV4(0);
ASSERT_EQ(
bind(sender.get(), reinterpret_cast<const sockaddr*>(&sender_addr), sizeof(sender_addr)), 0)
<< strerror(errno);
// Send to a *different* address and/or port than the listener socket is bound to.
sockaddr_in dst_addr = {
.sin_family = AF_INET,
.sin_port = htons(dst_port),
};
ASSERT_EQ(inet_pton(AF_INET, dst_addr_str.c_str(), &dst_addr.sin_addr.s_addr), 1)
<< strerror(errno);
ASSERT_EQ(sendto(sender.get(), kSendBuf.data(), sizeof(kSendBuf), 0,
reinterpret_cast<const sockaddr*>(&dst_addr), sizeof(dst_addr)),
static_cast<ssize_t>(sizeof(kSendBuf)))
<< strerror(errno);
std::array<uint8_t, sizeof(kSendBuf)> recv_buf;
iovec recv_iovec = {
.iov_base = recv_buf.data(),
.iov_len = sizeof(recv_buf),
};
msghdr recv_msghdr = {
.msg_iov = &recv_iovec,
.msg_iovlen = 1,
.msg_control = nullptr,
.msg_controllen = 0,
};
// The message should be received by the listener socket.
//
// TODO(https://fxbug.dev/337872703): verify that the original destination
// address is received as an IPv4-mapped IPv6 address in a control message.
ASSERT_EQ(recvmsg(listener.get(), &recv_msghdr, 0), static_cast<ssize_t>(sizeof(kSendBuf)))
<< strerror(errno);
EXPECT_EQ(kSendBuf, recv_buf);
}
INSTANTIATE_TEST_SUITE_P(TransparentProxyTests, TransparentProxyDualStackTest,
testing::ValuesIn(Ipv4TestCases()),
[](const testing::TestParamInfo<TestCase>& info) {
std::string test_name(info.param.test_name);
std::replace(test_name.begin(), test_name.end(), ' ', '_');
return test_name;
});
} // namespace
int main(int argc, char** argv) {
// Make sure the transparent proxy has been configured before running any tests
// by waiting on the synchronization bus for the message that setup is complete.
zx::result client_end = component::Connect<fuchsia_netemul_sync::SyncManager>();
if (!client_end.is_ok()) {
ZX_PANIC("failed to connect to sync manager: %s", client_end.status_string());
}
fidl::WireSyncClient sync_manager{std::move(*client_end)};
auto bus_endpoints = fidl::CreateEndpoints<fuchsia_netemul_sync::Bus>();
if (bus_endpoints.is_error()) {
ZX_PANIC("error creating bus endpoints: %s", zx_status_get_string(bus_endpoints.error_value()));
}
{
fidl::Status result =
sync_manager->BusSubscribe(kBusName, kTestName, std::move(bus_endpoints->server));
ZX_ASSERT_MSG(result.status() == ZX_OK, "error getting bus: %s", result.status_string());
}
fidl::WireSyncClient bus{std::move(bus_endpoints->client)};
{
std::array<fidl::StringView, 1> clients = {fidl::StringView(kFilterSetupName)};
fidl::Status result = bus->WaitForClients(
fidl::VectorView<fidl::StringView>::FromExternal(clients), /* no timeout */ 0);
ZX_ASSERT_MSG(result.status() == ZX_OK, "error waiting for filter setup to complete: %s",
result.status_string());
}
testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}