QBONE Bonnet: pull netlink usage out of packet exchanger We had organically evolved to a point where every queue silo needed its own netlink to hand to a packet exchanger to craft `ethhdr`s, even though the header was uniform and constant. Instead, in TAP mode, `TunTapDevice` builds its own eth_hdr via `ioctl(SIOCGIFHWADDR)` once the device is opened, which is passed as a parameter to any override or wrapper of `QboneClientPacketExchanger::Start` Additionally move the eBPF loader creation up one level so that `QboneTunnel` can be agnostic as to whether the underlying device is TAP vs. TUN PiperOrigin-RevId: 1003988200
diff --git a/quiche/quic/qbone/bonnet/async_write_packet_exchanger.cc b/quiche/quic/qbone/bonnet/async_write_packet_exchanger.cc index 5fdabd0..4cc546f 100644 --- a/quiche/quic/qbone/bonnet/async_write_packet_exchanger.cc +++ b/quiche/quic/qbone/bonnet/async_write_packet_exchanger.cc
@@ -7,6 +7,7 @@ #include <cstddef> #include <cstdint> #include <memory> +#include <optional> #include <queue> #include <thread> // NOLINT (for open-sourceable thread ID) #include <utility> @@ -22,8 +23,8 @@ #include "absl/types/span.h" #include "quiche/quic/platform/api/quic_thread.h" #include "quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h" +#include "quiche/quic/qbone/bonnet/tun_device_packet_exchanger.h" #include "quiche/quic/qbone/platform/kernel_interface.h" -#include "quiche/quic/qbone/platform/netlink_interface.h" #include "quiche/common/platform/api/quiche_bug_tracker.h" #include "quiche/common/platform/api/quiche_logging.h" @@ -232,8 +233,7 @@ AsyncWritePacketExchanger::AsyncWritePacketExchanger( size_t mtu, KernelInterface* absl_nonnull kernel, - NetlinkInterface* absl_nonnull netlink, Visitor* absl_nonnull visitor, - bool is_tap, absl::string_view ifname, int64_t max_buffer_size_bytes, + Visitor* absl_nonnull visitor, int64_t max_buffer_size_bytes, int64_t max_result_buffer_size_bytes, absl_nonnull std::unique_ptr<SiloExecutor> silo_executor) : max_buffer_size_bytes_(max_buffer_size_bytes), @@ -241,8 +241,7 @@ silo_executor_(std::move(silo_executor)), visitor_(*visitor), intermediate_visitor_(std::make_unique<IntermediateVisitor>(this)), - thread_local_exchanger_(mtu, kernel, netlink, intermediate_visitor_.get(), - is_tap, ifname) {} + thread_local_exchanger_(mtu, kernel, intermediate_visitor_.get()) {} AsyncWritePacketExchanger::~AsyncWritePacketExchanger() { QUICHE_DCHECK(silo_executor_->IsOnSiloThread()); @@ -255,14 +254,15 @@ void AsyncWritePacketExchanger::Start( int read_fd, int write_fd, - QboneClientPacketExchanger* absl_nullable exchanger) { + QboneClientPacketExchanger* absl_nullable exchanger, + std::optional<ethhdr> eth_hdr) { QUICHE_DCHECK(silo_executor_->IsOnSiloThread()); if (exchanger == nullptr) { exchanger = this; } - thread_local_exchanger_.Start(read_fd, write_fd, exchanger); + thread_local_exchanger_.Start(read_fd, write_fd, exchanger, eth_hdr); // Allow idempotent Start() calls (assuming `thread_local_exchanger_` // validates same inputs).
diff --git a/quiche/quic/qbone/bonnet/async_write_packet_exchanger.h b/quiche/quic/qbone/bonnet/async_write_packet_exchanger.h index bb69645..49e50b4 100644 --- a/quiche/quic/qbone/bonnet/async_write_packet_exchanger.h +++ b/quiche/quic/qbone/bonnet/async_write_packet_exchanger.h
@@ -5,9 +5,12 @@ #ifndef QUICHE_QUIC_QBONE_BONNET_ASYNC_WRITE_PACKET_EXCHANGER_H_ #define QUICHE_QUIC_QBONE_BONNET_ASYNC_WRITE_PACKET_EXCHANGER_H_ +#include <linux/if_ether.h> + #include <cstddef> #include <cstdint> #include <memory> +#include <optional> #include <queue> #include <vector> @@ -15,14 +18,12 @@ #include "absl/base/nullability.h" #include "absl/base/thread_annotations.h" #include "absl/status/statusor.h" -#include "absl/strings/string_view.h" #include "absl/synchronization/mutex.h" #include "absl/time/time.h" #include "absl/types/span.h" #include "quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h" #include "quiche/quic/qbone/bonnet/tun_device_packet_exchanger.h" #include "quiche/quic/qbone/platform/kernel_interface.h" -#include "quiche/quic/qbone/platform/netlink_interface.h" #include "quiche/common/quiche_callbacks.h" namespace quic { @@ -55,16 +56,16 @@ AsyncWritePacketExchanger( size_t mtu, KernelInterface* absl_nonnull kernel ABSL_ATTRIBUTE_LIFETIME_BOUND, - NetlinkInterface* absl_nonnull netlink ABSL_ATTRIBUTE_LIFETIME_BOUND, - Visitor* absl_nonnull visitor ABSL_ATTRIBUTE_LIFETIME_BOUND, bool is_tap, - absl::string_view ifname, int64_t max_buffer_size_bytes, - int64_t max_result_buffer_size_bytes, + Visitor* absl_nonnull visitor ABSL_ATTRIBUTE_LIFETIME_BOUND, + int64_t max_buffer_size_bytes, int64_t max_result_buffer_size_bytes, absl_nonnull std::unique_ptr<SiloExecutor> silo_executor); + ~AsyncWritePacketExchanger() override; // QboneClientPacketExchanger: void Start(int read_fd, int write_fd, - QboneClientPacketExchanger* absl_nullable exchanger) override; + QboneClientPacketExchanger* absl_nullable exchanger, + std::optional<ethhdr> eth_hdr = std::nullopt) override; void Stop() override; int OnReadFromNetworkReady(int max_packets_to_read) override; void WritePacketToNetwork(absl::Span<const std::byte> packet) override;
diff --git a/quiche/quic/qbone/bonnet/async_write_packet_exchanger_test.cc b/quiche/quic/qbone/bonnet/async_write_packet_exchanger_test.cc index d1cb113..3f42eac 100644 --- a/quiche/quic/qbone/bonnet/async_write_packet_exchanger_test.cc +++ b/quiche/quic/qbone/bonnet/async_write_packet_exchanger_test.cc
@@ -26,8 +26,8 @@ #include "quiche/quic/platform/api/quic_test.h" #include "quiche/quic/qbone/bonnet/mock_qbone_client_packet_exchanger.h" #include "quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h" +#include "quiche/quic/qbone/bonnet/tun_device.h" #include "quiche/quic/qbone/platform/mock_kernel.h" -#include "quiche/quic/qbone/platform/mock_netlink.h" #include "quiche/quic/qbone/qbone_constants.h" #include "quiche/common/platform/api/quiche_logging.h" #include "quiche/common/quiche_callbacks.h" @@ -41,6 +41,7 @@ constexpr int kWriteFd = 16; constexpr int64_t kMaxBufferSizeBytes = 1024 * 1024; constexpr int64_t kMaxResultsBufferSizeBytes = kMaxBufferSizeBytes; +constexpr uint8_t kTestMac[ETH_ALEN] = {0x12, 0x12, 0x12, 0x12, 0x12, 0x12}; using ::absl_testing::IsOkAndHolds; using ::absl_testing::StatusIs; @@ -133,9 +134,8 @@ auto executor = std::make_unique<TestExecutor>(); executor_ = executor.get(); exchanger_ = std::make_unique<AsyncWritePacketExchanger>( - kMtu, &mock_kernel_, &mock_netlink_, &mock_visitor_, /*is_tap=*/false, - "ifname", kMaxBufferSizeBytes, kMaxResultsBufferSizeBytes, - std::move(executor)); + kMtu, &mock_kernel_, &mock_visitor_, kMaxBufferSizeBytes, + kMaxResultsBufferSizeBytes, std::move(executor)); } ~AsyncWritePacketExchangerTest() override = default; @@ -151,7 +151,6 @@ const std::thread::id thread_id_ = std::this_thread::get_id(); StrictMock<MockKernel> mock_kernel_; - NiceMock<MockNetlink> mock_netlink_; StrictMock<MockQboneClientPacketExchanger::MockVisitor> mock_visitor_; TestExecutor* executor_; std::unique_ptr<AsyncWritePacketExchanger> exchanger_; @@ -324,8 +323,7 @@ // Create an exchanger with a very small buffer. exchanger_ = std::make_unique<AsyncWritePacketExchanger>( - kMtu, &mock_kernel_, &mock_netlink_, &mock_visitor_, false, - absl::string_view(), /*max_buffer_size_bytes=*/5, + kMtu, &mock_kernel_, &mock_visitor_, /*max_buffer_size_bytes=*/5, kMaxResultsBufferSizeBytes, std::move(executor)); exchanger_->Start(kReadFd, kWriteFd, /*exchanger=*/nullptr); @@ -348,8 +346,7 @@ // Create an exchanger with a very small response buffer. exchanger_ = std::make_unique<AsyncWritePacketExchanger>( - kMtu, &mock_kernel_, &mock_netlink_, &mock_visitor_, false, - absl::string_view(), kMaxBufferSizeBytes, + kMtu, &mock_kernel_, &mock_visitor_, kMaxBufferSizeBytes, /*max_result_buffer_size_bytes=*/5, std::move(executor)); exchanger_->Start(kReadFd, kWriteFd, /*exchanger=*/nullptr); @@ -427,11 +424,11 @@ // Create an exchanger with TAP enabled. exchanger_ = std::make_unique<AsyncWritePacketExchanger>( - kMtu, &mock_kernel_, &mock_netlink_, &mock_visitor_, /*is_tap=*/true, - absl::string_view(), kMaxBufferSizeBytes, kMaxResultsBufferSizeBytes, - std::move(executor)); + kMtu, &mock_kernel_, &mock_visitor_, kMaxBufferSizeBytes, + kMaxResultsBufferSizeBytes, std::move(executor)); - exchanger_->Start(kReadFd, kWriteFd, /*exchanger=*/nullptr); + exchanger_->Start(kReadFd, kWriteFd, /*exchanger=*/nullptr, + CreateTapEthHdr(kTestMac)); ip6_hdr ip_hdr{}; ip_hdr.ip6_vfc = 0x60; // Version 6 @@ -494,12 +491,12 @@ // Create an exchanger with TAP enabled. exchanger_ = std::make_unique<AsyncWritePacketExchanger>( - kMtu, &mock_kernel_, &mock_netlink_, &mock_visitor_, /*is_tap=*/true, - absl::string_view(), kMaxBufferSizeBytes, kMaxResultsBufferSizeBytes, - std::move(executor)); + kMtu, &mock_kernel_, &mock_visitor_, kMaxBufferSizeBytes, + kMaxResultsBufferSizeBytes, std::move(executor)); StrictMock<MockQboneClientPacketExchanger> mock_outer_exchanger; - exchanger_->Start(kReadFd, kWriteFd, &mock_outer_exchanger); + exchanger_->Start(kReadFd, kWriteFd, &mock_outer_exchanger, + CreateTapEthHdr(kTestMac)); ip6_hdr ip_hdr{}; ip_hdr.ip6_vfc = 0x60; // Version 6
diff --git a/quiche/quic/qbone/bonnet/mock_qbone_client_packet_exchanger.h b/quiche/quic/qbone/bonnet/mock_qbone_client_packet_exchanger.h index 5581d7c..233a74c 100644 --- a/quiche/quic/qbone/bonnet/mock_qbone_client_packet_exchanger.h +++ b/quiche/quic/qbone/bonnet/mock_qbone_client_packet_exchanger.h
@@ -6,6 +6,7 @@ #define QUICHE_QUIC_QBONE_MOCK_QBONE_PACKET_EXCHANGER_H_ #include <cstddef> +#include <optional> #include <vector> #include "absl/base/nullability.h" @@ -32,7 +33,8 @@ MOCK_METHOD(void, Start, (int read_fd, int write_fd, - QboneClientPacketExchanger* absl_nullable exchanger), + QboneClientPacketExchanger* absl_nullable exchanger, + std::optional<ethhdr> eth_hdr), (override)); MOCK_METHOD(void, Stop, (), (override)); MOCK_METHOD(int, OnReadFromNetworkReady, (int max_packets_to_read),
diff --git a/quiche/quic/qbone/bonnet/mock_tun_device.h b/quiche/quic/qbone/bonnet/mock_tun_device.h index 5e086ee..c245f9a 100644 --- a/quiche/quic/qbone/bonnet/mock_tun_device.h +++ b/quiche/quic/qbone/bonnet/mock_tun_device.h
@@ -5,6 +5,8 @@ #ifndef QUICHE_QUIC_QBONE_BONNET_MOCK_TUN_DEVICE_H_ #define QUICHE_QUIC_QBONE_BONNET_MOCK_TUN_DEVICE_H_ +#include <optional> + #include "quiche/quic/platform/api/quic_test.h" #include "quiche/quic/qbone/bonnet/tun_device_interface.h" @@ -24,6 +26,8 @@ MOCK_METHOD(int, GetWriteFileDescriptor, (), (const, override)); MOCK_METHOD(int, OpenQueue, (), (override)); + + MOCK_METHOD(std::optional<ethhdr>, eth_hdr, (), (const, override)); }; } // namespace quic
diff --git a/quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h b/quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h index 7763850..fcef539 100644 --- a/quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h +++ b/quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h
@@ -5,14 +5,16 @@ #ifndef QUICHE_QUIC_QBONE_QBONE_PACKET_EXCHANGER_H_ #define QUICHE_QUIC_QBONE_QBONE_PACKET_EXCHANGER_H_ +#include <linux/if_ether.h> + #include <cstddef> +#include <optional> #include <vector> #include "absl/base/nullability.h" #include "absl/status/statusor.h" #include "absl/time/time.h" #include "absl/types/span.h" -#include "quiche/common/quiche_callbacks.h" namespace quic { @@ -45,8 +47,12 @@ // // If `exchanger` is not null, it will be used instead of `this` to handle any // underlying-triggered read/write operations. + // + // In TAP mode, `eth_hdr` provides the Ethernet header to prepend on writes + // and validate on reads. In TUN mode, `eth_hdr` is std::nullopt. virtual void Start(int read_fd, int write_fd, - QboneClientPacketExchanger* absl_nullable exchanger) = 0; + QboneClientPacketExchanger* absl_nullable exchanger, + std::optional<ethhdr> eth_hdr = std::nullopt) = 0; // Uninitializes the exchanger and blocks until all pending read/write // operations (on- or off-thread) are complete. No Visitor callbacks will be
diff --git a/quiche/quic/qbone/bonnet/tun_device.cc b/quiche/quic/qbone/bonnet/tun_device.cc index 64a2c3d..c1427c7 100644 --- a/quiche/quic/qbone/bonnet/tun_device.cc +++ b/quiche/quic/qbone/bonnet/tun_device.cc
@@ -5,13 +5,16 @@ #include "quiche/quic/qbone/bonnet/tun_device.h" #include <fcntl.h> +#include <linux/if_ether.h> #include <linux/if_tun.h> #include <net/if.h> #include <sys/ioctl.h> #include <sys/socket.h> +#include <cstdint> #include <cstring> #include <ios> +#include <optional> #include <string> #include <vector> @@ -19,12 +22,22 @@ #include "quiche/quic/platform/api/quic_bug_tracker.h" #include "quiche/quic/platform/api/quic_logging.h" #include "quiche/quic/qbone/platform/kernel_interface.h" +#include "quiche/common/quiche_endian.h" ABSL_FLAG(std::string, qbone_client_tun_device_path, "/dev/net/tun", "The path to the QBONE client's TUN device."); namespace quic { +ethhdr CreateTapEthHdr(const uint8_t mac[ETH_ALEN]) { + ethhdr hdr{}; + memcpy(&hdr.h_dest, mac, ETH_ALEN); + memcpy(&hdr.h_source, mac, ETH_ALEN); + // Assume ipv6 for now + hdr.h_proto = quiche::QuicheEndian::HostToNet16(ETH_P_IPV6); + return hdr; +} + const int kInvalidFd = -1; TunTapDevice::TunTapDevice(const std::string& interface_name, int mtu, @@ -59,6 +72,20 @@ return false; } + if (is_tap_) { + struct ifreq if_request; + memset(&if_request, 0, sizeof(if_request)); + interface_name_.copy(if_request.ifr_name, IFNAMSIZ); + if (!NetdeviceIoctl(SIOCGIFHWADDR, &if_request)) { + QUIC_LOG(ERROR) << "Failed to get hardware address for TAP device: " + << interface_name_; + CloseDevice(); + return false; + } + eth_hdr_ = CreateTapEthHdr( + reinterpret_cast<const uint8_t*>(if_request.ifr_hwaddr.sa_data)); + } + return true; } @@ -232,6 +259,7 @@ kernel_.close(fd); } file_descriptors_.clear(); + eth_hdr_ = std::nullopt; } } // namespace quic
diff --git a/quiche/quic/qbone/bonnet/tun_device.h b/quiche/quic/qbone/bonnet/tun_device.h index 20ee320..7107f46 100644 --- a/quiche/quic/qbone/bonnet/tun_device.h +++ b/quiche/quic/qbone/bonnet/tun_device.h
@@ -5,9 +5,11 @@ #ifndef QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_H_ #define QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_H_ +#include <linux/if_ether.h> #include <net/if.h> -#include <cstddef> +#include <cstdint> +#include <optional> #include <string> #include <vector> @@ -16,6 +18,9 @@ namespace quic { +// Builds an ethhdr for a TAP device from the given hardware MAC address. +ethhdr CreateTapEthHdr(const uint8_t mac[ETH_ALEN]); + class TunTapDevice : public TunDeviceInterface { public: // This represents a tun device created in the OS kernel, which is a virtual @@ -64,6 +69,8 @@ // Returns the open queue FD on success, or -1 on failure. int OpenQueue() override; + std::optional<ethhdr> eth_hdr() const override { return eth_hdr_; } + static bool CheckFeatures(KernelInterface& kernel, int tun_device_fd); static bool OpenFileDescriptor(KernelInterface& kernel, const std::string& path, ifreq if_request, @@ -88,6 +95,7 @@ KernelInterface& kernel_; std::vector<int> file_descriptors_; + std::optional<ethhdr> eth_hdr_; }; } // namespace quic
diff --git a/quiche/quic/qbone/bonnet/tun_device_interface.h b/quiche/quic/qbone/bonnet/tun_device_interface.h index 57444c6..12e4d69 100644 --- a/quiche/quic/qbone/bonnet/tun_device_interface.h +++ b/quiche/quic/qbone/bonnet/tun_device_interface.h
@@ -5,6 +5,10 @@ #ifndef QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_INTERFACE_H_ #define QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_INTERFACE_H_ +#include <linux/if_ether.h> + +#include <optional> + namespace quic { // An interface with methods for interacting with a TUN device. @@ -34,6 +38,10 @@ // Opens the next sequential queue file descriptor for this multiqueue device. // Returns the open queue FD on success, or -1 on failure. virtual int OpenQueue() = 0; + + // In TAP mode, returns the Ethernet header configured for this device once + // opened. In TUN mode or prior to Init(), returns std::nullopt. + virtual std::optional<ethhdr> eth_hdr() const { return std::nullopt; } }; } // namespace quic
diff --git a/quiche/quic/qbone/bonnet/tun_device_packet_exchanger.cc b/quiche/quic/qbone/bonnet/tun_device_packet_exchanger.cc index 010c5d1..35b0f40 100644 --- a/quiche/quic/qbone/bonnet/tun_device_packet_exchanger.cc +++ b/quiche/quic/qbone/bonnet/tun_device_packet_exchanger.cc
@@ -13,11 +13,11 @@ #include <cstddef> #include <cstring> #include <memory> +#include <optional> #include <string> #include <utility> #include <vector> -#include "absl/base/macros.h" #include "absl/base/nullability.h" #include "absl/status/status.h" #include "absl/status/statusor.h" @@ -32,26 +32,20 @@ #include "quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h" #include "quiche/quic/qbone/platform/icmp_packet.h" #include "quiche/quic/qbone/platform/kernel_interface.h" -#include "quiche/quic/qbone/platform/netlink_interface.h" #include "quiche/quic/qbone/qbone_constants.h" #include "quiche/common/platform/api/quiche_logging.h" #include "quiche/common/quiche_endian.h" namespace quic { -using ::quiche::QuicheEndian; - TunDevicePacketExchanger::TunDevicePacketExchanger( - size_t mtu, KernelInterface* kernel, NetlinkInterface* netlink, - Visitor* absl_nonnull visitor, bool is_tap, absl::string_view ifname) + size_t mtu, KernelInterface* absl_nonnull kernel, + Visitor* absl_nonnull visitor) : kernel_(kernel), - netlink_(netlink), visitor_(*visitor), - ifname_(ifname), // Reading on a TUN device returns a packet at a time. If the packet is // longer than the buffer, it's truncated. - read_buffer_(mtu), - is_tap_(is_tap) {} + read_buffer_(mtu) {} TunDevicePacketExchanger::~TunDevicePacketExchanger() { QUIC_BUG_IF(bonnet_tun_device_packet_exchanger_not_stopped, @@ -60,11 +54,15 @@ void TunDevicePacketExchanger::Start( int read_fd, int write_fd, - QboneClientPacketExchanger* absl_nullable exchanger) { + QboneClientPacketExchanger* absl_nullable exchanger, + std::optional<ethhdr> eth_hdr) { if (exchanger == nullptr) { exchanger = this; } + is_tap_ = eth_hdr.has_value(); + eth_hdr_ = eth_hdr.value_or(ethhdr{}); + // Allow idempotent Start() calls with the same file descriptors, but // otherwise it's a bug to try starting an already started exchanger. QUIC_BUG_IF(qbone_tun_device_packet_exchanger_already_started, @@ -86,6 +84,8 @@ read_fd_ = -1; write_fd_ = -1; exchanger_ = nullptr; + is_tap_ = false; + eth_hdr_ = {}; } int TunDevicePacketExchanger::OnReadFromNetworkReady(int max_packets_to_read) { @@ -123,9 +123,6 @@ return; } - if (is_tap_ && !eth_hdr_initialized_) { - InitializeEthHdr(); - } struct iovec iov[2]; iov[0].iov_base = is_tap_ ? ð_hdr_ : nullptr; iov[0].iov_len = is_tap_ ? ETH_HLEN : 0; @@ -224,28 +221,10 @@ return true; } -void TunDevicePacketExchanger::InitializeEthHdr() { - if (!eth_hdr_initialized_) { - NetlinkInterface::LinkInfo link_info{}; - if (netlink_->GetLinkInfo(ifname_, &link_info)) { - // Set src & dst to my own address - memcpy(ð_hdr_.h_dest, link_info.hardware_address, ETH_ALEN); - memcpy(ð_hdr_.h_source, link_info.hardware_address, ETH_ALEN); - // Assume ipv6 for now - // TODO(b/195113643): Support additional protocols. - eth_hdr_.h_proto = QuicheEndian::HostToNet16(ETH_P_IPV6); - eth_hdr_initialized_ = true; - } else { - QUIC_LOG_EVERY_N_SEC(ERROR, 30) - << "Unable to get link info for: " << ifname_; - } - } -} - TunDevicePacketExchanger::L2ValidationResult TunDevicePacketExchanger::ValidateL2Headers( const ethhdr& eth_header, absl::Span<const std::byte> packet) { - if (eth_header.h_proto != QuicheEndian::HostToNet16(ETH_P_IPV6)) { + if (eth_header.h_proto != quiche::QuicheEndian::HostToNet16(ETH_P_IPV6)) { return L2ValidationResult::kInvalid; } constexpr auto kIp6PrefixLen = sizeof(ip6_hdr); @@ -269,10 +248,6 @@ } if (is_neighbor_solicit) { - // We need the local interface MAC address to respond. - if (!eth_hdr_initialized_) { - InitializeEthHdr(); - } // If we've received a neighbor solicitation, craft an advertisement to // respond with and write it back to the local interface. absl::Span<const std::byte> icmp6_payload = packet.subspan(kIcmp6PrefixLen);
diff --git a/quiche/quic/qbone/bonnet/tun_device_packet_exchanger.h b/quiche/quic/qbone/bonnet/tun_device_packet_exchanger.h index 2c8130d..8a8f5d6 100644 --- a/quiche/quic/qbone/bonnet/tun_device_packet_exchanger.h +++ b/quiche/quic/qbone/bonnet/tun_device_packet_exchanger.h
@@ -8,16 +8,15 @@ #include <linux/if_ether.h> #include <cstddef> -#include <string> +#include <memory> +#include <optional> #include <vector> #include "absl/base/attributes.h" #include "absl/base/nullability.h" -#include "absl/strings/string_view.h" #include "absl/types/span.h" #include "quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h" #include "quiche/quic/qbone/platform/kernel_interface.h" -#include "quiche/quic/qbone/platform/netlink_interface.h" namespace quic { @@ -27,20 +26,16 @@ // the same thread and all reads occur on the same thread. class TunDevicePacketExchanger : public QboneClientPacketExchanger { public: - // |mtu| is the mtu of the TUN device. - // |kernel| is not owned but should out live objects of this class. - // |visitor| is not owned but should out live objects of this class. - TunDevicePacketExchanger(size_t mtu, KernelInterface* kernel, - NetlinkInterface* netlink, + TunDevicePacketExchanger(size_t mtu, KernelInterface* absl_nonnull kernel, QboneClientPacketExchanger::Visitor* absl_nonnull - visitor ABSL_ATTRIBUTE_LIFETIME_BOUND, - bool is_tap, absl::string_view ifname); + visitor ABSL_ATTRIBUTE_LIFETIME_BOUND); ~TunDevicePacketExchanger() override; // QboneClientPacketExchanger: void Start(int read_fd, int write_fd, - QboneClientPacketExchanger* absl_nullable exchanger) override; + QboneClientPacketExchanger* absl_nullable exchanger, + std::optional<ethhdr> eth_hdr = std::nullopt) override; void Stop() override; int OnReadFromNetworkReady(int max_packets_to_read) override; void WritePacketToNetwork(absl::Span<const std::byte> packet) override; @@ -62,20 +57,16 @@ // Returns true if more packets may be available to read. bool ReadAndExchangeSinglePacket(bool exchange_blocked_error); - void InitializeEthHdr(); L2ValidationResult ValidateL2Headers(const ethhdr& eth_header, absl::Span<const std::byte> packet); KernelInterface* kernel_; - NetlinkInterface* netlink_; QboneClientPacketExchanger::Visitor& visitor_; - const std::string ifname_; std::vector<std::byte> read_buffer_; - const bool is_tap_; + bool is_tap_ = false; ethhdr eth_hdr_ = {}; - bool eth_hdr_initialized_ = false; // -1/nullptr before Start() or after Stop(). int read_fd_ = -1;
diff --git a/quiche/quic/qbone/bonnet/tun_device_packet_exchanger_test.cc b/quiche/quic/qbone/bonnet/tun_device_packet_exchanger_test.cc index 34a3f3d..5bc9276 100644 --- a/quiche/quic/qbone/bonnet/tun_device_packet_exchanger_test.cc +++ b/quiche/quic/qbone/bonnet/tun_device_packet_exchanger_test.cc
@@ -21,9 +21,8 @@ #include "quiche/quic/platform/api/quic_test.h" #include "quiche/quic/qbone/bonnet/mock_qbone_client_packet_exchanger.h" #include "quiche/quic/qbone/bonnet/qbone_client_packet_exchanger.h" +#include "quiche/quic/qbone/bonnet/tun_device.h" #include "quiche/quic/qbone/platform/mock_kernel.h" -#include "quiche/quic/qbone/platform/mock_netlink.h" -#include "quiche/quic/qbone/platform/netlink_interface.h" #include "quiche/quic/qbone/qbone_constants.h" #include "quiche/common/quiche_endian.h" @@ -33,6 +32,7 @@ const size_t kMtu = 1000; const int kReadFd = 15; const int kWriteFd = 16; +constexpr uint8_t kTestMac[ETH_ALEN] = {0x12, 0x12, 0x12, 0x12, 0x12, 0x12}; using ::absl_testing::IsOkAndHolds; using ::absl_testing::StatusIs; @@ -49,8 +49,7 @@ class TunDevicePacketExchangerTest : public QuicTest { protected: TunDevicePacketExchangerTest() - : exchanger_(kMtu, &mock_kernel_, nullptr, &mock_visitor_, false, - absl::string_view()) {} + : exchanger_(kMtu, &mock_kernel_, &mock_visitor_) {} ~TunDevicePacketExchangerTest() override = default; @@ -165,25 +164,14 @@ TEST_F(TunDevicePacketExchangerTest, TapWritePacketSuccessful) { StrictMock<MockKernel> mock_kernel; - StrictMock<MockNetlink> mock_netlink; StrictMock<MockQboneClientPacketExchanger::MockVisitor> mock_visitor; - TunDevicePacketExchanger tap_exchanger(kMtu, &mock_kernel, &mock_netlink, - &mock_visitor, /*is_tap=*/true, - "tap0"); - tap_exchanger.Start(kReadFd, kWriteFd, &delegate_exchanger_); + uint8_t mac[ETH_ALEN] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55}; + TunDevicePacketExchanger tap_exchanger(kMtu, &mock_kernel, &mock_visitor); + tap_exchanger.Start(kReadFd, kWriteFd, &delegate_exchanger_, + CreateTapEthHdr(mac)); 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, @@ -383,20 +371,19 @@ class TunDevicePacketExchangerTapTest : public QuicTest { protected: TunDevicePacketExchangerTapTest() - : exchanger_(kMtu, &mock_kernel_, &mock_netlink_, &mock_visitor_, true, - "tap0") {} + : exchanger_(kMtu, &mock_kernel_, &mock_visitor_) {} - ~TunDevicePacketExchangerTapTest() override = default; + ~TunDevicePacketExchangerTapTest() override { exchanger_.Stop(); } MockKernel mock_kernel_; - StrictMock<MockNetlink> mock_netlink_; StrictMock<MockQboneClientPacketExchanger::MockVisitor> mock_visitor_; TunDevicePacketExchanger exchanger_; StrictMock<MockQboneClientPacketExchanger> delegate_exchanger_; }; TEST_F(TunDevicePacketExchangerTapTest, ReadPacketTapSuccess) { - exchanger_.Start(kReadFd, kWriteFd, &delegate_exchanger_); + exchanger_.Start(kReadFd, kWriteFd, &delegate_exchanger_, + CreateTapEthHdr(kTestMac)); ip6_hdr ip_hdr{}; ip_hdr.ip6_vfc = 0x60; // Version 6 @@ -432,7 +419,8 @@ } TEST_F(TunDevicePacketExchangerTapTest, ReadPacketTapInvalidL2) { - exchanger_.Start(kReadFd, kWriteFd, &delegate_exchanger_); + exchanger_.Start(kReadFd, kWriteFd, &delegate_exchanger_, + CreateTapEthHdr(kTestMac)); ethhdr eth_hdr{}; eth_hdr.h_proto = QuicheEndian::HostToNet16(ETH_P_ARP); // Non-IPv6 @@ -450,7 +438,8 @@ } TEST_F(TunDevicePacketExchangerTapTest, ReadPacketTapNeighborSolicitation) { - exchanger_.Start(kReadFd, kWriteFd, &delegate_exchanger_); + exchanger_.Start(kReadFd, kWriteFd, &delegate_exchanger_, + CreateTapEthHdr(kTestMac)); ip6_hdr ip_hdr{}; ip_hdr.ip6_vfc = 0x60; // Version 6 @@ -480,14 +469,6 @@ 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 sent via delegate exchanger. EXPECT_CALL(delegate_exchanger_, WritePacketToNetwork(_)); @@ -503,7 +484,8 @@ // requests. TEST_F(TunDevicePacketExchangerTapTest, ReadPacketTapNeighborSolicitationSameExchanger) { - exchanger_.Start(kReadFd, kWriteFd, /*exchanger=*/nullptr); + exchanger_.Start(kReadFd, kWriteFd, /*exchanger=*/nullptr, + CreateTapEthHdr(kTestMac)); ip6_hdr ip_hdr{}; ip_hdr.ip6_vfc = 0x60; // Version 6 @@ -533,14 +515,6 @@ 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 { @@ -556,7 +530,8 @@ } TEST_F(TunDevicePacketExchangerTapTest, MultiReadInvalidSizeShort) { - exchanger_.Start(kReadFd, kWriteFd, &delegate_exchanger_); + exchanger_.Start(kReadFd, kWriteFd, &delegate_exchanger_, + CreateTapEthHdr(kTestMac)); ip6_hdr ip_hdr{}; ip_hdr.ip6_vfc = 0x60; // Version 6 @@ -598,7 +573,8 @@ } TEST_F(TunDevicePacketExchangerTapTest, MultiReadInvalidL2) { - exchanger_.Start(kReadFd, kWriteFd, &delegate_exchanger_); + exchanger_.Start(kReadFd, kWriteFd, &delegate_exchanger_, + CreateTapEthHdr(kTestMac)); ethhdr invalid_eth_hdr{}; invalid_eth_hdr.h_proto = QuicheEndian::HostToNet16(ETH_P_ARP); // Non-IPv6
diff --git a/quiche/quic/qbone/bonnet/tun_device_test.cc b/quiche/quic/qbone/bonnet/tun_device_test.cc index ccbab2d..4519fb9 100644 --- a/quiche/quic/qbone/bonnet/tun_device_test.cc +++ b/quiche/quic/qbone/bonnet/tun_device_test.cc
@@ -8,6 +8,9 @@ #include <linux/if_tun.h> #include <sys/ioctl.h> +#include <cstdint> +#include <cstring> + #include "quiche/quic/platform/api/quic_test.h" #include "quiche/quic/qbone/platform/mock_kernel.h" @@ -84,6 +87,17 @@ EXPECT_THAT(ifr->ifr_name, StrEq(kDeviceName)); return 0; }); + if (is_tap) { + EXPECT_CALL(mock_kernel_, ioctl(_, SIOCGIFHWADDR, _)) + .Times(AnyNumber()) + .WillRepeatedly([](Unused, Unused, void* argp) { + auto* ifr = reinterpret_cast<struct ifreq*>(argp); + constexpr uint8_t kMac[ETH_ALEN] = {0x00, 0x11, 0x22, + 0x33, 0x44, 0x55}; + std::memcpy(ifr->ifr_hwaddr.sa_data, kMac, ETH_ALEN); + return 0; + }); + } } // Expect that Up() will be called. Force the call to fail when fail == true.