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