gfe-relnote: Default-initialize QUIC BBRv2 loss event threshold for exiting STARTUP from a flag. Protected by --gfe2_reloadable_flag_quic_default_to_bbr_v2.
PiperOrigin-RevId: 264298542
Change-Id: I304ab19e4820dec51d3f8ef53762a393f6b175fd
diff --git a/quic/qbone/bonnet/icmp_reachable.cc b/quic/qbone/bonnet/icmp_reachable.cc
new file mode 100644
index 0000000..7779a8b
--- /dev/null
+++ b/quic/qbone/bonnet/icmp_reachable.cc
@@ -0,0 +1,212 @@
+// 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 "net/third_party/quiche/src/quic/qbone/bonnet/icmp_reachable.h"
+
+#include <netinet/ip6.h>
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_endian.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_mutex.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/icmp_packet.h"
+
+namespace quic {
+namespace {
+
+constexpr int kEpollFlags = EPOLLIN | EPOLLET;
+constexpr size_t kMtu = 1280;
+
+constexpr size_t kIPv6AddrSize = sizeof(in6_addr);
+
+} // namespace
+
+const char kUnknownSource[] = "UNKNOWN";
+const char kNoSource[] = "N/A";
+
+IcmpReachable::IcmpReachable(QuicIpAddress source,
+ QuicIpAddress destination,
+ absl::Duration timeout,
+ KernelInterface* kernel,
+ QuicEpollServer* epoll_server,
+ StatsInterface* stats)
+ : timeout_(timeout),
+ cb_(this),
+ kernel_(kernel),
+ epoll_server_(epoll_server),
+ stats_(stats),
+ send_fd_(0),
+ recv_fd_(0) {
+ src_.sin6_family = AF_INET6;
+ dst_.sin6_family = AF_INET6;
+
+ memcpy(&src_.sin6_addr, source.ToPackedString().data(), kIPv6AddrSize);
+ memcpy(&dst_.sin6_addr, destination.ToPackedString().data(), kIPv6AddrSize);
+}
+
+IcmpReachable::~IcmpReachable() {
+ if (send_fd_ > 0) {
+ kernel_->close(send_fd_);
+ }
+ if (recv_fd_ > 0) {
+ if (!epoll_server_->ShutdownCalled()) {
+ epoll_server_->UnregisterFD(recv_fd_);
+ }
+
+ kernel_->close(recv_fd_);
+ }
+}
+
+bool IcmpReachable::Init() {
+ send_fd_ = kernel_->socket(PF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_RAW);
+ if (send_fd_ < 0) {
+ QUIC_LOG(ERROR) << "Unable to open socket: " << errno;
+ return false;
+ }
+
+ if (kernel_->bind(send_fd_, reinterpret_cast<struct sockaddr*>(&src_),
+ sizeof(sockaddr_in6)) < 0) {
+ QUIC_LOG(ERROR) << "Unable to bind socket: " << errno;
+ return false;
+ }
+
+ recv_fd_ =
+ kernel_->socket(PF_INET6, SOCK_RAW | SOCK_NONBLOCK, IPPROTO_ICMPV6);
+ if (recv_fd_ < 0) {
+ QUIC_LOG(ERROR) << "Unable to open socket: " << errno;
+ return false;
+ }
+
+ if (kernel_->bind(recv_fd_, reinterpret_cast<struct sockaddr*>(&src_),
+ sizeof(sockaddr_in6)) < 0) {
+ QUIC_LOG(ERROR) << "Unable to bind socket: " << errno;
+ return false;
+ }
+
+ icmp6_filter filter;
+ ICMP6_FILTER_SETBLOCKALL(&filter);
+ ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter);
+ if (kernel_->setsockopt(recv_fd_, SOL_ICMPV6, ICMP6_FILTER, &filter,
+ sizeof(filter)) < 0) {
+ QUIC_LOG(ERROR) << "Unable to set ICMP6 filter.";
+ return false;
+ }
+
+ epoll_server_->RegisterFD(recv_fd_, &cb_, kEpollFlags);
+ epoll_server_->RegisterAlarm(0, this);
+
+ epoll_server_->set_timeout_in_us(50000);
+
+ QuicWriterMutexLock mu(&header_lock_);
+ icmp_header_.icmp6_type = ICMP6_ECHO_REQUEST;
+ icmp_header_.icmp6_code = 0;
+
+ QuicRandom::GetInstance()->RandBytes(&icmp_header_.icmp6_id,
+ sizeof(uint16_t));
+
+ return true;
+}
+
+bool IcmpReachable::OnEvent(int fd) {
+ char buffer[kMtu];
+
+ sockaddr_in6 source_addr{};
+ socklen_t source_addr_len = sizeof(source_addr);
+
+ ssize_t size = kernel_->recvfrom(fd, &buffer, kMtu, 0,
+ reinterpret_cast<sockaddr*>(&source_addr),
+ &source_addr_len);
+
+ if (size < 0) {
+ if (errno != EAGAIN && errno != EWOULDBLOCK) {
+ stats_->OnReadError(errno);
+ }
+ return false;
+ }
+
+ QUIC_VLOG(2) << QuicTextUtils::HexDump(QuicStringPiece(buffer, size));
+
+ auto* header = reinterpret_cast<const icmp6_hdr*>(&buffer);
+ QuicWriterMutexLock mu(&header_lock_);
+ if (header->icmp6_data32[0] != icmp_header_.icmp6_data32[0]) {
+ QUIC_VLOG(2) << "Unexpected response. id: " << header->icmp6_id
+ << " seq: " << header->icmp6_seq
+ << " Expected id: " << icmp_header_.icmp6_id
+ << " seq: " << icmp_header_.icmp6_seq;
+ return true;
+ }
+ end_ = absl::Now();
+ QUIC_VLOG(1) << "Received ping response in "
+ << absl::ToInt64Microseconds(end_ - start_) << "us.";
+
+ string source;
+ QuicIpAddress source_ip;
+ if (!source_ip.FromPackedString(
+ reinterpret_cast<char*>(&source_addr.sin6_addr), sizeof(in6_addr))) {
+ QUIC_LOG(WARNING) << "Unable to parse source address.";
+ source = kUnknownSource;
+ } else {
+ source = source_ip.ToString();
+ }
+ stats_->OnEvent({Status::REACHABLE, end_ - start_, source});
+ return true;
+}
+
+int64 /* allow-non-std-int */ IcmpReachable::OnAlarm() {
+ EpollAlarm::OnAlarm();
+
+ QuicWriterMutexLock mu(&header_lock_);
+
+ if (end_ < start_) {
+ QUIC_VLOG(1) << "Timed out on sequence: " << icmp_header_.icmp6_seq;
+ stats_->OnEvent({Status::UNREACHABLE, absl::ZeroDuration(), kNoSource});
+ }
+
+ icmp_header_.icmp6_seq++;
+ CreateIcmpPacket(src_.sin6_addr, dst_.sin6_addr, icmp_header_, "",
+ [this](QuicStringPiece packet) {
+ QUIC_VLOG(2) << QuicTextUtils::HexDump(packet);
+
+ ssize_t size = kernel_->sendto(
+ send_fd_, packet.data(), packet.size(), 0,
+ reinterpret_cast<struct sockaddr*>(&dst_),
+ sizeof(sockaddr_in6));
+
+ if (size < packet.size()) {
+ stats_->OnWriteError(errno);
+ }
+ start_ = absl::Now();
+ });
+
+ return absl::ToUnixMicros(absl::Now() + timeout_);
+}
+
+QuicStringPiece IcmpReachable::StatusName(IcmpReachable::Status status) {
+ switch (status) {
+ case REACHABLE:
+ return "REACHABLE";
+ case UNREACHABLE:
+ return "UNREACHABLE";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+void IcmpReachable::EpollCallback::OnEvent(int fd, QuicEpollEvent* event) {
+ bool can_read_more = reachable_->OnEvent(fd);
+ if (can_read_more) {
+ event->out_ready_mask |= EPOLLIN;
+ }
+}
+
+void IcmpReachable::EpollCallback::OnShutdown(QuicEpollServer* eps, int fd) {
+ eps->UnregisterFD(fd);
+}
+
+string IcmpReachable::EpollCallback::Name() const {
+ return "ICMP Reachable";
+}
+
+} // namespace quic
diff --git a/quic/qbone/bonnet/icmp_reachable.h b/quic/qbone/bonnet/icmp_reachable.h
new file mode 100644
index 0000000..4b6403e
--- /dev/null
+++ b/quic/qbone/bonnet/icmp_reachable.h
@@ -0,0 +1,139 @@
+// 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.
+
+#ifndef QUICHE_QUIC_QBONE_BONNET_ICMP_REACHABLE_H_
+#define QUICHE_QUIC_QBONE_BONNET_ICMP_REACHABLE_H_
+
+#include <netinet/icmp6.h>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_mutex.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/qbone/bonnet/icmp_reachable_interface.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/kernel_interface.h"
+
+namespace quic {
+
+extern const char kUnknownSource[];
+extern const char kNoSource[];
+
+// IcmpReachable schedules itself with an EpollServer, periodically sending
+// ICMPv6 Echo Requests to the given |destination| on the interface that the
+// given |source| is bound to. Echo Requests are sent once every |timeout|.
+// On Echo Replies, timeouts, and I/O errors, the given |stats| object will
+// be called back with details of the event.
+class IcmpReachable : public IcmpReachableInterface {
+ public:
+ enum Status { REACHABLE, UNREACHABLE };
+
+ struct ReachableEvent {
+ Status status;
+ absl::Duration response_time;
+ string source;
+ };
+
+ class StatsInterface {
+ public:
+ StatsInterface() = default;
+
+ StatsInterface(const StatsInterface&) = delete;
+ StatsInterface& operator=(const StatsInterface&) = delete;
+
+ StatsInterface(StatsInterface&&) = delete;
+ StatsInterface& operator=(StatsInterface&&) = delete;
+
+ virtual ~StatsInterface() = default;
+
+ virtual void OnEvent(ReachableEvent event) = 0;
+
+ virtual void OnReadError(int error) = 0;
+
+ virtual void OnWriteError(int error) = 0;
+ };
+
+ // |source| is the IPv6 address bound to the interface that IcmpReachable will
+ // send Echo Requests on.
+ // |destination| is the IPv6 address of the destination of the Echo Requests.
+ // |timeout| is the duration IcmpReachable will wait between Echo Requests.
+ // If no Echo Response is received by the next Echo Request, it will
+ // be considered a timeout.
+ // |kernel| is not owned, but should outlive this instance.
+ // |epoll_server| is not owned, but should outlive this instance.
+ // IcmpReachable's Init() must be called from within the Epoll
+ // Server's thread.
+ // |stats| is not owned, but should outlive this instance. It will be called
+ // back on Echo Replies, timeouts, and I/O errors.
+ IcmpReachable(QuicIpAddress source,
+ QuicIpAddress destination,
+ absl::Duration timeout,
+ KernelInterface* kernel,
+ QuicEpollServer* epoll_server,
+ StatsInterface* stats);
+
+ ~IcmpReachable() override;
+
+ // Initializes this reachability probe. Must be called from within the
+ // |epoll_server|'s thread.
+ bool Init() LOCKS_EXCLUDED(header_lock_) override;
+
+ int64 /* allow-non-std-int */ OnAlarm() LOCKS_EXCLUDED(header_lock_) override;
+
+ static QuicStringPiece StatusName(Status status);
+
+ private:
+ class EpollCallback : public QuicEpollCallbackInterface {
+ public:
+ explicit EpollCallback(IcmpReachable* reachable) : reachable_(reachable) {}
+
+ EpollCallback(const EpollCallback&) = delete;
+ EpollCallback& operator=(const EpollCallback&) = delete;
+
+ EpollCallback(EpollCallback&&) = delete;
+ EpollCallback& operator=(EpollCallback&&) = delete;
+
+ void OnRegistration(QuicEpollServer* eps,
+ int fd,
+ int event_mask) override{};
+
+ void OnModification(int fd, int event_mask) override{};
+
+ void OnEvent(int fd, QuicEpollEvent* event) override;
+
+ void OnUnregistration(int fd, bool replaced) override{};
+
+ void OnShutdown(QuicEpollServer* eps, int fd) override;
+
+ string Name() const override;
+
+ private:
+ IcmpReachable* reachable_;
+ };
+
+ bool OnEvent(int fd) LOCKS_EXCLUDED(header_lock_);
+
+ const absl::Duration timeout_;
+
+ EpollCallback cb_;
+
+ sockaddr_in6 src_{};
+ sockaddr_in6 dst_{};
+
+ KernelInterface* kernel_;
+ QuicEpollServer* epoll_server_;
+
+ StatsInterface* stats_;
+
+ int send_fd_;
+ int recv_fd_;
+
+ QuicMutex header_lock_;
+ icmp6_hdr icmp_header_ GUARDED_BY(header_lock_){};
+
+ absl::Time start_ = absl::InfinitePast();
+ absl::Time end_ = absl::InfinitePast();
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_BONNET_ICMP_REACHABLE_H_
diff --git a/quic/qbone/bonnet/icmp_reachable_interface.h b/quic/qbone/bonnet/icmp_reachable_interface.h
new file mode 100644
index 0000000..e766a89
--- /dev/null
+++ b/quic/qbone/bonnet/icmp_reachable_interface.h
@@ -0,0 +1,28 @@
+// 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.
+
+#ifndef QUICHE_QUIC_QBONE_BONNET_ICMP_REACHABLE_INTERFACE_H_
+#define QUICHE_QUIC_QBONE_BONNET_ICMP_REACHABLE_INTERFACE_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h"
+
+namespace quic {
+
+class IcmpReachableInterface : public QuicEpollAlarmBase {
+ public:
+ IcmpReachableInterface() = default;
+
+ IcmpReachableInterface(const IcmpReachableInterface&) = delete;
+ IcmpReachableInterface& operator=(const IcmpReachableInterface&) = delete;
+
+ IcmpReachableInterface(IcmpReachableInterface&&) = delete;
+ IcmpReachableInterface& operator=(IcmpReachableInterface&&) = delete;
+
+ // Initializes this reachability probe.
+ virtual bool Init() = 0;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_BONNET_ICMP_REACHABLE_INTERFACE_H_
diff --git a/quic/qbone/bonnet/icmp_reachable_test.cc b/quic/qbone/bonnet/icmp_reachable_test.cc
new file mode 100644
index 0000000..303f0e2
--- /dev/null
+++ b/quic/qbone/bonnet/icmp_reachable_test.cc
@@ -0,0 +1,266 @@
+// 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 "net/third_party/quiche/src/quic/qbone/bonnet/icmp_reachable.h"
+
+#include <netinet/ip6.h>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/mock_kernel.h"
+
+namespace quic {
+namespace {
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrictMock;
+
+constexpr char kSourceAddress[] = "fe80:1:2:3:4::1";
+constexpr char kDestinationAddress[] = "fe80:4:3:2:1::1";
+
+constexpr int kFakeWriteFd = 0;
+
+icmp6_hdr GetHeaderFromPacket(const void* buf, size_t len) {
+ CHECK_GE(len, sizeof(ip6_hdr) + sizeof(icmp6_hdr));
+
+ auto* buffer = reinterpret_cast<const char*>(buf);
+ return *reinterpret_cast<const icmp6_hdr*>(&buffer[sizeof(ip6_hdr)]);
+}
+
+class StatsInterface : public IcmpReachable::StatsInterface {
+ public:
+ void OnEvent(IcmpReachable::ReachableEvent event) override {
+ switch (event.status) {
+ case IcmpReachable::REACHABLE: {
+ reachable_count_++;
+ break;
+ }
+ case IcmpReachable::UNREACHABLE: {
+ unreachable_count_++;
+ break;
+ }
+ }
+ current_source_ = event.source;
+ }
+
+ void OnReadError(int error) override { read_errors_[error]++; }
+
+ void OnWriteError(int error) override { write_errors_[error]++; }
+
+ bool HasWriteErrors() { return !write_errors_.empty(); }
+
+ int WriteErrorCount(int error) { return write_errors_[error]; }
+
+ bool HasReadErrors() { return !read_errors_.empty(); }
+
+ int ReadErrorCount(int error) { return read_errors_[error]; }
+
+ int reachable_count() { return reachable_count_; }
+
+ int unreachable_count() { return unreachable_count_; }
+
+ string current_source() { return current_source_; }
+
+ private:
+ int reachable_count_ = 0;
+ int unreachable_count_ = 0;
+
+ string current_source_{};
+
+ QuicUnorderedMap<int, int> read_errors_;
+ QuicUnorderedMap<int, int> write_errors_;
+};
+
+class IcmpReachableTest : public QuicTest {
+ public:
+ IcmpReachableTest() {
+ CHECK(source_.FromString(kSourceAddress));
+ CHECK(destination_.FromString(kDestinationAddress));
+
+ int pipe_fds[2];
+ CHECK(pipe(pipe_fds) >= 0) << "pipe() failed";
+
+ read_fd_ = pipe_fds[0];
+ read_src_fd_ = pipe_fds[1];
+ }
+
+ void SetFdExpectations() {
+ InSequence seq;
+ EXPECT_CALL(kernel_, socket(_, _, _)).WillOnce(Return(kFakeWriteFd));
+ EXPECT_CALL(kernel_, bind(kFakeWriteFd, _, _)).WillOnce(Return(0));
+
+ EXPECT_CALL(kernel_, socket(_, _, _)).WillOnce(Return(read_fd_));
+ EXPECT_CALL(kernel_, bind(read_fd_, _, _)).WillOnce(Return(0));
+
+ EXPECT_CALL(kernel_, setsockopt(read_fd_, SOL_ICMPV6, ICMP6_FILTER, _, _));
+
+ EXPECT_CALL(kernel_, close(read_fd_)).WillOnce(Invoke([](int fd) {
+ return close(fd);
+ }));
+ }
+
+ protected:
+ QuicIpAddress source_;
+ QuicIpAddress destination_;
+
+ int read_fd_;
+ int read_src_fd_;
+
+ StrictMock<MockKernel> kernel_;
+ QuicEpollServer epoll_server_;
+ StatsInterface stats_;
+};
+
+TEST_F(IcmpReachableTest, SendsPings) {
+ IcmpReachable reachable(source_, destination_, absl::Seconds(0), &kernel_,
+ &epoll_server_, &stats_);
+
+ SetFdExpectations();
+ ASSERT_TRUE(reachable.Init());
+
+ EXPECT_CALL(kernel_, sendto(kFakeWriteFd, _, _, _, _, _))
+ .WillOnce(Invoke([](int sockfd, const void* buf, size_t len, int flags,
+ const struct sockaddr* dest_addr, socklen_t addrlen) {
+ auto icmp_header = GetHeaderFromPacket(buf, len);
+ EXPECT_EQ(icmp_header.icmp6_type, ICMP6_ECHO_REQUEST);
+ EXPECT_EQ(icmp_header.icmp6_seq, 1);
+ return len;
+ }));
+
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ EXPECT_FALSE(stats_.HasWriteErrors());
+
+ epoll_server_.Shutdown();
+}
+
+TEST_F(IcmpReachableTest, HandlesUnreachableEvents) {
+ IcmpReachable reachable(source_, destination_, absl::Seconds(0), &kernel_,
+ &epoll_server_, &stats_);
+
+ SetFdExpectations();
+ ASSERT_TRUE(reachable.Init());
+
+ EXPECT_CALL(kernel_, sendto(kFakeWriteFd, _, _, _, _, _))
+ .Times(2)
+ .WillRepeatedly(Invoke([](int sockfd, const void* buf, size_t len,
+ int flags, const struct sockaddr* dest_addr,
+ socklen_t addrlen) { return len; }));
+
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ EXPECT_EQ(stats_.unreachable_count(), 0);
+
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ EXPECT_FALSE(stats_.HasWriteErrors());
+ EXPECT_EQ(stats_.unreachable_count(), 1);
+ EXPECT_EQ(stats_.current_source(), kNoSource);
+
+ epoll_server_.Shutdown();
+}
+
+TEST_F(IcmpReachableTest, HandlesReachableEvents) {
+ IcmpReachable reachable(source_, destination_, absl::Seconds(0), &kernel_,
+ &epoll_server_, &stats_);
+
+ SetFdExpectations();
+ ASSERT_TRUE(reachable.Init());
+
+ icmp6_hdr last_request_hdr{};
+ EXPECT_CALL(kernel_, sendto(kFakeWriteFd, _, _, _, _, _))
+ .Times(2)
+ .WillRepeatedly(
+ Invoke([&last_request_hdr](
+ int sockfd, const void* buf, size_t len, int flags,
+ const struct sockaddr* dest_addr, socklen_t addrlen) {
+ last_request_hdr = GetHeaderFromPacket(buf, len);
+ return len;
+ }));
+
+ sockaddr_in6 source_addr{};
+ string packed_source = source_.ToPackedString();
+ memcpy(&source_addr.sin6_addr, packed_source.data(), packed_source.size());
+
+ EXPECT_CALL(kernel_, recvfrom(read_fd_, _, _, _, _, _))
+ .WillOnce(
+ Invoke([&source_addr](int sockfd, void* buf, size_t len, int flags,
+ struct sockaddr* src_addr, socklen_t* addrlen) {
+ *reinterpret_cast<sockaddr_in6*>(src_addr) = source_addr;
+ return read(sockfd, buf, len);
+ }));
+
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ EXPECT_EQ(stats_.reachable_count(), 0);
+
+ icmp6_hdr response = last_request_hdr;
+ response.icmp6_type = ICMP6_ECHO_REPLY;
+
+ write(read_src_fd_, reinterpret_cast<const void*>(&response),
+ sizeof(icmp6_hdr));
+
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ EXPECT_FALSE(stats_.HasReadErrors());
+ EXPECT_FALSE(stats_.HasWriteErrors());
+ EXPECT_EQ(stats_.reachable_count(), 1);
+ EXPECT_EQ(stats_.current_source(), source_.ToString());
+
+ epoll_server_.Shutdown();
+}
+
+TEST_F(IcmpReachableTest, HandlesWriteErrors) {
+ IcmpReachable reachable(source_, destination_, absl::Seconds(0), &kernel_,
+ &epoll_server_, &stats_);
+
+ SetFdExpectations();
+ ASSERT_TRUE(reachable.Init());
+
+ EXPECT_CALL(kernel_, sendto(kFakeWriteFd, _, _, _, _, _))
+ .WillOnce(Invoke([](int sockfd, const void* buf, size_t len, int flags,
+ const struct sockaddr* dest_addr, socklen_t addrlen) {
+ errno = EAGAIN;
+ return 0;
+ }));
+
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ EXPECT_EQ(stats_.WriteErrorCount(EAGAIN), 1);
+
+ epoll_server_.Shutdown();
+}
+
+TEST_F(IcmpReachableTest, HandlesReadErrors) {
+ IcmpReachable reachable(source_, destination_, absl::Seconds(0), &kernel_,
+ &epoll_server_, &stats_);
+
+ SetFdExpectations();
+ ASSERT_TRUE(reachable.Init());
+
+ EXPECT_CALL(kernel_, sendto(kFakeWriteFd, _, _, _, _, _))
+ .WillOnce(Invoke([](int sockfd, const void* buf, size_t len, int flags,
+ const struct sockaddr* dest_addr,
+ socklen_t addrlen) { return len; }));
+
+ EXPECT_CALL(kernel_, recvfrom(read_fd_, _, _, _, _, _))
+ .WillOnce(Invoke([](int sockfd, void* buf, size_t len, int flags,
+ struct sockaddr* src_addr, socklen_t* addrlen) {
+ errno = EIO;
+ return -1;
+ }));
+
+ icmp6_hdr response{};
+
+ write(read_src_fd_, reinterpret_cast<const void*>(&response),
+ sizeof(icmp6_hdr));
+
+ epoll_server_.WaitForEventsAndExecuteCallbacks();
+ EXPECT_EQ(stats_.reachable_count(), 0);
+ EXPECT_EQ(stats_.ReadErrorCount(EIO), 1);
+
+ epoll_server_.Shutdown();
+}
+
+} // namespace
+} // namespace quic
diff --git a/quic/qbone/bonnet/mock_icmp_reachable.h b/quic/qbone/bonnet/mock_icmp_reachable.h
new file mode 100644
index 0000000..092845e
--- /dev/null
+++ b/quic/qbone/bonnet/mock_icmp_reachable.h
@@ -0,0 +1,20 @@
+// 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.
+
+#ifndef QUICHE_QUIC_QBONE_BONNET_MOCK_ICMP_REACHABLE_H_
+#define QUICHE_QUIC_QBONE_BONNET_MOCK_ICMP_REACHABLE_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/bonnet/icmp_reachable_interface.h"
+
+namespace quic {
+
+class MockIcmpReachable : public IcmpReachableInterface {
+ public:
+ MOCK_METHOD0(Init, bool());
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_BONNET_MOCK_ICMP_REACHABLE_H_
diff --git a/quic/qbone/bonnet/mock_tun_device.h b/quic/qbone/bonnet/mock_tun_device.h
new file mode 100644
index 0000000..37e852a
--- /dev/null
+++ b/quic/qbone/bonnet/mock_tun_device.h
@@ -0,0 +1,26 @@
+// 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.
+
+#ifndef QUICHE_QUIC_QBONE_BONNET_MOCK_TUN_DEVICE_H_
+#define QUICHE_QUIC_QBONE_BONNET_MOCK_TUN_DEVICE_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/bonnet/tun_device_interface.h"
+
+namespace quic {
+
+class MockTunDevice : public TunDeviceInterface {
+ public:
+ MOCK_METHOD0(Init, bool());
+
+ MOCK_METHOD0(Up, bool());
+
+ MOCK_METHOD0(Down, bool());
+
+ MOCK_CONST_METHOD0(GetFileDescriptor, int());
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_BONNET_MOCK_TUN_DEVICE_H_
diff --git a/quic/qbone/bonnet/tun_device.cc b/quic/qbone/bonnet/tun_device.cc
new file mode 100644
index 0000000..6c0a8a5
--- /dev/null
+++ b/quic/qbone/bonnet/tun_device.cc
@@ -0,0 +1,201 @@
+// 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 "net/third_party/quiche/src/quic/qbone/bonnet/tun_device.h"
+
+#include <fcntl.h>
+#include <linux/if_tun.h>
+#include <net/if.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/kernel_interface.h"
+
+namespace quic {
+
+const char kTapTunDevicePath[] = "/dev/net/tun";
+const int kInvalidFd = -1;
+
+TunDevice::TunDevice(const string& interface_name,
+ int mtu,
+ bool persist,
+ KernelInterface* kernel)
+ : interface_name_(interface_name),
+ mtu_(mtu),
+ persist_(persist),
+ file_descriptor_(kInvalidFd),
+ kernel_(*kernel) {}
+
+TunDevice::~TunDevice() {
+ Down();
+ CleanUpFileDescriptor();
+}
+
+bool TunDevice::Init() {
+ if (interface_name_.empty() || interface_name_.size() >= IFNAMSIZ) {
+ QUIC_BUG << "interface_name must be nonempty and shorter than " << IFNAMSIZ;
+ return false;
+ }
+
+ if (!OpenDevice()) {
+ return false;
+ }
+
+ if (!ConfigureInterface()) {
+ return false;
+ }
+
+ return true;
+}
+
+// TODO(pengg): might be better to use netlink socket, once we have a library to
+// use
+bool TunDevice::Up() {
+ if (!is_interface_up_) {
+ struct ifreq if_request;
+ memset(&if_request, 0, sizeof(if_request));
+ // copy does not zero-terminate the result string, but we've memset the
+ // entire struct.
+ interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
+ if_request.ifr_flags = IFF_UP;
+
+ is_interface_up_ =
+ NetdeviceIoctl(SIOCSIFFLAGS, reinterpret_cast<void*>(&if_request));
+ return is_interface_up_;
+ } else {
+ return true;
+ }
+}
+
+// TODO(pengg): might be better to use netlink socket, once we have a library to
+// use
+bool TunDevice::Down() {
+ if (is_interface_up_) {
+ struct ifreq if_request;
+ memset(&if_request, 0, sizeof(if_request));
+ // copy does not zero-terminate the result string, but we've memset the
+ // entire struct.
+ interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
+ if_request.ifr_flags = 0;
+
+ is_interface_up_ =
+ !NetdeviceIoctl(SIOCSIFFLAGS, reinterpret_cast<void*>(&if_request));
+ return !is_interface_up_;
+ } else {
+ return true;
+ }
+}
+
+int TunDevice::GetFileDescriptor() const {
+ return file_descriptor_;
+}
+
+bool TunDevice::OpenDevice() {
+ struct ifreq if_request;
+ memset(&if_request, 0, sizeof(if_request));
+ // copy does not zero-terminate the result string, but we've memset the entire
+ // struct.
+ interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
+
+ // Always set IFF_MULTI_QUEUE since a persistent device does not allow this
+ // flag to be flipped when re-opening it. The only way to flip this flag is to
+ // destroy the device and create a new one, but that deletes any existing
+ // routing associated with the interface, which makes the meaning of the
+ // 'persist' bit ambiguous.
+ if_request.ifr_flags = IFF_TUN | IFF_MULTI_QUEUE | IFF_NO_PI;
+
+ // TODO(pengg): port MakeCleanup to quic/platform? This makes the call to
+ // CleanUpFileDescriptor nicer and less error-prone.
+ // When the device is running with IFF_MULTI_QUEUE set, each call to open will
+ // create a queue which can be used to read/write packets from/to the device.
+ int fd = kernel_.open(kTapTunDevicePath, O_RDWR);
+ if (fd < 0) {
+ QUIC_PLOG(WARNING) << "Failed to open " << kTapTunDevicePath;
+ CleanUpFileDescriptor();
+ return false;
+ }
+ file_descriptor_ = fd;
+ if (!CheckFeatures(fd)) {
+ CleanUpFileDescriptor();
+ return false;
+ }
+
+ if (kernel_.ioctl(fd, TUNSETIFF, reinterpret_cast<void*>(&if_request)) != 0) {
+ QUIC_PLOG(WARNING) << "Failed to TUNSETIFF on fd(" << fd << ")";
+ CleanUpFileDescriptor();
+ return false;
+ }
+
+ if (kernel_.ioctl(
+ fd, TUNSETPERSIST,
+ persist_ ? reinterpret_cast<void*>(&if_request) : nullptr) != 0) {
+ QUIC_PLOG(WARNING) << "Failed to TUNSETPERSIST on fd(" << fd << ")";
+ CleanUpFileDescriptor();
+ return false;
+ }
+
+ return true;
+}
+
+// TODO(pengg): might be better to use netlink socket, once we have a library to
+// use
+bool TunDevice::ConfigureInterface() {
+ struct ifreq if_request;
+ memset(&if_request, 0, sizeof(if_request));
+ // copy does not zero-terminate the result string, but we've memset the entire
+ // struct.
+ interface_name_.copy(if_request.ifr_name, IFNAMSIZ);
+ if_request.ifr_mtu = mtu_;
+
+ if (!NetdeviceIoctl(SIOCSIFMTU, reinterpret_cast<void*>(&if_request))) {
+ CleanUpFileDescriptor();
+ return false;
+ }
+
+ return true;
+}
+
+bool TunDevice::CheckFeatures(int tun_device_fd) {
+ unsigned int actual_features;
+ if (kernel_.ioctl(tun_device_fd, TUNGETFEATURES, &actual_features) != 0) {
+ QUIC_PLOG(WARNING) << "Failed to TUNGETFEATURES";
+ return false;
+ }
+ unsigned int required_features = IFF_TUN | IFF_NO_PI;
+ if ((required_features & actual_features) != required_features) {
+ QUIC_LOG(WARNING)
+ << "Required feature does not exist. required_features: 0x" << std::hex
+ << required_features << " vs actual_features: 0x" << std::hex
+ << actual_features;
+ return false;
+ }
+ return true;
+}
+
+bool TunDevice::NetdeviceIoctl(int request, void* argp) {
+ int fd = kernel_.socket(AF_INET6, SOCK_DGRAM, 0);
+ if (fd < 0) {
+ QUIC_PLOG(WARNING) << "Failed to create AF_INET6 socket.";
+ return false;
+ }
+
+ if (kernel_.ioctl(fd, request, argp) != 0) {
+ QUIC_PLOG(WARNING) << "Failed ioctl request: " << request;
+ kernel_.close(fd);
+ return false;
+ }
+ kernel_.close(fd);
+ return true;
+}
+
+void TunDevice::CleanUpFileDescriptor() {
+ if (file_descriptor_ != kInvalidFd) {
+ kernel_.close(file_descriptor_);
+ file_descriptor_ = kInvalidFd;
+ }
+}
+
+} // namespace quic
diff --git a/quic/qbone/bonnet/tun_device.h b/quic/qbone/bonnet/tun_device.h
new file mode 100644
index 0000000..1828b81
--- /dev/null
+++ b/quic/qbone/bonnet/tun_device.h
@@ -0,0 +1,82 @@
+// 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.
+
+#ifndef QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_H_
+#define QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_H_
+
+#include <string>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/qbone/bonnet/tun_device_interface.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/kernel_interface.h"
+
+namespace quic {
+
+class TunDevice : public TunDeviceInterface {
+ public:
+ // This represents a tun device created in the OS kernel, which is a virtual
+ // network interface that any packets sent to it can be read by a user space
+ // program that owns it. The routing rule that routes packets to this
+ // interface should be defined somewhere else.
+ //
+ // Standard read/write system calls can be used to receive/send packets
+ // from/to this interface. The file descriptor is owned by this class.
+ //
+ // If persist is set to true, the device won't be deleted even after
+ // destructing. The device will be picked up when initializing this class with
+ // the same interface_name on the next time.
+ //
+ // Persisting the device is useful if one wants to keep the routing rules
+ // since once a tun device is destroyed by the kernel, all the associated
+ // routing rules go away.
+ //
+ // The caller should own kernel and make sure it outlives this.
+ TunDevice(const string& interface_name,
+ int mtu,
+ bool persist,
+ KernelInterface* kernel);
+
+ ~TunDevice() override;
+
+ // Actually creates/reopens and configures the device.
+ bool Init() override;
+
+ // Marks the interface up to start receiving packets.
+ bool Up() override;
+
+ // Marks the interface down to stop receiving packets.
+ bool Down() override;
+
+ // Gets the file descriptor that can be used to send/receive packets.
+ // This returns -1 when the TUN device is in an invalid state.
+ int GetFileDescriptor() const override;
+
+ private:
+ // Creates or reopens the tun device.
+ bool OpenDevice();
+
+ // Configure the interface.
+ bool ConfigureInterface();
+
+ // Checks if the required kernel features exists.
+ bool CheckFeatures(int tun_device_fd);
+
+ // Closes the opened file descriptor and makes sure the file descriptor
+ // is no longer available from GetFileDescriptor;
+ void CleanUpFileDescriptor();
+
+ // Opens a socket and makes netdevice ioctl call
+ bool NetdeviceIoctl(int request, void* argp);
+
+ const string interface_name_;
+ const int mtu_;
+ const bool persist_;
+ int file_descriptor_;
+ KernelInterface& kernel_;
+ bool is_interface_up_ = false;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_H_
diff --git a/quic/qbone/bonnet/tun_device_interface.h b/quic/qbone/bonnet/tun_device_interface.h
new file mode 100644
index 0000000..e99c547
--- /dev/null
+++ b/quic/qbone/bonnet/tun_device_interface.h
@@ -0,0 +1,33 @@
+// 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.
+
+#ifndef QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_INTERFACE_H_
+#define QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_INTERFACE_H_
+
+#include <vector>
+
+namespace quic {
+
+// An interface with methods for interacting with a TUN device.
+class TunDeviceInterface {
+ public:
+ virtual ~TunDeviceInterface() {}
+
+ // Actually creates/reopens and configures the device.
+ virtual bool Init() = 0;
+
+ // Marks the interface up to start receiving packets.
+ virtual bool Up() = 0;
+
+ // Marks the interface down to stop receiving packets.
+ virtual bool Down() = 0;
+
+ // Gets the file descriptor that can be used to send/receive packets.
+ // This returns -1 when the TUN device is in an invalid state.
+ virtual int GetFileDescriptor() const = 0;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_INTERFACE_H_
diff --git a/quic/qbone/bonnet/tun_device_packet_exchanger.cc b/quic/qbone/bonnet/tun_device_packet_exchanger.cc
new file mode 100644
index 0000000..1d246a2
--- /dev/null
+++ b/quic/qbone/bonnet/tun_device_packet_exchanger.cc
@@ -0,0 +1,82 @@
+// 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 "net/third_party/quiche/src/quic/qbone/bonnet/tun_device_packet_exchanger.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
+
+namespace quic {
+
+TunDevicePacketExchanger::TunDevicePacketExchanger(
+ int fd,
+ size_t mtu,
+ KernelInterface* kernel,
+ QbonePacketExchanger::Visitor* visitor,
+ size_t max_pending_packets, StatsInterface* stats)
+ : QbonePacketExchanger(visitor, max_pending_packets),
+ fd_(fd),
+ mtu_(mtu),
+ kernel_(kernel),
+ stats_(stats) {}
+
+bool TunDevicePacketExchanger::WritePacket(const char* packet,
+ size_t size,
+ bool* blocked,
+ string* error) {
+ *blocked = false;
+ if (fd_ < 0) {
+ *error = QuicStrCat("Invalid file descriptor of the TUN device: ", fd_);
+ stats_->OnWriteError(error);
+ return false;
+ }
+
+ int result = kernel_->write(fd_, packet, size);
+ if (result == -1) {
+ if (errno == EWOULDBLOCK || errno == EAGAIN) {
+ // The tunnel is blocked. Note that this does not mean the receive buffer
+ // of a TCP connection is filled. This simply means the TUN device itself
+ // is blocked on handing packets to the rest part of the kernel.
+ *error = QuicStrCat("Write to the TUN device was blocked: ", errno);
+ *blocked = true;
+ stats_->OnWriteError(error);
+ }
+ return false;
+ }
+ stats_->OnPacketWritten();
+
+ return true;
+}
+
+std::unique_ptr<QuicData> TunDevicePacketExchanger::ReadPacket(bool* blocked,
+ string* error) {
+ *blocked = false;
+ if (fd_ < 0) {
+ *error = QuicStrCat("Invalid file descriptor of the TUN device: ", fd_);
+ stats_->OnReadError(error);
+ return nullptr;
+ }
+ // Reading on a TUN device returns a packet at a time. If the packet is longer
+ // than the buffer, it's truncated.
+ auto read_buffer = QuicMakeUnique<char[]>(mtu_);
+ int result = kernel_->read(fd_, read_buffer.get(), mtu_);
+ // Note that 0 means end of file, but we're talking about a TUN device - there
+ // is no end of file. Therefore 0 also indicates error.
+ if (result <= 0) {
+ if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ *error = QuicStrCat("Read from the TUN device was blocked: ", errno);
+ *blocked = true;
+ stats_->OnReadError(error);
+ }
+ return nullptr;
+ }
+ stats_->OnPacketRead();
+ return QuicMakeUnique<QuicData>(read_buffer.release(), result, true);
+}
+
+int TunDevicePacketExchanger::file_descriptor() const {
+ return fd_;
+}
+
+} // namespace quic
diff --git a/quic/qbone/bonnet/tun_device_packet_exchanger.h b/quic/qbone/bonnet/tun_device_packet_exchanger.h
new file mode 100644
index 0000000..12d9efa
--- /dev/null
+++ b/quic/qbone/bonnet/tun_device_packet_exchanger.h
@@ -0,0 +1,72 @@
+// 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.
+
+#ifndef QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_PACKET_EXCHANGER_H_
+#define QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_PACKET_EXCHANGER_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/kernel_interface.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_client_interface.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_exchanger.h"
+
+namespace quic {
+
+class TunDevicePacketExchanger : public QbonePacketExchanger {
+ public:
+ class StatsInterface {
+ public:
+ StatsInterface() = default;
+
+ StatsInterface(const StatsInterface&) = delete;
+ StatsInterface& operator=(const StatsInterface&) = delete;
+
+ StatsInterface(StatsInterface&&) = delete;
+ StatsInterface& operator=(StatsInterface&&) = delete;
+
+ virtual ~StatsInterface() = default;
+
+ virtual void OnPacketRead() = 0;
+ virtual void OnPacketWritten() = 0;
+ virtual void OnReadError(string* error) = 0;
+ virtual void OnWriteError(string* error) = 0;
+ };
+
+ // |fd| is a open file descriptor on a TUN device that's opened for both read
+ // and write.
+ // |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.
+ // |max_pending_packets| controls the number of packets to be queued should
+ // the TUN device become blocked.
+ // |stats| is notified about packet read/write statistics. It is not owned,
+ // but should outlive objects of this class.
+ TunDevicePacketExchanger(int fd,
+ size_t mtu,
+ KernelInterface* kernel,
+ QbonePacketExchanger::Visitor* visitor,
+ size_t max_pending_packets,
+ StatsInterface* stats);
+
+ int file_descriptor() const;
+
+ private:
+ // From QbonePacketExchanger.
+ std::unique_ptr<QuicData> ReadPacket(bool* blocked, string* error) override;
+
+ // From QbonePacketExchanger.
+ bool WritePacket(const char* packet,
+ size_t size,
+ bool* blocked,
+ string* error) override;
+
+ int fd_ = -1;
+ size_t mtu_;
+ KernelInterface* kernel_;
+
+ StatsInterface* stats_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_BONNET_TUN_DEVICE_PACKET_EXCHANGER_H_
diff --git a/quic/qbone/bonnet/tun_device_packet_exchanger_test.cc b/quic/qbone/bonnet/tun_device_packet_exchanger_test.cc
new file mode 100644
index 0000000..026ec26
--- /dev/null
+++ b/quic/qbone/bonnet/tun_device_packet_exchanger_test.cc
@@ -0,0 +1,128 @@
+// 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 "net/third_party/quiche/src/quic/qbone/bonnet/tun_device_packet_exchanger.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/mock_qbone_client.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/mock_kernel.h"
+
+namespace quic {
+namespace {
+
+const size_t kMtu = 1000;
+const size_t kMaxPendingPackets = 5;
+const int kFd = 15;
+
+using ::testing::_;
+using ::testing::Invoke;
+using ::testing::StrEq;
+using ::testing::StrictMock;
+
+class MockVisitor : public QbonePacketExchanger::Visitor {
+ public:
+ MOCK_METHOD1(OnReadError, void(const string&));
+ MOCK_METHOD1(OnWriteError, void(const string&));
+};
+
+class MockStatsInterface : public TunDevicePacketExchanger::StatsInterface {
+ public:
+ MOCK_METHOD0(OnPacketRead, void());
+ MOCK_METHOD0(OnPacketWritten, void());
+
+ MOCK_METHOD1(OnReadError, void(string*));
+ MOCK_METHOD1(OnWriteError, void(string*));
+};
+
+class TunDevicePacketExchangerTest : public QuicTest {
+ protected:
+ TunDevicePacketExchangerTest()
+ : exchanger_(kFd,
+ kMtu,
+ &mock_kernel_,
+ &mock_visitor_,
+ kMaxPendingPackets,
+ &mock_stats_) {}
+
+ ~TunDevicePacketExchangerTest() override {}
+
+ MockKernel mock_kernel_;
+ StrictMock<MockVisitor> mock_visitor_;
+ StrictMock<MockQboneClient> mock_client_;
+ StrictMock<MockStatsInterface> mock_stats_;
+ TunDevicePacketExchanger exchanger_;
+};
+
+TEST_F(TunDevicePacketExchangerTest, WritePacketReturnsFalseOnError) {
+ string packet = "fake packet";
+ EXPECT_CALL(mock_kernel_, write(kFd, _, packet.size()))
+ .WillOnce(Invoke([](int fd, const void* buf, size_t count) {
+ errno = ECOMM;
+ return -1;
+ }));
+
+ EXPECT_CALL(mock_visitor_, OnWriteError(_));
+ exchanger_.WritePacketToNetwork(packet.data(), packet.size());
+}
+
+TEST_F(TunDevicePacketExchangerTest,
+ WritePacketReturnFalseAndBlockedOnBlockedTunnel) {
+ string packet = "fake packet";
+ EXPECT_CALL(mock_kernel_, write(kFd, _, packet.size()))
+ .WillOnce(Invoke([](int fd, const void* buf, size_t count) {
+ errno = EAGAIN;
+ return -1;
+ }));
+
+ EXPECT_CALL(mock_stats_, OnWriteError(_)).Times(1);
+ exchanger_.WritePacketToNetwork(packet.data(), packet.size());
+}
+
+TEST_F(TunDevicePacketExchangerTest, WritePacketReturnsTrueOnSuccessfulWrite) {
+ string packet = "fake packet";
+ EXPECT_CALL(mock_kernel_, write(kFd, _, packet.size()))
+ .WillOnce(Invoke([packet](int fd, const void* buf, size_t count) {
+ EXPECT_THAT(reinterpret_cast<const char*>(buf), StrEq(packet));
+ return count;
+ }));
+
+ EXPECT_CALL(mock_stats_, OnPacketWritten()).Times(1);
+ exchanger_.WritePacketToNetwork(packet.data(), packet.size());
+}
+
+TEST_F(TunDevicePacketExchangerTest, ReadPacketReturnsNullOnError) {
+ EXPECT_CALL(mock_kernel_, read(kFd, _, kMtu))
+ .WillOnce(Invoke([](int fd, void* buf, size_t count) {
+ errno = ECOMM;
+ return -1;
+ }));
+ EXPECT_CALL(mock_visitor_, OnReadError(_));
+ exchanger_.ReadAndDeliverPacket(&mock_client_);
+}
+
+TEST_F(TunDevicePacketExchangerTest, ReadPacketReturnsNullOnBlockedRead) {
+ EXPECT_CALL(mock_kernel_, read(kFd, _, kMtu))
+ .WillOnce(Invoke([](int fd, void* buf, size_t count) {
+ errno = EAGAIN;
+ return -1;
+ }));
+ EXPECT_CALL(mock_stats_, OnReadError(_)).Times(1);
+ EXPECT_FALSE(exchanger_.ReadAndDeliverPacket(&mock_client_));
+}
+
+TEST_F(TunDevicePacketExchangerTest,
+ ReadPacketReturnsThePacketOnSuccessfulRead) {
+ string packet = "fake_packet";
+ EXPECT_CALL(mock_kernel_, read(kFd, _, kMtu))
+ .WillOnce(Invoke([packet](int fd, void* buf, size_t count) {
+ memcpy(buf, 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_));
+}
+
+} // namespace
+} // namespace quic
diff --git a/quic/qbone/bonnet/tun_device_test.cc b/quic/qbone/bonnet/tun_device_test.cc
new file mode 100644
index 0000000..e9ae4d3
--- /dev/null
+++ b/quic/qbone/bonnet/tun_device_test.cc
@@ -0,0 +1,208 @@
+// 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 "net/third_party/quiche/src/quic/qbone/bonnet/tun_device.h"
+
+#include <linux/if.h>
+#include <linux/if_tun.h>
+#include <sys/ioctl.h>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/mock_kernel.h"
+
+namespace quic {
+namespace {
+
+using ::testing::_;
+using ::testing::AnyNumber;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::StrEq;
+using ::testing::Unused;
+
+const char kDeviceName[] = "tun0";
+const int kSupportedFeatures =
+ IFF_TUN | IFF_TAP | IFF_MULTI_QUEUE | IFF_ONE_QUEUE | IFF_NO_PI;
+
+// Quite a bit of EXPECT_CALL().Times(AnyNumber()).WillRepeatedly() are used to
+// make sure we can correctly set common expectations and override the
+// expectation with later call to EXPECT_CALL(). ON_CALL cannot be used here
+// since when EPXECT_CALL overrides ON_CALL, it ignores the parameter matcher
+// which results in unexpected call even if ON_CALL exists.
+class TunDeviceTest : public QuicTest {
+ protected:
+ void SetUp() override {
+ EXPECT_CALL(mock_kernel_, socket(AF_INET6, _, _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke([this](Unused, Unused, Unused) {
+ EXPECT_CALL(mock_kernel_, close(next_fd_)).WillOnce(Return(0));
+ return next_fd_++;
+ }));
+ }
+
+ // Set the expectations for calling Init().
+ void SetInitExpectations(int mtu, bool persist) {
+ EXPECT_CALL(mock_kernel_, open(StrEq("/dev/net/tun"), _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke([this](Unused, Unused) {
+ EXPECT_CALL(mock_kernel_, close(next_fd_)).WillOnce(Return(0));
+ return next_fd_++;
+ }));
+ EXPECT_CALL(mock_kernel_, ioctl(_, TUNGETFEATURES, _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke([](Unused, Unused, void* argp) {
+ auto* actual_flags = reinterpret_cast<int*>(argp);
+ *actual_flags = kSupportedFeatures;
+ return 0;
+ }));
+ EXPECT_CALL(mock_kernel_, ioctl(_, TUNSETIFF, _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke([](Unused, Unused, void* argp) {
+ auto* ifr = reinterpret_cast<struct ifreq*>(argp);
+ EXPECT_EQ(IFF_TUN | IFF_MULTI_QUEUE | IFF_NO_PI, ifr->ifr_flags);
+ EXPECT_THAT(ifr->ifr_name, StrEq(kDeviceName));
+ return 0;
+ }));
+ EXPECT_CALL(mock_kernel_, ioctl(_, TUNSETPERSIST, _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke([persist](Unused, Unused, void* argp) {
+ auto* ifr = reinterpret_cast<struct ifreq*>(argp);
+ if (persist) {
+ EXPECT_THAT(ifr->ifr_name, StrEq(kDeviceName));
+ } else {
+ EXPECT_EQ(nullptr, ifr);
+ }
+ return 0;
+ }));
+ EXPECT_CALL(mock_kernel_, ioctl(_, SIOCSIFMTU, _))
+ .Times(AnyNumber())
+ .WillRepeatedly(Invoke([mtu](Unused, Unused, void* argp) {
+ auto* ifr = reinterpret_cast<struct ifreq*>(argp);
+ EXPECT_EQ(mtu, ifr->ifr_mtu);
+ EXPECT_THAT(ifr->ifr_name, StrEq(kDeviceName));
+ return 0;
+ }));
+ }
+
+ // Expect that Up() will be called. Force the call to fail when fail == true.
+ void ExpectUp(bool fail) {
+ EXPECT_CALL(mock_kernel_, ioctl(_, SIOCSIFFLAGS, _))
+ .WillOnce(Invoke([fail](Unused, Unused, void* argp) {
+ auto* ifr = reinterpret_cast<struct ifreq*>(argp);
+ EXPECT_TRUE(ifr->ifr_flags & IFF_UP);
+ EXPECT_THAT(ifr->ifr_name, StrEq(kDeviceName));
+ if (fail) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }));
+ }
+
+ // Expect that Down() will be called *after* the interface is up. Force the
+ // call to fail when fail == true.
+ void ExpectDown(bool fail) {
+ EXPECT_CALL(mock_kernel_, ioctl(_, SIOCSIFFLAGS, _))
+ .WillOnce(Invoke([fail](Unused, Unused, void* argp) {
+ auto* ifr = reinterpret_cast<struct ifreq*>(argp);
+ EXPECT_FALSE(ifr->ifr_flags & IFF_UP);
+ EXPECT_THAT(ifr->ifr_name, StrEq(kDeviceName));
+ if (fail) {
+ return -1;
+ } else {
+ return 0;
+ }
+ }));
+ }
+
+ MockKernel mock_kernel_;
+ int next_fd_ = 100;
+};
+
+// A TunDevice can be initialized and up
+TEST_F(TunDeviceTest, BasicWorkFlow) {
+ SetInitExpectations(/* mtu = */ 1500, /* persist = */ false);
+ TunDevice tun_device(kDeviceName, 1500, false, &mock_kernel_);
+ EXPECT_TRUE(tun_device.Init());
+ EXPECT_GT(tun_device.GetFileDescriptor(), -1);
+
+ ExpectUp(/* fail = */ false);
+ EXPECT_TRUE(tun_device.Up());
+ ExpectDown(/* fail = */ false);
+}
+
+TEST_F(TunDeviceTest, FailToOpenTunDevice) {
+ SetInitExpectations(/* mtu = */ 1500, /* persist = */ false);
+ EXPECT_CALL(mock_kernel_, open(StrEq("/dev/net/tun"), _))
+ .WillOnce(Return(-1));
+ TunDevice tun_device(kDeviceName, 1500, false, &mock_kernel_);
+ EXPECT_FALSE(tun_device.Init());
+ EXPECT_EQ(tun_device.GetFileDescriptor(), -1);
+}
+
+TEST_F(TunDeviceTest, FailToCheckFeature) {
+ SetInitExpectations(/* mtu = */ 1500, /* persist = */ false);
+ EXPECT_CALL(mock_kernel_, ioctl(_, TUNGETFEATURES, _)).WillOnce(Return(-1));
+ TunDevice tun_device(kDeviceName, 1500, false, &mock_kernel_);
+ EXPECT_FALSE(tun_device.Init());
+ EXPECT_EQ(tun_device.GetFileDescriptor(), -1);
+}
+
+TEST_F(TunDeviceTest, TooFewFeature) {
+ SetInitExpectations(/* mtu = */ 1500, /* persist = */ false);
+ EXPECT_CALL(mock_kernel_, ioctl(_, TUNGETFEATURES, _))
+ .WillOnce(Invoke([](Unused, Unused, void* argp) {
+ int* actual_features = reinterpret_cast<int*>(argp);
+ *actual_features = IFF_TUN | IFF_ONE_QUEUE;
+ return 0;
+ }));
+ TunDevice tun_device(kDeviceName, 1500, false, &mock_kernel_);
+ EXPECT_FALSE(tun_device.Init());
+ EXPECT_EQ(tun_device.GetFileDescriptor(), -1);
+}
+
+TEST_F(TunDeviceTest, FailToSetFlag) {
+ SetInitExpectations(/* mtu = */ 1500, /* persist = */ true);
+ EXPECT_CALL(mock_kernel_, ioctl(_, TUNSETIFF, _)).WillOnce(Return(-1));
+ TunDevice tun_device(kDeviceName, 1500, true, &mock_kernel_);
+ EXPECT_FALSE(tun_device.Init());
+ EXPECT_EQ(tun_device.GetFileDescriptor(), -1);
+}
+
+TEST_F(TunDeviceTest, FailToPersistDevice) {
+ SetInitExpectations(/* mtu = */ 1500, /* persist = */ true);
+ EXPECT_CALL(mock_kernel_, ioctl(_, TUNSETPERSIST, _)).WillOnce(Return(-1));
+ TunDevice tun_device(kDeviceName, 1500, true, &mock_kernel_);
+ EXPECT_FALSE(tun_device.Init());
+ EXPECT_EQ(tun_device.GetFileDescriptor(), -1);
+}
+
+TEST_F(TunDeviceTest, FailToOpenSocket) {
+ SetInitExpectations(/* mtu = */ 1500, /* persist = */ true);
+ EXPECT_CALL(mock_kernel_, socket(AF_INET6, _, _)).WillOnce(Return(-1));
+ TunDevice tun_device(kDeviceName, 1500, true, &mock_kernel_);
+ EXPECT_FALSE(tun_device.Init());
+ EXPECT_EQ(tun_device.GetFileDescriptor(), -1);
+}
+
+TEST_F(TunDeviceTest, FailToSetMtu) {
+ SetInitExpectations(/* mtu = */ 1500, /* persist = */ true);
+ EXPECT_CALL(mock_kernel_, ioctl(_, SIOCSIFMTU, _)).WillOnce(Return(-1));
+ TunDevice tun_device(kDeviceName, 1500, true, &mock_kernel_);
+ EXPECT_FALSE(tun_device.Init());
+ EXPECT_EQ(tun_device.GetFileDescriptor(), -1);
+}
+
+TEST_F(TunDeviceTest, FailToUp) {
+ SetInitExpectations(/* mtu = */ 1500, /* persist = */ true);
+ TunDevice tun_device(kDeviceName, 1500, true, &mock_kernel_);
+ EXPECT_TRUE(tun_device.Init());
+ EXPECT_GT(tun_device.GetFileDescriptor(), -1);
+
+ ExpectUp(/* fail = */ true);
+ EXPECT_FALSE(tun_device.Up());
+}
+
+} // namespace
+} // namespace quic