blob: 094b2c786ec16912df2a53c94f73fac282d1f348 [file]
// Copyright (c) 2019 The Chromium 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 "quiche/quic/qbone/bonnet/tun_device_packet_exchanger.h"
#include <arpa/inet.h>
#include <netinet/icmp6.h>
#include <netinet/ip6.h>
#include <sys/uio.h>
#include <cerrno>
#include <cstddef>
#include <cstring>
#include <string>
#include "absl/status/status.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/quic/qbone/bonnet/mock_packet_exchanger_stats_interface.h"
#include "quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h"
#include "quiche/quic/qbone/mock_qbone_client.h"
#include "quiche/quic/qbone/platform/mock_kernel.h"
#include "quiche/quic/qbone/platform/mock_netlink.h"
#include "quiche/quic/qbone/qbone_constants.h"
namespace quic::test {
namespace {
const size_t kMtu = 1000;
const int kReadFd = 15;
const int kWriteFd = 16;
using ::testing::_;
using ::testing::StrEq;
using ::testing::StrictMock;
class MockVisitor : public QboneClientPacketExchanger::Visitor {
public:
MOCK_METHOD(void, OnReadError, (const std::string&), (override));
MOCK_METHOD(void, OnWriteError, (const std::string&), (override));
MOCK_METHOD(absl::Status, OnWrite, (absl::string_view), (override));
};
class TunDevicePacketExchangerTest : public QuicTest {
protected:
TunDevicePacketExchangerTest()
: exchanger_(kMtu, &mock_kernel_, nullptr, &mock_visitor_, false,
&mock_stats_, absl::string_view()) {
exchanger_.set_read_file_descriptor(kReadFd);
exchanger_.set_write_file_descriptor(kWriteFd);
}
~TunDevicePacketExchangerTest() override = default;
MockKernel mock_kernel_;
StrictMock<MockVisitor> mock_visitor_;
StrictMock<MockQboneClient> mock_client_;
StrictMock<MockPacketExchangerStatsInterface> mock_stats_;
TunDevicePacketExchanger exchanger_;
};
TEST_F(TunDevicePacketExchangerTest, WritePacketError) {
std::string packet = "fake packet";
EXPECT_CALL(mock_kernel_, writev(kWriteFd, _, 2))
.WillOnce([](int fd, const struct iovec* iov, int iovcnt) -> ssize_t {
EXPECT_EQ(iov[0].iov_base, nullptr);
EXPECT_EQ(iov[0].iov_len, 0);
EXPECT_THAT(reinterpret_cast<const char*>(iov[1].iov_base),
testing::StrEq("fake packet"));
EXPECT_EQ(iov[1].iov_len, 11);
errno = ECOMM;
return -1;
});
EXPECT_CALL(mock_visitor_, OnWriteError(_));
EXPECT_CALL(mock_visitor_, OnWrite(StrEq(packet))).Times(1);
exchanger_.WritePacketToNetwork(packet.data(), packet.size());
}
TEST_F(TunDevicePacketExchangerTest, WritePacketBlocked) {
std::string packet = "fake packet";
EXPECT_CALL(mock_kernel_, writev(kWriteFd, _, 2))
.WillOnce([](int fd, const struct iovec* iov, int iovcnt) -> ssize_t {
EXPECT_EQ(iov[0].iov_base, nullptr);
EXPECT_EQ(iov[0].iov_len, 0);
EXPECT_THAT(reinterpret_cast<const char*>(iov[1].iov_base),
testing::StrEq("fake packet"));
EXPECT_EQ(iov[1].iov_len, 11);
errno = EAGAIN;
return -1;
});
EXPECT_CALL(mock_stats_, OnWriteError(_)).Times(1);
EXPECT_CALL(mock_visitor_, OnWrite(StrEq(packet))).Times(1);
EXPECT_CALL(mock_visitor_, OnWriteError(_)).Times(1);
exchanger_.WritePacketToNetwork(packet.data(), packet.size());
}
TEST_F(TunDevicePacketExchangerTest, WritePacketSuccessfulWrite) {
std::string packet = "fake packet";
EXPECT_CALL(mock_kernel_, writev(kWriteFd, _, 2))
.WillOnce(
[&packet](int fd, const struct iovec* iov, int iovcnt) -> ssize_t {
EXPECT_EQ(iov[0].iov_base, nullptr);
EXPECT_EQ(iov[0].iov_len, 0);
EXPECT_THAT(reinterpret_cast<const char*>(iov[1].iov_base),
StrEq(packet));
EXPECT_EQ(iov[1].iov_len, packet.size());
return packet.size();
});
EXPECT_CALL(mock_stats_, OnPacketWritten(packet.size(), _)).Times(1);
EXPECT_CALL(mock_visitor_, OnWrite(StrEq(packet))).Times(1);
exchanger_.WritePacketToNetwork(packet.data(), packet.size());
}
TEST_F(TunDevicePacketExchangerTest, TapWritePacketSuccessful) {
StrictMock<MockKernel> mock_kernel;
StrictMock<MockNetlink> mock_netlink;
StrictMock<MockVisitor> mock_visitor;
StrictMock<MockPacketExchangerStatsInterface> mock_stats;
TunDevicePacketExchanger tap_exchanger(kMtu, &mock_kernel, &mock_netlink,
&mock_visitor, /*is_tap=*/true,
&mock_stats, "tap0");
tap_exchanger.set_write_file_descriptor(kWriteFd);
std::string packet = "fake packet";
// Expectations on Netlink to get the hardware address when writing the
// first packet
EXPECT_CALL(mock_netlink, GetLinkInfo("tap0", _))
.WillOnce([](const std::string& ifname,
NetlinkInterface::LinkInfo* link_info) -> bool {
uint8_t mac[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55};
memcpy(link_info->hardware_address, mac, ETH_ALEN);
return true;
});
// iov[0] should contain the Ethernet header populated by InitializeEthHdr
EXPECT_CALL(mock_kernel, writev(kWriteFd, _, 2))
.WillOnce([&packet](int fd, const struct iovec* iov,
int iovcnt) -> ssize_t {
EXPECT_NE(iov[0].iov_base, nullptr);
EXPECT_EQ(iov[0].iov_len, ETH_HLEN);
const char* first_buffer = static_cast<const char*>(iov[0].iov_base);
EXPECT_EQ(absl::string_view(first_buffer, ETH_ALEN),
absl::string_view("\x00\x11\x22\x33\x44\x55", ETH_ALEN));
EXPECT_EQ(absl::string_view(first_buffer + ETH_ALEN, ETH_ALEN),
absl::string_view("\x00\x11\x22\x33\x44\x55", ETH_ALEN));
uint16_t proto;
memcpy(&proto, first_buffer + 2 * ETH_ALEN, 2);
EXPECT_EQ(proto, absl::ghtons(ETH_P_IPV6));
EXPECT_EQ(absl::string_view(static_cast<const char*>(iov[1].iov_base),
iov[1].iov_len),
"fake packet");
return ETH_HLEN + packet.length();
});
EXPECT_CALL(mock_stats, OnPacketWritten(ETH_HLEN + packet.size(), _))
.Times(1);
EXPECT_CALL(mock_visitor, OnWrite(StrEq(packet))).Times(1);
tap_exchanger.WritePacketToNetwork(packet.data(), packet.size());
}
TEST_F(TunDevicePacketExchangerTest, ReadPacketError) {
EXPECT_CALL(mock_kernel_, readv(kReadFd, _, 2))
.WillOnce([](int fd, const struct iovec* iov, int iovcnt) {
errno = ECOMM;
return -1;
});
EXPECT_CALL(mock_visitor_, OnReadError(_));
EXPECT_FALSE(exchanger_.ReadAndDeliverPacket(&mock_client_));
}
TEST_F(TunDevicePacketExchangerTest, ReadPacketBlocked) {
EXPECT_CALL(mock_kernel_, readv(kReadFd, _, 2))
.WillOnce([](int fd, const struct iovec* iov, int iovcnt) {
errno = EAGAIN;
return -1;
});
EXPECT_CALL(mock_stats_, OnReadError(_)).Times(1);
EXPECT_CALL(mock_visitor_, OnReadError(_)).Times(1);
EXPECT_FALSE(exchanger_.ReadAndDeliverPacket(&mock_client_));
}
TEST_F(TunDevicePacketExchangerTest, ReadPacketSuccessfulRead) {
std::string packet = "fake_packet";
EXPECT_CALL(mock_kernel_, readv(kReadFd, _, 2))
.WillOnce([packet](int fd, const struct iovec* iov, int iovcnt) {
EXPECT_EQ(iov[0].iov_len, 0);
EXPECT_EQ(iov[1].iov_len, kMtu);
memcpy(iov[1].iov_base, packet.data(), packet.size());
return packet.size();
});
EXPECT_CALL(mock_client_, ProcessPacketFromNetwork(StrEq(packet)));
EXPECT_CALL(mock_stats_, OnPacketRead(_, _)).Times(1);
EXPECT_TRUE(exchanger_.ReadAndDeliverPacket(&mock_client_));
}
TEST_F(TunDevicePacketExchangerTest, WriteWithNullVisitor) {
TunDevicePacketExchanger exchanger(kMtu, &mock_kernel_, nullptr,
/*visitor=*/nullptr, false, &mock_stats_,
absl::string_view());
exchanger.set_write_file_descriptor(kWriteFd);
std::string packet = "fake packet";
EXPECT_CALL(mock_kernel_, writev(kWriteFd, _, 2))
.WillOnce(
[&packet](int fd, const struct iovec* iov, int iovcnt) -> ssize_t {
EXPECT_EQ(iov[0].iov_base, nullptr);
EXPECT_EQ(iov[0].iov_len, 0);
EXPECT_THAT(reinterpret_cast<const char*>(iov[1].iov_base),
StrEq(packet));
EXPECT_EQ(iov[1].iov_len, packet.size());
return packet.size();
});
EXPECT_CALL(mock_stats_, OnPacketWritten(packet.size(), _)).Times(1);
exchanger.WritePacketToNetwork(packet.data(), packet.size());
}
TEST_F(TunDevicePacketExchangerTest, WritePacketErrorWithNullVisitor) {
TunDevicePacketExchanger exchanger(kMtu, &mock_kernel_, nullptr,
/*visitor=*/nullptr, false, &mock_stats_,
absl::string_view());
exchanger.set_write_file_descriptor(kWriteFd);
std::string packet = "fake packet";
EXPECT_CALL(mock_kernel_, writev(kWriteFd, _, 2))
.WillOnce([](int fd, const struct iovec* iov, int iovcnt) -> ssize_t {
EXPECT_EQ(iov[0].iov_base, nullptr);
EXPECT_EQ(iov[0].iov_len, 0);
EXPECT_THAT(reinterpret_cast<const char*>(iov[1].iov_base),
testing::StrEq("fake packet"));
EXPECT_EQ(iov[1].iov_len, 11);
errno = ECOMM;
return -1;
});
exchanger.WritePacketToNetwork(packet.data(), packet.size());
}
TEST_F(TunDevicePacketExchangerTest, ReadPacketWithNullVisitor) {
TunDevicePacketExchanger exchanger(kMtu, &mock_kernel_, nullptr,
/*visitor=*/nullptr, false, &mock_stats_,
absl::string_view());
exchanger.set_read_file_descriptor(kReadFd);
std::string packet = "fake_packet";
EXPECT_CALL(mock_kernel_, readv(kReadFd, _, 2))
.WillOnce([packet](int fd, const struct iovec* iov, int iovcnt) {
EXPECT_EQ(iov[0].iov_len, 0);
EXPECT_EQ(iov[1].iov_len, kMtu);
memcpy(iov[1].iov_base, packet.data(), packet.size());
return packet.size();
});
EXPECT_CALL(mock_client_, ProcessPacketFromNetwork(StrEq(packet)));
EXPECT_CALL(mock_stats_, OnPacketRead(_, _)).Times(1);
EXPECT_TRUE(exchanger.ReadAndDeliverPacket(&mock_client_));
}
TEST_F(TunDevicePacketExchangerTest, ReadPacketErrorWithNullVisitor) {
TunDevicePacketExchanger exchanger(kMtu, &mock_kernel_, nullptr,
/*visitor=*/nullptr, false, &mock_stats_,
absl::string_view());
exchanger.set_read_file_descriptor(kReadFd);
EXPECT_CALL(mock_kernel_, readv(kReadFd, _, 2))
.WillOnce([](int fd, const struct iovec* iov, int iovcnt) {
errno = ECOMM;
return -1;
});
EXPECT_FALSE(exchanger.ReadAndDeliverPacket(&mock_client_));
}
class TunDevicePacketExchangerTapTest : public QuicTest {
protected:
TunDevicePacketExchangerTapTest()
: exchanger_(kMtu, &mock_kernel_, &mock_netlink_, &mock_visitor_, true,
&mock_stats_, "tap0") {
exchanger_.set_read_file_descriptor(kReadFd);
exchanger_.set_write_file_descriptor(kWriteFd);
}
~TunDevicePacketExchangerTapTest() override = default;
MockKernel mock_kernel_;
StrictMock<MockNetlink> mock_netlink_;
StrictMock<MockVisitor> mock_visitor_;
StrictMock<MockQboneClient> mock_client_;
StrictMock<MockPacketExchangerStatsInterface> mock_stats_;
TunDevicePacketExchanger exchanger_;
};
TEST_F(TunDevicePacketExchangerTapTest, ReadPacketTapSuccess) {
ip6_hdr ip_hdr{};
ip_hdr.ip6_vfc = 0x60; // Version 6
ip_hdr.ip6_nxt = 59; // No next header
std::string l3_payload = "hello";
std::string l3_packet =
std::string(reinterpret_cast<char*>(&ip_hdr), sizeof(ip_hdr)) +
l3_payload;
ethhdr eth_hdr{};
eth_hdr.h_proto = absl::ghtons(ETH_P_IPV6);
EXPECT_CALL(mock_kernel_, readv(kReadFd, _, 2))
.WillOnce(
[eth_hdr, l3_packet](int fd, const struct iovec* iov, int iovcnt) {
EXPECT_EQ(iov[0].iov_len, ETH_HLEN);
EXPECT_EQ(iov[1].iov_len, kMtu);
memcpy(iov[0].iov_base, &eth_hdr, ETH_HLEN);
memcpy(iov[1].iov_base, l3_packet.data(), l3_packet.size());
return ETH_HLEN + l3_packet.size();
});
EXPECT_CALL(mock_client_, ProcessPacketFromNetwork(StrEq(l3_packet)));
EXPECT_CALL(mock_stats_, OnPacketRead(l3_packet.size(), _)).Times(1);
EXPECT_TRUE(exchanger_.ReadAndDeliverPacket(&mock_client_));
}
TEST_F(TunDevicePacketExchangerTapTest, ReadPacketTapInvalidL2) {
ethhdr eth_hdr{};
eth_hdr.h_proto = absl::ghtons(ETH_P_ARP); // Non-IPv6
EXPECT_CALL(mock_kernel_, readv(kReadFd, _, 2))
.WillOnce([eth_hdr](int fd, const struct iovec* iov, int iovcnt) {
memcpy(iov[0].iov_base, &eth_hdr, ETH_HLEN);
return ETH_HLEN + 10; // Read some bytes
});
EXPECT_CALL(mock_visitor_, OnReadError(_));
EXPECT_CALL(mock_stats_, OnReadError(_));
EXPECT_FALSE(exchanger_.ReadAndDeliverPacket(&mock_client_));
}
TEST_F(TunDevicePacketExchangerTapTest, ReadPacketTapNeighborSolicitation) {
ip6_hdr ip_hdr{};
ip_hdr.ip6_vfc = 0x60; // Version 6
ip_hdr.ip6_nxt = IPPROTO_ICMPV6;
inet_pton(AF_INET6, "fe80::2", &ip_hdr.ip6_src);
inet_pton(AF_INET6, "fe80::1", &ip_hdr.ip6_dst);
icmp6_hdr icmp_hdr{};
icmp_hdr.icmp6_type = ND_NEIGHBOR_SOLICIT;
in6_addr target_address = QboneConstants::GatewayAddress()->GetIPv6();
std::string l3_packet =
std::string(reinterpret_cast<char*>(&ip_hdr), sizeof(ip_hdr)) +
std::string(reinterpret_cast<char*>(&icmp_hdr), sizeof(icmp_hdr)) +
std::string(reinterpret_cast<char*>(&target_address),
sizeof(target_address));
ethhdr eth_hdr{};
eth_hdr.h_proto = absl::ghtons(ETH_P_IPV6);
EXPECT_CALL(mock_kernel_, readv(kReadFd, _, 2))
.WillOnce(
[eth_hdr, l3_packet](int fd, const struct iovec* iov, int iovcnt) {
memcpy(iov[0].iov_base, &eth_hdr, ETH_HLEN);
memcpy(iov[1].iov_base, l3_packet.data(), l3_packet.size());
return ETH_HLEN + l3_packet.size();
});
// Expect GetLinkInfo to populate ethhdr on writing neighbor solicit response.
EXPECT_CALL(mock_netlink_, GetLinkInfo("tap0", _))
.WillOnce(
[](const std::string& ifname, NetlinkInterface::LinkInfo* link_info) {
memset(link_info->hardware_address, 0x12, ETH_ALEN);
return true;
});
// Expect neighbor solicitation response to be written out.
EXPECT_CALL(mock_kernel_, writev(kWriteFd, _, 2))
.WillOnce([](int fd, const struct iovec* iov, int iovcnt) -> ssize_t {
return iov[0].iov_len + iov[1].iov_len;
});
EXPECT_CALL(mock_stats_, OnPacketWritten(_, _)).Times(1);
EXPECT_CALL(mock_visitor_, OnWrite(_));
// ReadAndDeliverPacket should return false because packet was handled
// internally (Neighbor Discovery).
EXPECT_FALSE(exchanger_.ReadAndDeliverPacket(&mock_client_));
}
} // namespace
} // namespace quic::test