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
diff --git a/quic/qbone/mock_qbone_client.h b/quic/qbone/mock_qbone_client.h
new file mode 100644
index 0000000..37df26d
--- /dev/null
+++ b/quic/qbone/mock_qbone_client.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_MOCK_QBONE_CLIENT_H_
+#define QUICHE_QUIC_QBONE_MOCK_QBONE_CLIENT_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_client_interface.h"
+
+namespace quic {
+
+class MockQboneClient : public QboneClientInterface {
+ public:
+ MOCK_METHOD1(ProcessPacketFromNetwork, void(QuicStringPiece packet));
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_MOCK_QBONE_CLIENT_H_
diff --git a/quic/qbone/mock_qbone_server_session.h b/quic/qbone/mock_qbone_server_session.h
new file mode 100644
index 0000000..652c017
--- /dev/null
+++ b/quic/qbone/mock_qbone_server_session.h
@@ -0,0 +1,36 @@
+// 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_MOCK_QBONE_SERVER_SESSION_H_
+#define QUICHE_QUIC_QBONE_MOCK_QBONE_SERVER_SESSION_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_server_session.h"
+
+namespace quic {
+
+class MockQboneServerSession : public QboneServerSession {
+ public:
+ explicit MockQboneServerSession(QuicConnection* connection)
+ : QboneServerSession(CurrentSupportedVersions(),
+ connection,
+ /*owner=*/nullptr,
+ /*config=*/{},
+ /*quic_crypto_server_config=*/nullptr,
+ /*compressed_certs_cache=*/nullptr,
+ /*writer=*/nullptr,
+ /*self_ip=*/QuicIpAddress::Loopback6(),
+ /*client_ip=*/QuicIpAddress::Loopback6(),
+ /*client_ip_subnet_length=*/0,
+ /*handler=*/nullptr) {}
+
+ MOCK_METHOD1(SendClientRequest, bool(const QboneClientRequest&));
+
+ MOCK_METHOD1(ProcessPacketFromNetwork, void(QuicStringPiece));
+ MOCK_METHOD1(ProcessPacketFromPeer, void(QuicStringPiece));
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_MOCK_QBONE_SERVER_SESSION_H_
diff --git a/quic/qbone/platform/icmp_packet.cc b/quic/qbone/platform/icmp_packet.cc
new file mode 100644
index 0000000..8ba3916
--- /dev/null
+++ b/quic/qbone/platform/icmp_packet.cc
@@ -0,0 +1,84 @@
+// 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/platform/icmp_packet.h"
+
+#include <netinet/ip6.h>
+#include "net/third_party/quiche/src/quic/platform/api/quic_endian.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/internet_checksum.h"
+
+namespace quic {
+namespace {
+
+constexpr size_t kIPv6AddressSize = sizeof(in6_addr);
+constexpr size_t kIPv6HeaderSize = sizeof(ip6_hdr);
+constexpr size_t kICMPv6HeaderSize = sizeof(icmp6_hdr);
+constexpr size_t kIPv6MinPacketSize = 1280;
+constexpr size_t kIcmpTtl = 64;
+constexpr size_t kICMPv6BodyMaxSize =
+ kIPv6MinPacketSize - kIPv6HeaderSize - kICMPv6HeaderSize;
+
+struct ICMPv6Packet {
+ ip6_hdr ip_header;
+ icmp6_hdr icmp_header;
+ uint8_t body[kICMPv6BodyMaxSize];
+};
+
+// pseudo header as described in RFC 2460 Section 8.1 (excluding addresses)
+struct IPv6PseudoHeader {
+ uint32_t payload_size{};
+ uint8_t zeros[3] = {0, 0, 0};
+ uint8_t next_header = IPPROTO_ICMPV6;
+};
+
+} // namespace
+
+void CreateIcmpPacket(in6_addr src,
+ in6_addr dst,
+ const icmp6_hdr& icmp_header,
+ QuicStringPiece body,
+ const std::function<void(QuicStringPiece)>& cb) {
+ const size_t body_size = std::min(body.size(), kICMPv6BodyMaxSize);
+ const size_t payload_size = kICMPv6HeaderSize + body_size;
+
+ ICMPv6Packet icmp_packet{};
+ // Set version to 6.
+ icmp_packet.ip_header.ip6_vfc = 0x6 << 4;
+ // Set the payload size, protocol and TTL.
+ icmp_packet.ip_header.ip6_plen = QuicEndian::HostToNet16(payload_size);
+ icmp_packet.ip_header.ip6_nxt = IPPROTO_ICMPV6;
+ icmp_packet.ip_header.ip6_hops = kIcmpTtl;
+ // Set the source address to the specified self IP.
+ icmp_packet.ip_header.ip6_src = src;
+ icmp_packet.ip_header.ip6_dst = dst;
+
+ icmp_packet.icmp_header = icmp_header;
+ // Per RFC 4443 Section 2.3, set checksum field to 0 prior to computing it
+ icmp_packet.icmp_header.icmp6_cksum = 0;
+
+ IPv6PseudoHeader pseudo_header{};
+ pseudo_header.payload_size = QuicEndian::HostToNet32(payload_size);
+
+ InternetChecksum checksum;
+ // Pseudoheader.
+ checksum.Update(icmp_packet.ip_header.ip6_src.s6_addr, kIPv6AddressSize);
+ checksum.Update(icmp_packet.ip_header.ip6_dst.s6_addr, kIPv6AddressSize);
+ checksum.Update(reinterpret_cast<char*>(&pseudo_header),
+ sizeof(pseudo_header));
+ // ICMP header.
+ checksum.Update(reinterpret_cast<const char*>(&icmp_packet.icmp_header),
+ sizeof(icmp_packet.icmp_header));
+ // Body.
+ checksum.Update(body.data(), body_size);
+ icmp_packet.icmp_header.icmp6_cksum = checksum.Value();
+
+ memcpy(icmp_packet.body, body.data(), body_size);
+
+ const char* packet = reinterpret_cast<char*>(&icmp_packet);
+ const size_t packet_size = offsetof(ICMPv6Packet, body) + body_size;
+
+ cb(QuicStringPiece(packet, packet_size));
+}
+
+} // namespace quic
diff --git a/quic/qbone/platform/icmp_packet.h b/quic/qbone/platform/icmp_packet.h
new file mode 100644
index 0000000..ae440d2
--- /dev/null
+++ b/quic/qbone/platform/icmp_packet.h
@@ -0,0 +1,29 @@
+// 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_PLATFORM_ICMP_PACKET_H_
+#define QUICHE_QUIC_QBONE_PLATFORM_ICMP_PACKET_H_
+
+#include <netinet/icmp6.h>
+#include <netinet/in.h>
+
+#include <functional>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// Creates an ICMPv6 packet, returning a packed string representation of the
+// packet to |cb|. The resulting packet is given to a callback because it's
+// stack allocated inside CreateIcmpPacket.
+void CreateIcmpPacket(in6_addr src,
+ in6_addr dst,
+ const icmp6_hdr& icmp_header,
+ quic::QuicStringPiece body,
+ const std::function<void(quic::QuicStringPiece)>& cb);
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_PLATFORM_ICMP_PACKET_H_
diff --git a/quic/qbone/platform/icmp_packet_test.cc b/quic/qbone/platform/icmp_packet_test.cc
new file mode 100644
index 0000000..1aeabe0
--- /dev/null
+++ b/quic/qbone/platform/icmp_packet_test.cc
@@ -0,0 +1,127 @@
+// 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/platform/icmp_packet.h"
+
+#include <netinet/ip6.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+namespace quic {
+namespace {
+
+constexpr char kReferenceSourceAddress[] = "fe80:1:2:3:4::1";
+constexpr char kReferenceDestinationAddress[] = "fe80:4:3:2:1::1";
+
+// clang-format off
+constexpr uint8_t kReferenceICMPMessageBody[] {
+ 0xd2, 0x61, 0x29, 0x5b, 0x00, 0x00, 0x00, 0x00,
+ 0x0d, 0x59, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37
+};
+
+constexpr uint8_t kReferenceICMPPacket[] = {
+ // START IPv6 Header
+ // IPv6 with zero TOS and flow label.
+ 0x60, 0x00, 0x00, 0x00,
+ // Payload is 64 bytes
+ 0x00, 0x40,
+ // Next header is 58
+ 0x3a,
+ // Hop limit is 64
+ 0x40,
+ // Source address of fe80:1:2:3:4::1
+ 0xfe, 0x80, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03,
+ 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // Destination address of fe80:4:3:2:1::1
+ 0xfe, 0x80, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // END IPv6 Header
+ // START ICMPv6 Header
+ // Echo Request, zero code
+ 0x80, 0x00,
+ // Checksum
+ 0xec, 0x00,
+ // Identifier
+ 0xcb, 0x82,
+ // Sequence Number
+ 0x00, 0x01,
+ // END ICMPv6 Header
+ // Message body
+ 0xd2, 0x61, 0x29, 0x5b, 0x00, 0x00, 0x00, 0x00,
+ 0x0d, 0x59, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
+ 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
+ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f,
+ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37
+};
+// clang-format on
+
+} // namespace
+
+TEST(IcmpPacketTest, CreatedPacketMatchesReference) {
+ QuicIpAddress src;
+ ASSERT_TRUE(src.FromString(kReferenceSourceAddress));
+ in6_addr src_addr;
+ memcpy(src_addr.s6_addr, src.ToPackedString().data(), sizeof(in6_addr));
+
+ QuicIpAddress dst;
+ ASSERT_TRUE(dst.FromString(kReferenceDestinationAddress));
+ in6_addr dst_addr;
+ memcpy(dst_addr.s6_addr, dst.ToPackedString().data(), sizeof(in6_addr));
+
+ icmp6_hdr icmp_header{};
+ icmp_header.icmp6_type = ICMP6_ECHO_REQUEST;
+ icmp_header.icmp6_id = 0x82cb;
+ icmp_header.icmp6_seq = 0x0100;
+
+ QuicStringPiece message_body = QuicStringPiece(
+ reinterpret_cast<const char*>(kReferenceICMPMessageBody), 56);
+ QuicStringPiece expected_packet =
+ QuicStringPiece(reinterpret_cast<const char*>(kReferenceICMPPacket), 104);
+ CreateIcmpPacket(src_addr, dst_addr, icmp_header, message_body,
+ [&expected_packet](QuicStringPiece packet) {
+ QUIC_LOG(INFO) << QuicTextUtils::HexDump(packet);
+ ASSERT_EQ(packet, expected_packet);
+ });
+}
+
+TEST(IcmpPacketTest, NonZeroChecksumIsIgnored) {
+ QuicIpAddress src;
+ ASSERT_TRUE(src.FromString(kReferenceSourceAddress));
+ in6_addr src_addr;
+ memcpy(src_addr.s6_addr, src.ToPackedString().data(), sizeof(in6_addr));
+
+ QuicIpAddress dst;
+ ASSERT_TRUE(dst.FromString(kReferenceDestinationAddress));
+ in6_addr dst_addr;
+ memcpy(dst_addr.s6_addr, dst.ToPackedString().data(), sizeof(in6_addr));
+
+ icmp6_hdr icmp_header{};
+ icmp_header.icmp6_type = ICMP6_ECHO_REQUEST;
+ icmp_header.icmp6_id = 0x82cb;
+ icmp_header.icmp6_seq = 0x0100;
+ // Set the checksum to a bogus value
+ icmp_header.icmp6_cksum = 0x1234;
+
+ QuicStringPiece message_body = QuicStringPiece(
+ reinterpret_cast<const char*>(kReferenceICMPMessageBody), 56);
+ QuicStringPiece expected_packet =
+ QuicStringPiece(reinterpret_cast<const char*>(kReferenceICMPPacket), 104);
+ CreateIcmpPacket(src_addr, dst_addr, icmp_header, message_body,
+ [&expected_packet](QuicStringPiece packet) {
+ QUIC_LOG(INFO) << QuicTextUtils::HexDump(packet);
+ ASSERT_EQ(packet, expected_packet);
+ });
+}
+
+} // namespace quic
diff --git a/quic/qbone/platform/internet_checksum.cc b/quic/qbone/platform/internet_checksum.cc
new file mode 100644
index 0000000..9cbe227
--- /dev/null
+++ b/quic/qbone/platform/internet_checksum.cc
@@ -0,0 +1,32 @@
+// 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/platform/internet_checksum.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_endian.h"
+
+namespace quic {
+
+void InternetChecksum::Update(const char* data, size_t size) {
+ const char* current;
+ for (current = data; current + 1 < data + size; current += 2) {
+ accumulator_ += *reinterpret_cast<const uint16_t*>(current);
+ }
+ if (current < data + size) {
+ accumulator_ += *reinterpret_cast<const uint8_t*>(current);
+ }
+}
+
+void InternetChecksum::Update(const uint8_t* data, size_t size) {
+ Update(reinterpret_cast<const char*>(data), size);
+}
+
+uint16_t InternetChecksum::Value() const {
+ uint32_t total = accumulator_;
+ while (total & 0xffff0000u) {
+ total = (total >> 16u) + (total & 0xffffu);
+ }
+ return ~static_cast<uint16_t>(total);
+}
+
+} // namespace quic
diff --git a/quic/qbone/platform/internet_checksum.h b/quic/qbone/platform/internet_checksum.h
new file mode 100644
index 0000000..85d2415
--- /dev/null
+++ b/quic/qbone/platform/internet_checksum.h
@@ -0,0 +1,32 @@
+// 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_PLATFORM_INTERNET_CHECKSUM_H_
+#define QUICHE_QUIC_QBONE_PLATFORM_INTERNET_CHECKSUM_H_
+
+#include <cstddef>
+#include <cstdint>
+
+namespace quic {
+
+// Incrementally compute an Internet header checksum as described in RFC 1071.
+class InternetChecksum {
+ public:
+ // Update the checksum with the specified data. Note that while the checksum
+ // is commutative, the data has to be supplied in the units of two-byte words.
+ // If there is an extra byte at the end, the function has to be called on it
+ // last.
+ void Update(const char* data, size_t size);
+
+ void Update(const uint8_t* data, size_t size);
+
+ uint16_t Value() const;
+
+ private:
+ uint32_t accumulator_ = 0;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_PLATFORM_INTERNET_CHECKSUM_H_
diff --git a/quic/qbone/platform/internet_checksum_test.cc b/quic/qbone/platform/internet_checksum_test.cc
new file mode 100644
index 0000000..a4736e2
--- /dev/null
+++ b/quic/qbone/platform/internet_checksum_test.cc
@@ -0,0 +1,67 @@
+// 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/platform/internet_checksum.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+
+namespace quic {
+namespace {
+
+// From the Numerical Example described in RFC 1071
+// https://tools.ietf.org/html/rfc1071#section-3
+TEST(InternetChecksumTest, MatchesRFC1071Example) {
+ uint8_t data[] = {0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6, 0xf7};
+
+ InternetChecksum checksum;
+ checksum.Update(data, 8);
+ uint16_t result = checksum.Value();
+ auto* result_bytes = reinterpret_cast<uint8_t*>(&result);
+ ASSERT_EQ(0x22, result_bytes[0]);
+ ASSERT_EQ(0x0d, result_bytes[1]);
+}
+
+// Same as above, except 7 bytes. Should behave as if there was an 8th byte
+// that equals 0.
+TEST(InternetChecksumTest, MatchesRFC1071ExampleWithOddByteCount) {
+ uint8_t data[] = {0x00, 0x01, 0xf2, 0x03, 0xf4, 0xf5, 0xf6};
+
+ InternetChecksum checksum;
+ checksum.Update(data, 7);
+ uint16_t result = checksum.Value();
+ auto* result_bytes = reinterpret_cast<uint8_t*>(&result);
+ ASSERT_EQ(0x23, result_bytes[0]);
+ ASSERT_EQ(0x04, result_bytes[1]);
+}
+
+// From the example described at:
+// http://www.cs.berkeley.edu/~kfall/EE122/lec06/tsld023.htm
+TEST(InternetChecksumTest, MatchesBerkleyExample) {
+ uint8_t data[] = {0xe3, 0x4f, 0x23, 0x96, 0x44, 0x27, 0x99, 0xf3};
+
+ InternetChecksum checksum;
+ checksum.Update(data, 8);
+ uint16_t result = checksum.Value();
+ auto* result_bytes = reinterpret_cast<uint8_t*>(&result);
+ ASSERT_EQ(0x1a, result_bytes[0]);
+ ASSERT_EQ(0xff, result_bytes[1]);
+}
+
+TEST(InternetChecksumTest, ChecksumRequiringMultipleCarriesInLittleEndian) {
+ uint8_t data[] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02, 0x00};
+
+ // Data will accumulate to 0x0002FFFF
+ // Summing lower and upper halves gives 0x00010001
+ // Second sum of lower and upper halves gives 0x0002
+ // One's complement gives 0xfffd, or [0xfd, 0xff] in network byte order
+ InternetChecksum checksum;
+ checksum.Update(data, 8);
+ uint16_t result = checksum.Value();
+ auto* result_bytes = reinterpret_cast<uint8_t*>(&result);
+ EXPECT_EQ(0xfd, result_bytes[0]);
+ EXPECT_EQ(0xff, result_bytes[1]);
+}
+
+} // namespace
+} // namespace quic
diff --git a/quic/qbone/platform/ip_range.cc b/quic/qbone/platform/ip_range.cc
new file mode 100644
index 0000000..15ebb72
--- /dev/null
+++ b/quic/qbone/platform/ip_range.cc
@@ -0,0 +1,101 @@
+// 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/platform/ip_range.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_endian.h"
+
+namespace quic {
+
+namespace {
+
+constexpr size_t kIPv4Size = 32;
+constexpr size_t kIPv6Size = 128;
+
+QuicIpAddress TruncateToLength(const QuicIpAddress& input,
+ size_t* prefix_length) {
+ QuicIpAddress output;
+ if (input.IsIPv4()) {
+ if (*prefix_length > kIPv4Size) {
+ *prefix_length = kIPv4Size;
+ return input;
+ }
+ uint32_t raw_address =
+ *reinterpret_cast<const uint32_t*>(input.ToPackedString().data());
+ raw_address = QuicEndian::NetToHost32(raw_address);
+ raw_address &= ~0U << (kIPv4Size - *prefix_length);
+ raw_address = QuicEndian::HostToNet32(raw_address);
+ output.FromPackedString(reinterpret_cast<const char*>(&raw_address),
+ sizeof(raw_address));
+ return output;
+ }
+ if (input.IsIPv6()) {
+ if (*prefix_length > kIPv6Size) {
+ *prefix_length = kIPv6Size;
+ return input;
+ }
+ uint64_t raw_address[2];
+ memcpy(raw_address, input.ToPackedString().data(), sizeof(raw_address));
+ // raw_address[0] holds higher 8 bytes in big endian and raw_address[1]
+ // holds lower 8 bytes. Converting each to little endian for us to mask bits
+ // out.
+ // The endianess between raw_address[0] and raw_address[1] is handled
+ // explicitly by handling lower and higher bytes separately.
+ raw_address[0] = QuicEndian::NetToHost64(raw_address[0]);
+ raw_address[1] = QuicEndian::NetToHost64(raw_address[1]);
+ if (*prefix_length <= kIPv6Size / 2) {
+ raw_address[0] &= ~uint64_t{0} << (kIPv6Size / 2 - *prefix_length);
+ raw_address[1] = 0;
+ } else {
+ raw_address[1] &= ~uint64_t{0} << (kIPv6Size - *prefix_length);
+ }
+ raw_address[0] = QuicEndian::HostToNet64(raw_address[0]);
+ raw_address[1] = QuicEndian::HostToNet64(raw_address[1]);
+ output.FromPackedString(reinterpret_cast<const char*>(raw_address),
+ sizeof(raw_address));
+ return output;
+ }
+ return output;
+}
+
+} // namespace
+
+IpRange::IpRange(const QuicIpAddress& prefix, size_t prefix_length)
+ : prefix_(prefix), prefix_length_(prefix_length) {
+ prefix_ = TruncateToLength(prefix_, &prefix_length_);
+}
+
+bool IpRange::operator==(IpRange other) const {
+ return prefix_ == other.prefix_ && prefix_length_ == other.prefix_length_;
+}
+
+bool IpRange::operator!=(IpRange other) const {
+ return !(*this == other);
+}
+
+bool IpRange::FromString(const string& range) {
+ size_t slash_pos = range.find('/');
+ if (slash_pos == string::npos) {
+ return false;
+ }
+ QuicIpAddress prefix;
+ bool success = prefix.FromString(range.substr(0, slash_pos));
+ if (!success) {
+ return false;
+ }
+ uint64_t num_processed = 0;
+ size_t prefix_length = std::stoi(range.substr(slash_pos + 1), &num_processed);
+ if (num_processed + 1 + slash_pos != range.length()) {
+ return false;
+ }
+ prefix_ = TruncateToLength(prefix, &prefix_length);
+ prefix_length_ = prefix_length;
+ return true;
+}
+
+QuicIpAddress IpRange::FirstAddressInRange() {
+ return prefix();
+}
+
+} // namespace quic
diff --git a/quic/qbone/platform/ip_range.h b/quic/qbone/platform/ip_range.h
new file mode 100644
index 0000000..545c32c
--- /dev/null
+++ b/quic/qbone/platform/ip_range.h
@@ -0,0 +1,60 @@
+// 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_PLATFORM_IP_RANGE_H_
+#define QUICHE_QUIC_QBONE_PLATFORM_IP_RANGE_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
+
+namespace quic {
+
+class IpRange {
+ public:
+ // Default constructor to have an uninitialized IpRange.
+ IpRange() : prefix_length_(0) {}
+
+ // prefix will be automatically truncated to prefix_length, so that any bit
+ // after prefix_length are zero.
+ IpRange(const QuicIpAddress& prefix, size_t prefix_length);
+
+ bool operator==(IpRange other) const;
+ bool operator!=(IpRange other) const;
+
+ // Parses range that looks like "10.0.0.1/8". Tailing bits will be set to zero
+ // after prefix_length. Return false if the parsing failed.
+ bool FromString(const string& range);
+
+ // Returns the string representation of this object.
+ string ToString() const {
+ if (IsInitialized()) {
+ return absl::StrCat(prefix_.ToString(), "/", prefix_length_);
+ }
+ return "(uninitialized)";
+ }
+
+ // Whether this object is initialized.
+ bool IsInitialized() const { return prefix_.IsInitialized(); }
+
+ // Returns the first available IP address in this IpRange. The resulting
+ // address will be uninitialized if there is no available address.
+ QuicIpAddress FirstAddressInRange();
+
+ // The address family of this IpRange.
+ IpAddressFamily address_family() const { return prefix_.address_family(); }
+
+ // The subnet's prefix address.
+ QuicIpAddress prefix() const { return prefix_; }
+
+ // The subnet's prefix length.
+ size_t prefix_length() const { return prefix_length_; }
+
+ private:
+ QuicIpAddress prefix_;
+ size_t prefix_length_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_PLATFORM_IP_RANGE_H_
diff --git a/quic/qbone/platform/ip_range_test.cc b/quic/qbone/platform/ip_range_test.cc
new file mode 100644
index 0000000..bac5c96
--- /dev/null
+++ b/quic/qbone/platform/ip_range_test.cc
@@ -0,0 +1,65 @@
+// 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/platform/ip_range.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"
+
+namespace quic {
+namespace {
+
+TEST(IpRangeTest, TruncateWorksIPv4) {
+ QuicIpAddress before_truncate;
+ before_truncate.FromString("255.255.255.255");
+ EXPECT_EQ("128.0.0.0/1", IpRange(before_truncate, 1).ToString());
+ EXPECT_EQ("192.0.0.0/2", IpRange(before_truncate, 2).ToString());
+ EXPECT_EQ("255.224.0.0/11", IpRange(before_truncate, 11).ToString());
+ EXPECT_EQ("255.255.255.224/27", IpRange(before_truncate, 27).ToString());
+ EXPECT_EQ("255.255.255.254/31", IpRange(before_truncate, 31).ToString());
+ EXPECT_EQ("255.255.255.255/32", IpRange(before_truncate, 32).ToString());
+ EXPECT_EQ("255.255.255.255/32", IpRange(before_truncate, 33).ToString());
+}
+
+TEST(IpRangeTest, TruncateWorksIPv6) {
+ QuicIpAddress before_truncate;
+ before_truncate.FromString("ffff:ffff:ffff:ffff:f903::5");
+ EXPECT_EQ("fe00::/7", IpRange(before_truncate, 7).ToString());
+ EXPECT_EQ("ffff:ffff:ffff::/48", IpRange(before_truncate, 48).ToString());
+ EXPECT_EQ("ffff:ffff:ffff:ffff::/64",
+ IpRange(before_truncate, 64).ToString());
+ EXPECT_EQ("ffff:ffff:ffff:ffff:8000::/65",
+ IpRange(before_truncate, 65).ToString());
+ EXPECT_EQ("ffff:ffff:ffff:ffff:f903::4/127",
+ IpRange(before_truncate, 127).ToString());
+}
+
+TEST(IpRangeTest, FromStringWorksIPv4) {
+ IpRange range;
+ ASSERT_TRUE(range.FromString("127.0.3.249/26"));
+ EXPECT_EQ("127.0.3.192/26", range.ToString());
+}
+
+TEST(IpRangeTest, FromStringWorksIPv6) {
+ IpRange range;
+ ASSERT_TRUE(range.FromString("ff01:8f21:77f9::/33"));
+ EXPECT_EQ("ff01:8f21::/33", range.ToString());
+}
+
+TEST(IpRangeTest, FirstAddressWorksIPv6) {
+ IpRange range;
+ ASSERT_TRUE(range.FromString("ffff:ffff::/64"));
+ QuicIpAddress first_address = range.FirstAddressInRange();
+ EXPECT_EQ("ffff:ffff::", first_address.ToString());
+}
+
+TEST(IpRangeTest, FirstAddressWorksIPv4) {
+ IpRange range;
+ ASSERT_TRUE(range.FromString("10.0.0.0/24"));
+ QuicIpAddress first_address = range.FirstAddressInRange();
+ EXPECT_EQ("10.0.0.0", first_address.ToString());
+}
+
+} // namespace
+} // namespace quic
diff --git a/quic/qbone/platform/kernel_interface.h b/quic/qbone/platform/kernel_interface.h
new file mode 100644
index 0000000..c96b6e6
--- /dev/null
+++ b/quic/qbone/platform/kernel_interface.h
@@ -0,0 +1,169 @@
+// 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_PLATFORM_KERNEL_INTERFACE_H_
+#define QUICHE_QUIC_QBONE_PLATFORM_KERNEL_INTERFACE_H_
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <type_traits>
+#include <utility>
+
+namespace quic {
+
+// A wrapper for making syscalls to the kernel, so that syscalls can be
+// mocked during testing.
+class KernelInterface {
+ public:
+ virtual ~KernelInterface() {}
+ virtual int bind(int fd, const struct sockaddr* addr, socklen_t addr_len) = 0;
+ virtual int close(int fd) = 0;
+ virtual int ioctl(int fd, int request, void* argp) = 0;
+ virtual int open(const char* pathname, int flags) = 0;
+ virtual ssize_t read(int fd, void* buf, size_t count) = 0;
+ virtual ssize_t recvfrom(int sockfd,
+ void* buf,
+ size_t len,
+ int flags,
+ struct sockaddr* src_addr,
+ socklen_t* addrlen) = 0;
+ virtual ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flags) = 0;
+ virtual ssize_t sendto(int sockfd,
+ const void* buf,
+ size_t len,
+ int flags,
+ const struct sockaddr* dest_addr,
+ socklen_t addrlen) = 0;
+ virtual int socket(int domain, int type, int protocol) = 0;
+ virtual int setsockopt(int fd,
+ int level,
+ int optname,
+ const void* optval,
+ socklen_t optlen) = 0;
+ virtual ssize_t write(int fd, const void* buf, size_t count) = 0;
+};
+
+// It is unfortunate to have R here, but std::result_of cannot be used.
+template <typename F, typename R, typename... Params>
+auto SyscallRetryOnError(R r, F f, Params&&... params)
+ -> decltype(f(std::forward<Params>(params)...)) {
+ static_assert(
+ std::is_same<decltype(f(std::forward<Params>(params)...)), R>::value,
+ "Return type does not match");
+ decltype(f(std::forward<Params>(params)...)) result;
+ do {
+ result = f(std::forward<Params>(params)...);
+ } while (result == r && errno == EINTR);
+ return result;
+}
+
+template <typename F, typename... Params>
+auto SyscallRetry(F f, Params&&... params)
+ -> decltype(f(std::forward<Params>(params)...)) {
+ return SyscallRetryOnError(-1, f, std::forward<Params>(params)...);
+}
+
+template <typename Runner>
+class ParametrizedKernel final : public KernelInterface {
+ public:
+ static_assert(std::is_trivially_destructible<Runner>::value,
+ "Runner is used as static, must be trivially destructible");
+
+ ~ParametrizedKernel() override {}
+
+ int bind(int fd, const struct sockaddr* addr, socklen_t addr_len) override {
+ static Runner syscall("bind");
+ return syscall.Retry(&::bind, fd, addr, addr_len);
+ }
+ int close(int fd) override {
+ static Runner syscall("close");
+ return syscall.Retry(&::close, fd);
+ }
+ int ioctl(int fd, int request, void* argp) override {
+ static Runner syscall("ioctl");
+ return syscall.Retry(&::ioctl, fd, request, argp);
+ }
+ int open(const char* pathname, int flags) override {
+ static Runner syscall("open");
+ return syscall.Retry(&::open, pathname, flags);
+ }
+ ssize_t read(int fd, void* buf, size_t count) override {
+ static Runner syscall("read");
+ return syscall.Run(&::read, fd, buf, count);
+ }
+ ssize_t recvfrom(int sockfd,
+ void* buf,
+ size_t len,
+ int flags,
+ struct sockaddr* src_addr,
+ socklen_t* addrlen) override {
+ static Runner syscall("recvfrom");
+ return syscall.RetryOnError(&::recvfrom, static_cast<ssize_t>(-1), sockfd,
+ buf, len, flags, src_addr, addrlen);
+ }
+ ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flags) override {
+ static Runner syscall("sendmsg");
+ return syscall.RetryOnError(&::sendmsg, static_cast<ssize_t>(-1), sockfd,
+ msg, flags);
+ }
+ ssize_t sendto(int sockfd,
+ const void* buf,
+ size_t len,
+ int flags,
+ const struct sockaddr* dest_addr,
+ socklen_t addrlen) override {
+ static Runner syscall("sendto");
+ return syscall.RetryOnError(&::sendto, static_cast<ssize_t>(-1), sockfd,
+ buf, len, flags, dest_addr, addrlen);
+ }
+ int socket(int domain, int type, int protocol) override {
+ static Runner syscall("socket");
+ return syscall.Retry(&::socket, domain, type, protocol);
+ }
+ int setsockopt(int fd,
+ int level,
+ int optname,
+ const void* optval,
+ socklen_t optlen) override {
+ static Runner syscall("setsockopt");
+ return syscall.Retry(&::setsockopt, fd, level, optname, optval, optlen);
+ }
+ ssize_t write(int fd, const void* buf, size_t count) override {
+ static Runner syscall("write");
+ return syscall.Run(&::write, fd, buf, count);
+ }
+};
+
+class DefaultKernelRunner {
+ public:
+ explicit DefaultKernelRunner(const char* name) {}
+
+ template <typename F, typename R, typename... Params>
+ static auto RetryOnError(F f, R r, Params&&... params)
+ -> decltype(f(std::forward<Params>(params)...)) {
+ return SyscallRetryOnError(r, f, std::forward<Params>(params)...);
+ }
+
+ template <typename F, typename... Params>
+ static auto Retry(F f, Params&&... params)
+ -> decltype(f(std::forward<Params>(params)...)) {
+ return SyscallRetry(f, std::forward<Params>(params)...);
+ }
+
+ template <typename F, typename... Params>
+ static auto Run(F f, Params&&... params)
+ -> decltype(f(std::forward<Params>(params)...)) {
+ return f(std::forward<Params>(params)...);
+ }
+};
+
+using Kernel = ParametrizedKernel<DefaultKernelRunner>;
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_PLATFORM_KERNEL_INTERFACE_H_
diff --git a/quic/qbone/platform/mock_kernel.h b/quic/qbone/platform/mock_kernel.h
new file mode 100644
index 0000000..c01aad1
--- /dev/null
+++ b/quic/qbone/platform/mock_kernel.h
@@ -0,0 +1,46 @@
+// 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_PLATFORM_MOCK_KERNEL_H_
+#define QUICHE_QUIC_QBONE_PLATFORM_MOCK_KERNEL_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/kernel_interface.h"
+
+namespace quic {
+
+class MockKernel : public KernelInterface {
+ public:
+ MockKernel() {}
+
+ MOCK_METHOD3(bind,
+ int(int fd, const struct sockaddr* addr, socklen_t addr_len));
+ MOCK_METHOD1(close, int(int fd));
+ MOCK_METHOD3(ioctl, int(int fd, int request, void* argp));
+ MOCK_METHOD2(open, int(const char* pathname, int flags));
+ MOCK_METHOD3(read, ssize_t(int fd, void* buf, size_t count));
+ MOCK_METHOD6(recvfrom,
+ ssize_t(int sockfd,
+ void* buf,
+ size_t len,
+ int flags,
+ struct sockaddr* src_addr,
+ socklen_t* addrlen));
+ MOCK_METHOD3(sendmsg,
+ ssize_t(int sockfd, const struct msghdr* msg, int flags));
+ MOCK_METHOD6(sendto,
+ ssize_t(int sockfd,
+ const void* buf,
+ size_t len,
+ int flags,
+ const struct sockaddr* dest_addr,
+ socklen_t addrlen));
+ MOCK_METHOD3(socket, int(int domain, int type, int protocol));
+ MOCK_METHOD5(setsockopt, int(int, int, int, const void*, socklen_t));
+ MOCK_METHOD3(write, ssize_t(int fd, const void* buf, size_t count));
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_PLATFORM_MOCK_KERNEL_H_
diff --git a/quic/qbone/platform/mock_netlink.h b/quic/qbone/platform/mock_netlink.h
new file mode 100644
index 0000000..5c1e7bc
--- /dev/null
+++ b/quic/qbone/platform/mock_netlink.h
@@ -0,0 +1,46 @@
+// 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_PLATFORM_MOCK_NETLINK_H_
+#define QUICHE_QUIC_QBONE_PLATFORM_MOCK_NETLINK_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/netlink_interface.h"
+
+namespace quic {
+
+class MockNetlink : public NetlinkInterface {
+ public:
+ MOCK_METHOD2(GetLinkInfo, bool(const string&, LinkInfo*));
+
+ MOCK_METHOD4(GetAddresses,
+ bool(int, uint8_t, std::vector<AddressInfo>*, int*));
+
+ MOCK_METHOD7(ChangeLocalAddress,
+ bool(uint32_t,
+ Verb,
+ const QuicIpAddress&,
+ uint8_t,
+ uint8_t,
+ uint8_t,
+ const std::vector<struct rtattr*>&));
+
+ MOCK_METHOD1(GetRouteInfo, bool(std::vector<RoutingRule>*));
+
+ MOCK_METHOD6(
+ ChangeRoute,
+ bool(Verb, uint32_t, const IpRange&, uint8_t, QuicIpAddress, int32_t));
+
+ MOCK_METHOD1(GetRuleInfo, bool(std::vector<IpRule>*));
+
+ MOCK_METHOD3(ChangeRule, bool(Verb, uint32_t, IpRange));
+
+ MOCK_METHOD2(Send, bool(struct iovec*, size_t));
+
+ MOCK_METHOD2(Recv, bool(uint32_t, NetlinkParserInterface*));
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_PLATFORM_MOCK_NETLINK_H_
diff --git a/quic/qbone/platform/netlink.cc b/quic/qbone/platform/netlink.cc
new file mode 100644
index 0000000..1a4270e
--- /dev/null
+++ b/quic/qbone/platform/netlink.cc
@@ -0,0 +1,828 @@
+// 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/platform/netlink.h"
+
+#include <linux/fib_rules.h>
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_fallthrough.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.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"
+#include "net/quic/platform/impl/quic_ip_address_impl.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/rtnetlink_message.h"
+
+namespace quic {
+
+Netlink::Netlink(KernelInterface* kernel) : kernel_(kernel) {
+ seq_ = QuicRandom::GetInstance()->RandUint64();
+}
+
+Netlink::~Netlink() {
+ CloseSocket();
+}
+
+void Netlink::ResetRecvBuf(size_t size) {
+ if (size != 0) {
+ recvbuf_ = QuicMakeUnique<char[]>(size);
+ } else {
+ recvbuf_ = nullptr;
+ }
+ recvbuf_length_ = size;
+}
+
+bool Netlink::OpenSocket() {
+ if (socket_fd_ >= 0) {
+ return true;
+ }
+
+ socket_fd_ = kernel_->socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+
+ if (socket_fd_ < 0) {
+ QUIC_PLOG(ERROR) << "can't open netlink socket";
+ return false;
+ }
+
+ QUIC_LOG(INFO) << "Opened a new netlink socket fd = " << socket_fd_;
+
+ // bind a local address to the socket
+ sockaddr_nl myaddr;
+ memset(&myaddr, 0, sizeof(myaddr));
+ myaddr.nl_family = AF_NETLINK;
+ if (kernel_->bind(socket_fd_, reinterpret_cast<struct sockaddr*>(&myaddr),
+ sizeof(myaddr)) < 0) {
+ QUIC_LOG(INFO) << "can't bind address to socket";
+ CloseSocket();
+ return false;
+ }
+
+ return true;
+}
+
+void Netlink::CloseSocket() {
+ if (socket_fd_ >= 0) {
+ QUIC_LOG(INFO) << "Closing netlink socket fd = " << socket_fd_;
+ kernel_->close(socket_fd_);
+ }
+ ResetRecvBuf(0);
+ socket_fd_ = -1;
+}
+
+namespace {
+
+class LinkInfoParser : public NetlinkParserInterface {
+ public:
+ LinkInfoParser(string interface_name, Netlink::LinkInfo* link_info)
+ : interface_name_(std::move(interface_name)), link_info_(link_info) {}
+
+ void Run(struct nlmsghdr* netlink_message) override {
+ if (netlink_message->nlmsg_type != RTM_NEWLINK) {
+ QUIC_LOG(INFO) << QuicStrCat(
+ "Unexpected nlmsg_type: ", netlink_message->nlmsg_type,
+ " expected: ", RTM_NEWLINK);
+ return;
+ }
+
+ struct ifinfomsg* interface_info =
+ reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(netlink_message));
+
+ // make sure interface_info is what we asked for.
+ if (interface_info->ifi_family != AF_UNSPEC) {
+ QUIC_LOG(INFO) << QuicStrCat(
+ "Unexpected ifi_family: ", interface_info->ifi_family,
+ " expected: ", AF_UNSPEC);
+ return;
+ }
+
+ char hardware_address[kHwAddrSize];
+ size_t hardware_address_length = 0;
+ char broadcast_address[kHwAddrSize];
+ size_t broadcast_address_length = 0;
+ string name;
+
+ // loop through the attributes
+ struct rtattr* rta;
+ int payload_length = IFLA_PAYLOAD(netlink_message);
+ for (rta = IFLA_RTA(interface_info); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ int attribute_length;
+ switch (rta->rta_type) {
+ case IFLA_ADDRESS: {
+ attribute_length = RTA_PAYLOAD(rta);
+ if (attribute_length > kHwAddrSize) {
+ QUIC_VLOG(2) << "IFLA_ADDRESS too long: " << attribute_length;
+ break;
+ }
+ memmove(hardware_address, RTA_DATA(rta), attribute_length);
+ hardware_address_length = attribute_length;
+ break;
+ }
+ case IFLA_BROADCAST: {
+ attribute_length = RTA_PAYLOAD(rta);
+ if (attribute_length > kHwAddrSize) {
+ QUIC_VLOG(2) << "IFLA_BROADCAST too long: " << attribute_length;
+ break;
+ }
+ memmove(broadcast_address, RTA_DATA(rta), attribute_length);
+ broadcast_address_length = attribute_length;
+ break;
+ }
+ case IFLA_IFNAME: {
+ name =
+ string(reinterpret_cast<char*>(RTA_DATA(rta)), RTA_PAYLOAD(rta));
+ // The name maybe a 0 terminated c string.
+ name = name.substr(0, name.find('\0'));
+ break;
+ }
+ }
+ }
+
+ QUIC_VLOG(2) << "interface name: " << name
+ << ", index: " << interface_info->ifi_index;
+
+ if (name == interface_name_) {
+ link_info_->index = interface_info->ifi_index;
+ link_info_->type = interface_info->ifi_type;
+ link_info_->hardware_address_length = hardware_address_length;
+ if (hardware_address_length > 0) {
+ memmove(&link_info_->hardware_address, hardware_address,
+ hardware_address_length);
+ }
+ link_info_->broadcast_address_length = broadcast_address_length;
+ if (broadcast_address_length > 0) {
+ memmove(&link_info_->broadcast_address, broadcast_address,
+ broadcast_address_length);
+ }
+ found_link_ = true;
+ }
+ }
+
+ bool found_link() { return found_link_; }
+
+ private:
+ const string interface_name_;
+ Netlink::LinkInfo* const link_info_;
+ bool found_link_ = false;
+};
+
+} // namespace
+
+bool Netlink::GetLinkInfo(const string& interface_name, LinkInfo* link_info) {
+ auto message = LinkMessage::New(RtnetlinkMessage::Operation::GET,
+ NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST,
+ seq_, getpid(), nullptr);
+
+ if (!Send(message.BuildIoVec().get(), message.IoVecSize())) {
+ QUIC_LOG(ERROR) << "send failed.";
+ return false;
+ }
+
+ // Pass the parser to the receive routine. It may be called multiple times
+ // since there may be multiple reply packets each with multiple reply
+ // messages.
+ LinkInfoParser parser(interface_name, link_info);
+ if (!Recv(seq_++, &parser)) {
+ QUIC_LOG(ERROR) << "recv failed.";
+ return false;
+ }
+
+ return parser.found_link();
+}
+
+namespace {
+
+class LocalAddressParser : public NetlinkParserInterface {
+ public:
+ LocalAddressParser(int interface_index,
+ uint8_t unwanted_flags,
+ std::vector<Netlink::AddressInfo>* local_addresses,
+ int* num_ipv6_nodad_dadfailed_addresses)
+ : interface_index_(interface_index),
+ unwanted_flags_(unwanted_flags),
+ local_addresses_(local_addresses),
+ num_ipv6_nodad_dadfailed_addresses_(
+ num_ipv6_nodad_dadfailed_addresses) {}
+
+ void Run(struct nlmsghdr* netlink_message) override {
+ // each nlmsg contains a header and multiple address attributes.
+ if (netlink_message->nlmsg_type != RTM_NEWADDR) {
+ QUIC_LOG(INFO) << "Unexpected nlmsg_type: " << netlink_message->nlmsg_type
+ << " expected: " << RTM_NEWADDR;
+ return;
+ }
+
+ struct ifaddrmsg* interface_address =
+ reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(netlink_message));
+
+ // Make sure this is for an address family we're interested in.
+ if (interface_address->ifa_family != AF_INET &&
+ interface_address->ifa_family != AF_INET6) {
+ QUIC_VLOG(2) << QuicStrCat("uninteresting ifa family: ",
+ interface_address->ifa_family);
+ return;
+ }
+
+ // Keep track of addresses with both 'nodad' and 'dadfailed', this really
+ // should't be possible and is likely a kernel bug.
+ if (num_ipv6_nodad_dadfailed_addresses_ != nullptr &&
+ (interface_address->ifa_flags & IFA_F_NODAD) &&
+ (interface_address->ifa_flags & IFA_F_DADFAILED)) {
+ ++(*num_ipv6_nodad_dadfailed_addresses_);
+ }
+
+ uint8_t unwanted_flags = interface_address->ifa_flags & unwanted_flags_;
+ if (unwanted_flags != 0) {
+ QUIC_VLOG(2) << QuicStrCat("unwanted ifa flags: ", unwanted_flags);
+ return;
+ }
+
+ // loop through the attributes
+ struct rtattr* rta;
+ int payload_length = IFA_PAYLOAD(netlink_message);
+ Netlink::AddressInfo address_info;
+ for (rta = IFA_RTA(interface_address); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ // There's quite a lot of confusion in Linux over the use of IFA_LOCAL and
+ // IFA_ADDRESS (source and destination address). For broadcast links, such
+ // as Ethernet, they are identical (see <linux/if_addr.h>), but the kernel
+ // sometimes uses only one or the other. We'll return both so that the
+ // caller can decide which to use.
+ if (rta->rta_type != IFA_LOCAL && rta->rta_type != IFA_ADDRESS) {
+ QUIC_VLOG(2) << "Ignoring uninteresting rta_type: " << rta->rta_type;
+ continue;
+ }
+
+ switch (interface_address->ifa_family) {
+ case AF_INET:
+ QUIC_FALLTHROUGH_INTENDED;
+ case AF_INET6:
+ // QuicIpAddress knows how to parse ip from raw bytes as long as they
+ // are in network byte order.
+ if (RTA_PAYLOAD(rta) == sizeof(struct in_addr) ||
+ RTA_PAYLOAD(rta) == sizeof(struct in6_addr)) {
+ auto* raw_ip = reinterpret_cast<char*>(RTA_DATA(rta));
+ if (rta->rta_type == IFA_LOCAL) {
+ address_info.local_address.FromPackedString(raw_ip,
+ RTA_PAYLOAD(rta));
+ } else {
+ address_info.interface_address.FromPackedString(raw_ip,
+ RTA_PAYLOAD(rta));
+ }
+ }
+ break;
+ default:
+ QUIC_LOG(ERROR) << QuicStrCat("Unknown address family: ",
+ interface_address->ifa_family);
+ }
+ }
+
+ QUIC_VLOG(2) << "local_address: " << address_info.local_address.ToString()
+ << " interface_address: "
+ << address_info.interface_address.ToString()
+ << " index: " << interface_address->ifa_index;
+ if (interface_address->ifa_index != interface_index_) {
+ return;
+ }
+
+ address_info.prefix_length = interface_address->ifa_prefixlen;
+ address_info.scope = interface_address->ifa_scope;
+ if (address_info.local_address.IsInitialized() ||
+ address_info.interface_address.IsInitialized()) {
+ local_addresses_->push_back(address_info);
+ }
+ }
+
+ private:
+ const int interface_index_;
+ const uint8_t unwanted_flags_;
+ std::vector<Netlink::AddressInfo>* const local_addresses_;
+ int* const num_ipv6_nodad_dadfailed_addresses_;
+};
+
+} // namespace
+
+bool Netlink::GetAddresses(int interface_index,
+ uint8_t unwanted_flags,
+ std::vector<AddressInfo>* addresses,
+ int* num_ipv6_nodad_dadfailed_addresses) {
+ // the message doesn't contain the index, we'll have to do the filtering while
+ // parsing the reply. This is because NLM_F_MATCH, which only returns entries
+ // that matches the request criteria, is not yet implemented (see man 3
+ // netlink).
+ auto message = AddressMessage::New(RtnetlinkMessage::Operation::GET,
+ NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST,
+ seq_, getpid(), nullptr);
+
+ // the send routine returns the socket to listen on.
+ if (!Send(message.BuildIoVec().get(), message.IoVecSize())) {
+ QUIC_LOG(ERROR) << "send failed.";
+ return false;
+ }
+
+ addresses->clear();
+ if (num_ipv6_nodad_dadfailed_addresses != nullptr) {
+ *num_ipv6_nodad_dadfailed_addresses = 0;
+ }
+
+ LocalAddressParser parser(interface_index, unwanted_flags, addresses,
+ num_ipv6_nodad_dadfailed_addresses);
+ // Pass the parser to the receive routine. It may be called multiple times
+ // since there may be multiple reply packets each with multiple reply
+ // messages.
+ if (!Recv(seq_++, &parser)) {
+ QUIC_LOG(ERROR) << "recv failed";
+ return false;
+ }
+ return true;
+}
+
+namespace {
+
+class UnknownParser : public NetlinkParserInterface {
+ public:
+ void Run(struct nlmsghdr* netlink_message) override {
+ QUIC_LOG(INFO) << "nlmsg reply type: " << netlink_message->nlmsg_type;
+ }
+};
+
+} // namespace
+
+bool Netlink::ChangeLocalAddress(
+ uint32_t interface_index,
+ Verb verb,
+ const QuicIpAddress& address,
+ uint8_t prefix_length,
+ uint8_t ifa_flags,
+ uint8_t ifa_scope,
+ const std::vector<struct rtattr*>& additional_attributes) {
+ if (verb == Verb::kReplace) {
+ return false;
+ }
+ auto operation = verb == Verb::kAdd ? RtnetlinkMessage::Operation::NEW
+ : RtnetlinkMessage::Operation::DEL;
+ uint8_t address_family;
+ if (address.address_family() == IpAddressFamily::IP_V4) {
+ address_family = AF_INET;
+ } else if (address.address_family() == IpAddressFamily::IP_V6) {
+ address_family = AF_INET6;
+ } else {
+ return false;
+ }
+
+ struct ifaddrmsg address_header = {address_family, prefix_length, ifa_flags,
+ ifa_scope, interface_index};
+
+ auto message = AddressMessage::New(operation, NLM_F_REQUEST | NLM_F_ACK, seq_,
+ getpid(), &address_header);
+
+ for (const auto& attribute : additional_attributes) {
+ if (attribute->rta_type == IFA_LOCAL) {
+ continue;
+ }
+ message.AppendAttribute(attribute->rta_type, RTA_DATA(attribute),
+ RTA_PAYLOAD(attribute));
+ }
+
+ message.AppendAttribute(IFA_LOCAL, address.ToPackedString().c_str(),
+ address.ToPackedString().size());
+
+ if (!Send(message.BuildIoVec().get(), message.IoVecSize())) {
+ QUIC_LOG(ERROR) << "send failed";
+ return false;
+ }
+
+ UnknownParser parser;
+ if (!Recv(seq_++, &parser)) {
+ QUIC_LOG(ERROR) << "receive failed.";
+ return false;
+ }
+ return true;
+}
+
+namespace {
+
+class RoutingRuleParser : public NetlinkParserInterface {
+ public:
+ explicit RoutingRuleParser(std::vector<Netlink::RoutingRule>* routing_rules)
+ : routing_rules_(routing_rules) {}
+
+ void Run(struct nlmsghdr* netlink_message) override {
+ if (netlink_message->nlmsg_type != RTM_NEWROUTE) {
+ QUIC_LOG(WARNING) << QuicStrCat(
+ "Unexpected nlmsg_type: ", netlink_message->nlmsg_type,
+ " expected: ", RTM_NEWROUTE);
+ return;
+ }
+
+ auto* route = reinterpret_cast<struct rtmsg*>(NLMSG_DATA(netlink_message));
+ int payload_length = RTM_PAYLOAD(netlink_message);
+
+ if (route->rtm_family != AF_INET && route->rtm_family != AF_INET6) {
+ QUIC_VLOG(2) << QuicStrCat("Uninteresting family: ", route->rtm_family);
+ return;
+ }
+
+ Netlink::RoutingRule rule;
+ rule.scope = route->rtm_scope;
+ rule.table = route->rtm_table;
+
+ struct rtattr* rta;
+ for (rta = RTM_RTA(route); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case RTA_TABLE: {
+ rule.table = *reinterpret_cast<uint32_t*>(RTA_DATA(rta));
+ break;
+ }
+ case RTA_DST: {
+ QuicIpAddress destination;
+ destination.FromPackedString(reinterpret_cast<char*> RTA_DATA(rta),
+ RTA_PAYLOAD(rta));
+ rule.destination_subnet = IpRange(destination, route->rtm_dst_len);
+ break;
+ }
+ case RTA_PREFSRC: {
+ QuicIpAddress preferred_source;
+ rule.preferred_source.FromPackedString(
+ reinterpret_cast<char*> RTA_DATA(rta), RTA_PAYLOAD(rta));
+ break;
+ }
+ case RTA_OIF: {
+ rule.out_interface = *reinterpret_cast<int*>(RTA_DATA(rta));
+ break;
+ }
+ default: {
+ QUIC_VLOG(2) << QuicStrCat("Uninteresting attribute: ",
+ rta->rta_type);
+ }
+ }
+ }
+ routing_rules_->push_back(rule);
+ }
+
+ private:
+ std::vector<Netlink::RoutingRule>* routing_rules_;
+};
+
+} // namespace
+
+bool Netlink::GetRouteInfo(std::vector<Netlink::RoutingRule>* routing_rules) {
+ rtmsg route_message{};
+ // Only manipulate main routing table.
+ route_message.rtm_table = RT_TABLE_MAIN;
+
+ auto message = RouteMessage::New(RtnetlinkMessage::Operation::GET,
+ NLM_F_REQUEST | NLM_F_ROOT | NLM_F_MATCH,
+ seq_, getpid(), &route_message);
+
+ if (!Send(message.BuildIoVec().get(), message.IoVecSize())) {
+ QUIC_LOG(ERROR) << "send failed";
+ return false;
+ }
+
+ RoutingRuleParser parser(routing_rules);
+ if (!Recv(seq_++, &parser)) {
+ QUIC_LOG(ERROR) << "recv failed";
+ return false;
+ }
+
+ return true;
+}
+
+bool Netlink::ChangeRoute(Netlink::Verb verb,
+ uint32_t table,
+ const IpRange& destination_subnet,
+ uint8_t scope,
+ QuicIpAddress preferred_source,
+ int32_t interface_index) {
+ if (!destination_subnet.prefix().IsInitialized()) {
+ return false;
+ }
+ if (destination_subnet.address_family() != IpAddressFamily::IP_V4 &&
+ destination_subnet.address_family() != IpAddressFamily::IP_V6) {
+ return false;
+ }
+ if (preferred_source.IsInitialized() &&
+ preferred_source.address_family() !=
+ destination_subnet.address_family()) {
+ return false;
+ }
+
+ RtnetlinkMessage::Operation operation;
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+ switch (verb) {
+ case Verb::kAdd:
+ operation = RtnetlinkMessage::Operation::NEW;
+ // Setting NLM_F_EXCL so that an existing entry for this subnet will fail
+ // the request. NLM_F_CREATE is necessary to indicate this is trying to
+ // create a new entry - simply having RTM_NEWROUTE is not enough even the
+ // name suggests so.
+ flags |= NLM_F_EXCL | NLM_F_CREATE;
+ break;
+ case Verb::kRemove:
+ operation = RtnetlinkMessage::Operation::DEL;
+ break;
+ case Verb::kReplace:
+ operation = RtnetlinkMessage::Operation::NEW;
+ // Setting NLM_F_REPLACE to tell the kernel that existing entry for this
+ // subnet should be replaced.
+ flags |= NLM_F_REPLACE | NLM_F_CREATE;
+ break;
+ }
+
+ struct rtmsg route_message;
+ memset(&route_message, 0, sizeof(route_message));
+ route_message.rtm_family =
+ destination_subnet.address_family() == IpAddressFamily::IP_V4 ? AF_INET
+ : AF_INET6;
+ // rtm_dst_len and rtm_src_len are actually the subnet prefix lengths. Poor
+ // naming.
+ route_message.rtm_dst_len = destination_subnet.prefix_length();
+ // 0 means no source subnet for this rule.
+ route_message.rtm_src_len = 0;
+ // Only program the main table. Other tables are intended for the kernel to
+ // manage.
+ route_message.rtm_table = RT_TABLE_MAIN;
+ // Use RTPROT_UNSPEC to match all the different protocol. Rules added by
+ // kernel have RTPROT_KERNEL. Rules added by the root user have RTPROT_STATIC
+ // instead.
+ route_message.rtm_protocol =
+ verb == Verb::kRemove ? RTPROT_UNSPEC : RTPROT_STATIC;
+ route_message.rtm_scope = scope;
+ // Only add unicast routing rule.
+ route_message.rtm_type = RTN_UNICAST;
+ auto message =
+ RouteMessage::New(operation, flags, seq_, getpid(), &route_message);
+
+ message.AppendAttribute(RTA_TABLE, &table, sizeof(table));
+
+ // RTA_OIF is the target interface for this rule.
+ message.AppendAttribute(RTA_OIF, &interface_index, sizeof(interface_index));
+ // The actual destination subnet must be truncated of all the tailing zeros.
+ message.AppendAttribute(
+ RTA_DST,
+ reinterpret_cast<const void*>(
+ destination_subnet.prefix().ToPackedString().c_str()),
+ destination_subnet.prefix().ToPackedString().size());
+ // This is the source address to use in the IP packet should this routing rule
+ // is used.
+ if (preferred_source.IsInitialized()) {
+ message.AppendAttribute(RTA_PREFSRC,
+ reinterpret_cast<const void*>(
+ preferred_source.ToPackedString().c_str()),
+ preferred_source.ToPackedString().size());
+ }
+
+ if (!Send(message.BuildIoVec().get(), message.IoVecSize())) {
+ QUIC_LOG(ERROR) << "send failed";
+ return false;
+ }
+
+ UnknownParser parser;
+ if (!Recv(seq_++, &parser)) {
+ QUIC_LOG(ERROR) << "receive failed.";
+ return false;
+ }
+ return true;
+}
+
+namespace {
+
+class IpRuleParser : public NetlinkParserInterface {
+ public:
+ explicit IpRuleParser(std::vector<Netlink::IpRule>* ip_rules)
+ : ip_rules_(ip_rules) {}
+
+ void Run(struct nlmsghdr* netlink_message) override {
+ if (netlink_message->nlmsg_type != RTM_NEWRULE) {
+ QUIC_LOG(WARNING) << QuicStrCat(
+ "Unexpected nlmsg_type: ", netlink_message->nlmsg_type,
+ " expected: ", RTM_NEWRULE);
+ return;
+ }
+
+ auto* rule = reinterpret_cast<rtmsg*>(NLMSG_DATA(netlink_message));
+ int payload_length = RTM_PAYLOAD(netlink_message);
+
+ if (rule->rtm_family != AF_INET6) {
+ QUIC_LOG(ERROR) << QuicStrCat("Unexpected family: ", rule->rtm_family);
+ return;
+ }
+
+ Netlink::IpRule ip_rule;
+ ip_rule.table = rule->rtm_table;
+
+ struct rtattr* rta;
+ for (rta = RTM_RTA(rule); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case RTA_TABLE: {
+ ip_rule.table = *reinterpret_cast<uint32_t*>(RTA_DATA(rta));
+ break;
+ }
+ case RTA_SRC: {
+ QuicIpAddress src_addr;
+ src_addr.FromPackedString(reinterpret_cast<char*>(RTA_DATA(rta)),
+ RTA_PAYLOAD(rta));
+ IpRange src_range(src_addr, rule->rtm_src_len);
+ ip_rule.source_range = src_range;
+ break;
+ }
+ default: {
+ QUIC_VLOG(2) << QuicStrCat("Uninteresting attribute: ",
+ rta->rta_type);
+ }
+ }
+ }
+ ip_rules_->emplace_back(ip_rule);
+ }
+
+ private:
+ std::vector<Netlink::IpRule>* ip_rules_;
+};
+
+} // namespace
+
+bool Netlink::GetRuleInfo(std::vector<Netlink::IpRule>* ip_rules) {
+ rtmsg rule_message{};
+ rule_message.rtm_family = AF_INET6;
+
+ auto message = RuleMessage::New(RtnetlinkMessage::Operation::GET,
+ NLM_F_REQUEST | NLM_F_DUMP, seq_, getpid(),
+ &rule_message);
+
+ if (!Send(message.BuildIoVec().get(), message.IoVecSize())) {
+ QUIC_LOG(ERROR) << "send failed";
+ return false;
+ }
+
+ IpRuleParser parser(ip_rules);
+ if (!Recv(seq_++, &parser)) {
+ QUIC_LOG(ERROR) << "receive failed.";
+ return false;
+ }
+ return true;
+}
+
+bool Netlink::ChangeRule(Verb verb, uint32_t table, IpRange source_range) {
+ RtnetlinkMessage::Operation operation;
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ACK;
+
+ rtmsg rule_message{};
+ rule_message.rtm_family = AF_INET6;
+ rule_message.rtm_protocol = RTPROT_STATIC;
+ rule_message.rtm_scope = RT_SCOPE_UNIVERSE;
+ rule_message.rtm_table = RT_TABLE_UNSPEC;
+
+ rule_message.rtm_flags |= FIB_RULE_FIND_SADDR;
+
+ switch (verb) {
+ case Verb::kAdd:
+ if (!source_range.IsInitialized()) {
+ QUIC_LOG(ERROR) << "Source range must be initialized.";
+ return false;
+ }
+ operation = RtnetlinkMessage::Operation::NEW;
+ flags |= NLM_F_EXCL | NLM_F_CREATE;
+ rule_message.rtm_type = FRA_DST;
+ rule_message.rtm_src_len = source_range.prefix_length();
+ break;
+ case Verb::kRemove:
+ operation = RtnetlinkMessage::Operation::DEL;
+ break;
+ case Verb::kReplace:
+ QUIC_LOG(ERROR) << "Unsupported verb: kReplace";
+ return false;
+ }
+ auto message =
+ RuleMessage::New(operation, flags, seq_, getpid(), &rule_message);
+
+ message.AppendAttribute(RTA_TABLE, &table, sizeof(table));
+
+ if (source_range.IsInitialized()) {
+ std::string packed_src = source_range.prefix().ToPackedString();
+ message.AppendAttribute(RTA_SRC,
+ reinterpret_cast<const void*>(packed_src.c_str()),
+ packed_src.size());
+ }
+
+ if (!Send(message.BuildIoVec().get(), message.IoVecSize())) {
+ QUIC_LOG(ERROR) << "send failed";
+ return false;
+ }
+
+ UnknownParser parser;
+ if (!Recv(seq_++, &parser)) {
+ QUIC_LOG(ERROR) << "receive failed.";
+ return false;
+ }
+ return true;
+}
+
+bool Netlink::Send(struct iovec* iov, size_t iovlen) {
+ if (!OpenSocket()) {
+ QUIC_LOG(ERROR) << "can't open socket";
+ return false;
+ }
+
+ // an address for communicating with the kernel netlink code
+ sockaddr_nl netlink_address;
+ memset(&netlink_address, 0, sizeof(netlink_address));
+ netlink_address.nl_family = AF_NETLINK;
+ netlink_address.nl_pid = 0; // destination is kernel
+ netlink_address.nl_groups = 0; // no multicast
+
+ struct msghdr msg = {
+ &netlink_address, sizeof(netlink_address), iov, iovlen, nullptr, 0, 0};
+
+ if (kernel_->sendmsg(socket_fd_, &msg, 0) < 0) {
+ QUIC_LOG(ERROR) << "sendmsg failed";
+ CloseSocket();
+ return false;
+ }
+
+ return true;
+}
+
+bool Netlink::Recv(uint32_t seq, NetlinkParserInterface* parser) {
+ sockaddr_nl netlink_address;
+
+ // replies can span multiple packets
+ for (;;) {
+ socklen_t address_length = sizeof(netlink_address);
+
+ // First, call recvfrom with buffer size of 0 and MSG_PEEK | MSG_TRUNC set
+ // so that we know the size of the incoming packet before actually receiving
+ // it.
+ int next_packet_size = kernel_->recvfrom(
+ socket_fd_, recvbuf_.get(), /* len = */ 0, MSG_PEEK | MSG_TRUNC,
+ reinterpret_cast<struct sockaddr*>(&netlink_address), &address_length);
+ if (next_packet_size < 0) {
+ QUIC_LOG(ERROR)
+ << "error recvfrom with MSG_PEEK | MSG_TRUNC to get packet length.";
+ CloseSocket();
+ return false;
+ }
+ QUIC_VLOG(3) << "netlink packet size: " << next_packet_size;
+ if (next_packet_size > recvbuf_length_) {
+ QUIC_VLOG(2) << "resizing recvbuf to " << next_packet_size;
+ ResetRecvBuf(next_packet_size);
+ }
+
+ // Get the packet for real.
+ memset(recvbuf_.get(), 0, recvbuf_length_);
+ int len = kernel_->recvfrom(
+ socket_fd_, recvbuf_.get(), recvbuf_length_, /* flags = */ 0,
+ reinterpret_cast<struct sockaddr*>(&netlink_address), &address_length);
+ QUIC_VLOG(3) << "recvfrom returned: " << len;
+ if (len < 0) {
+ QUIC_LOG(INFO) << "can't receive netlink packet";
+ CloseSocket();
+ return false;
+ }
+
+ // there may be multiple nlmsg's in each reply packet
+ struct nlmsghdr* netlink_message;
+ for (netlink_message = reinterpret_cast<struct nlmsghdr*>(recvbuf_.get());
+ NLMSG_OK(netlink_message, len);
+ netlink_message = NLMSG_NEXT(netlink_message, len)) {
+ QUIC_VLOG(3) << "netlink_message->nlmsg_type = "
+ << netlink_message->nlmsg_type;
+ // make sure this is to us
+ if (netlink_message->nlmsg_seq != seq) {
+ QUIC_LOG(INFO) << "netlink_message not meant for us."
+ << " seq: " << seq
+ << " nlmsg_seq: " << netlink_message->nlmsg_seq;
+ continue;
+ }
+
+ // done with this whole reply (not just this particular packet)
+ if (netlink_message->nlmsg_type == NLMSG_DONE) {
+ return true;
+ }
+ if (netlink_message->nlmsg_type == NLMSG_ERROR) {
+ struct nlmsgerr* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ if (netlink_message->nlmsg_len <
+ NLMSG_LENGTH(sizeof(struct nlmsgerr))) {
+ QUIC_LOG(INFO) << "netlink_message ERROR truncated";
+ } else {
+ // an ACK
+ if (err->error == 0) {
+ QUIC_VLOG(3) << "Netlink sent an ACK";
+ return true;
+ }
+ QUIC_LOG(INFO) << "netlink_message ERROR: " << err->error;
+ }
+ return false;
+ }
+
+ parser->Run(netlink_message);
+ }
+ }
+}
+
+} // namespace quic
diff --git a/quic/qbone/platform/netlink.h b/quic/qbone/platform/netlink.h
new file mode 100644
index 0000000..591da0f
--- /dev/null
+++ b/quic/qbone/platform/netlink.h
@@ -0,0 +1,142 @@
+// 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_PLATFORM_NETLINK_H_
+#define QUICHE_QUIC_QBONE_PLATFORM_NETLINK_H_
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/ip_range.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/kernel_interface.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/netlink_interface.h"
+
+namespace quic {
+
+// A wrapper class to provide convenient methods of manipulating IP address and
+// routing table using netlink (man 7 netlink) socket. More specifically,
+// rtnetlink is used (man 7 rtnetlink).
+//
+// This class is not thread safe, but thread compatible, as long as callers can
+// make sure Send and Recv pairs are executed in sequence for a particular
+// query.
+class Netlink : public NetlinkInterface {
+ public:
+ explicit Netlink(KernelInterface* kernel);
+ ~Netlink() override;
+
+ // Gets the link information for the interface referred by the given
+ // interface_name.
+ //
+ // This is a synchronous communication. That should not be a problem since the
+ // kernel should answer immediately.
+ bool GetLinkInfo(const string& interface_name, LinkInfo* link_info) override;
+
+ // Gets the addresses for the given interface referred by the given
+ // interface_index.
+ //
+ // This is a synchronous communication. This should not be a problem since the
+ // kernel should answer immediately.
+ bool GetAddresses(int interface_index,
+ uint8_t unwanted_flags,
+ std::vector<AddressInfo>* addresses,
+ int* num_ipv6_nodad_dadfailed_addresses) override;
+
+ // Performs the given verb that modifies local addresses on the given
+ // interface_index.
+ //
+ // additional_attributes are RTAs (man 7 rtnelink) that will be sent together
+ // with the netlink message. Note that rta_len in each RTA is used to decide
+ // the length of the payload. The caller is responsible for making sure
+ // payload bytes are accessible after the RTA header.
+ bool ChangeLocalAddress(
+ uint32_t interface_index,
+ Verb verb,
+ const QuicIpAddress& address,
+ uint8_t prefix_length,
+ uint8_t ifa_flags,
+ uint8_t ifa_scope,
+ const std::vector<struct rtattr*>& additional_attributes) override;
+
+ // Gets the list of routing rules from the main routing table (RT_TABLE_MAIN),
+ // which is programmable.
+ //
+ // This is a synchronous communication. This should not be a problem since the
+ // kernel should answer immediately.
+ bool GetRouteInfo(std::vector<RoutingRule>* routing_rules) override;
+
+ // Performs the given Verb on the matching rule in the main routing table
+ // (RT_TABLE_MAIN).
+ //
+ // preferred_source can be !IsInitialized(), in which case it will be omitted.
+ //
+ // For Verb::kRemove, rule matching is done by (destination_subnet, scope,
+ // preferred_source, interface_index). Return true if a matching rule is
+ // found. interface_index can be 0 for wilecard.
+ //
+ // For Verb::kAdd, rule matching is done by destination_subnet. If a rule for
+ // the given destination_subnet already exists, nothing will happen and false
+ // is returned.
+ //
+ // For Verb::kReplace, rule matching is done by destination_subnet. If no
+ // matching rule is found, a new entry will be created.
+ bool ChangeRoute(Netlink::Verb verb,
+ uint32_t table,
+ const IpRange& destination_subnet,
+ uint8_t scope,
+ QuicIpAddress preferred_source,
+ int32_t interface_index) override;
+
+ // Returns the set of all rules in the routing policy database.
+ bool GetRuleInfo(std::vector<Netlink::IpRule>* ip_rules) override;
+
+ // Performs the give verb on the matching rule in the routing policy database.
+ // When deleting a rule, the |source_range| may be unspecified, in which case
+ // the lowest priority rule from |table| will be removed. When adding a rule,
+ // the |source_address| must be specified.
+ bool ChangeRule(Verb verb, uint32_t table, IpRange source_range) override;
+
+ // Sends a netlink message to the kernel. iov and iovlen represents an array
+ // of struct iovec to be fed into sendmsg. The caller needs to make sure the
+ // message conform to what's expected by NLMSG_* macros.
+ //
+ // This can be useful if more flexibility is needed than the provided
+ // convenient methods can provide.
+ bool Send(struct iovec* iov, size_t iovlen) override;
+
+ // Receives a netlink message from the kernel.
+ // parser will be called on the caller's stack.
+ //
+ // This can be useful if more flexibility is needed than the provided
+ // convenient methods can provide.
+ // TODO(b/69412655): vectorize this.
+ bool Recv(uint32_t seq, NetlinkParserInterface* parser) override;
+
+ private:
+ // Reset the size of recvbuf_ to size. If size is 0, recvbuf_ will be nullptr.
+ void ResetRecvBuf(size_t size);
+
+ // Opens a netlink socket if not already opened.
+ bool OpenSocket();
+
+ // Closes the opened netlink socket. Noop if no netlink socket is opened.
+ void CloseSocket();
+
+ KernelInterface* kernel_;
+ int socket_fd_ = -1;
+ std::unique_ptr<char[]> recvbuf_ = nullptr;
+ size_t recvbuf_length_ = 0;
+ uint32_t seq_; // next msg sequence number
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_PLATFORM_NETLINK_H_
diff --git a/quic/qbone/platform/netlink_interface.h b/quic/qbone/platform/netlink_interface.h
new file mode 100644
index 0000000..447c8b2
--- /dev/null
+++ b/quic/qbone/platform/netlink_interface.h
@@ -0,0 +1,146 @@
+// 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_PLATFORM_NETLINK_INTERFACE_H_
+#define QUICHE_QUIC_QBONE_PLATFORM_NETLINK_INTERFACE_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/ip_range.h"
+
+namespace quic {
+
+constexpr int kHwAddrSize = 6;
+
+class NetlinkParserInterface {
+ public:
+ virtual ~NetlinkParserInterface() {}
+ virtual void Run(struct nlmsghdr* netlink_message) = 0;
+};
+
+// An interface providing convenience methods for manipulating IP address and
+// routing table using netlink (man 7 netlink) socket.
+class NetlinkInterface {
+ public:
+ virtual ~NetlinkInterface() = default;
+
+ // Link information returned from GetLinkInfo.
+ struct LinkInfo {
+ int index;
+ uint8_t type;
+ uint8_t hardware_address[kHwAddrSize];
+ uint8_t broadcast_address[kHwAddrSize];
+ size_t hardware_address_length; // 0 if no hardware address found
+ size_t broadcast_address_length; // 0 if no broadcast address found
+ };
+
+ // Gets the link information for the interface referred by the given
+ // interface_name.
+ virtual bool GetLinkInfo(const string& interface_name,
+ LinkInfo* link_info) = 0;
+
+ // Address information reported back from GetAddresses.
+ struct AddressInfo {
+ QuicIpAddress local_address;
+ QuicIpAddress interface_address;
+ uint8_t prefix_length = 0;
+ uint8_t scope = 0;
+ };
+
+ // Gets the addresses for the given interface referred by the given
+ // interface_index.
+ virtual bool GetAddresses(int interface_index,
+ uint8_t unwanted_flags,
+ std::vector<AddressInfo>* addresses,
+ int* num_ipv6_nodad_dadfailed_addresses) = 0;
+
+ enum class Verb {
+ kAdd,
+ kRemove,
+ kReplace,
+ };
+
+ // Performs the given verb that modifies local addresses on the given
+ // interface_index.
+ //
+ // additional_attributes are RTAs (man 7 rtnelink) that will be sent together
+ // with the netlink message. Note that rta_len in each RTA is used to decide
+ // the length of the payload. The caller is responsible for making sure
+ // payload bytes are accessible after the RTA header.
+ virtual bool ChangeLocalAddress(
+ uint32_t interface_index,
+ Verb verb,
+ const QuicIpAddress& address,
+ uint8_t prefix_length,
+ uint8_t ifa_flags,
+ uint8_t ifa_scope,
+ const std::vector<struct rtattr*>& additional_attributes) = 0;
+
+ // Routing rule reported back from GetRouteInfo.
+ struct RoutingRule {
+ uint32_t table;
+ IpRange destination_subnet;
+ QuicIpAddress preferred_source;
+ uint8_t scope;
+ int out_interface;
+ };
+
+ struct IpRule {
+ uint32_t table;
+ IpRange source_range;
+ };
+
+ // Gets the list of routing rules from the main routing table (RT_TABLE_MAIN),
+ // which is programmable.
+ virtual bool GetRouteInfo(std::vector<RoutingRule>* routing_rules) = 0;
+
+ // Performs the given Verb on the matching rule in the main routing table
+ // (RT_TABLE_MAIN).
+ //
+ // preferred_source can be !IsInitialized(), in which case it will be omitted.
+ //
+ // For Verb::kRemove, rule matching is done by (destination_subnet, scope,
+ // preferred_source, interface_index). Return true if a matching rule is
+ // found. interface_index can be 0 for wilecard.
+ //
+ // For Verb::kAdd, rule matching is done by destination_subnet. If a rule for
+ // the given destination_subnet already exists, nothing will happen and false
+ // is returned.
+ //
+ // For Verb::kReplace, rule matching is done by destination_subnet. If no
+ // matching rule is found, a new entry will be created.
+ virtual bool ChangeRoute(Verb verb,
+ uint32_t table,
+ const IpRange& destination_subnet,
+ uint8_t scope,
+ QuicIpAddress preferred_source,
+ int32_t interface_index) = 0;
+
+ // Returns the set of all rules in the routing policy database.
+ virtual bool GetRuleInfo(std::vector<IpRule>* ip_rules) = 0;
+
+ // Performs the give verb on the matching rule in the routing policy database.
+ // When deleting a rule, the |source_range| may be unspecified, in which case
+ // the lowest priority rule from |table| will be removed. When adding a rule,
+ // the |source_address| must be specified.
+ virtual bool ChangeRule(Verb verb, uint32_t table, IpRange source_range) = 0;
+
+ // Sends a netlink message to the kernel. iov and iovlen represents an array
+ // of struct iovec to be fed into sendmsg. The caller needs to make sure the
+ // message conform to what's expected by NLMSG_* macros.
+ //
+ // This can be useful if more flexibility is needed than the provided
+ // convenient methods can provide.
+ virtual bool Send(struct iovec* iov, size_t iovlen) = 0;
+
+ // Receives a netlink message from the kernel.
+ // parser will be called on the caller's stack.
+ //
+ // This can be useful if more flexibility is needed than the provided
+ // convenient methods can provide.
+ virtual bool Recv(uint32_t seq, NetlinkParserInterface* parser) = 0;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_PLATFORM_NETLINK_INTERFACE_H_
diff --git a/quic/qbone/platform/netlink_test.cc b/quic/qbone/platform/netlink_test.cc
new file mode 100644
index 0000000..024e0fb
--- /dev/null
+++ b/quic/qbone/platform/netlink_test.cc
@@ -0,0 +1,763 @@
+// 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/platform/netlink.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.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"
+#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h"
+
+namespace quic {
+namespace {
+
+using ::testing::_;
+using ::testing::Contains;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Return;
+using ::testing::Unused;
+
+const int kSocketFd = 101;
+
+class NetlinkTest : public QuicTest {
+ protected:
+ NetlinkTest() {
+ ON_CALL(mock_kernel_, socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE))
+ .WillByDefault(Invoke([this](Unused, Unused, Unused) {
+ EXPECT_CALL(mock_kernel_, close(kSocketFd)).WillOnce(Return(0));
+ return kSocketFd;
+ }));
+ }
+
+ void ExpectNetlinkPacket(
+ uint16_t type,
+ uint16_t flags,
+ const std::function<ssize_t(void* buf, size_t len, int seq)>&
+ recv_callback,
+ const std::function<void(const void* buf, size_t len)>& send_callback =
+ nullptr) {
+ static int seq = -1;
+ InSequence s;
+
+ EXPECT_CALL(mock_kernel_, sendmsg(kSocketFd, _, _))
+ .WillOnce(Invoke([this, type, flags, send_callback](
+ Unused, const struct msghdr* msg, int) {
+ EXPECT_EQ(sizeof(struct sockaddr_nl), msg->msg_namelen);
+ auto* nl_addr =
+ reinterpret_cast<const struct sockaddr_nl*>(msg->msg_name);
+ EXPECT_EQ(AF_NETLINK, nl_addr->nl_family);
+ EXPECT_EQ(0, nl_addr->nl_pid);
+ EXPECT_EQ(0, nl_addr->nl_groups);
+
+ EXPECT_GE(msg->msg_iovlen, 1);
+ EXPECT_GE(msg->msg_iov[0].iov_len, sizeof(struct nlmsghdr));
+
+ string buf;
+ for (int i = 0; i < msg->msg_iovlen; i++) {
+ buf.append(string(reinterpret_cast<char*>(msg->msg_iov[i].iov_base),
+ msg->msg_iov[i].iov_len));
+ }
+
+ auto* netlink_message =
+ reinterpret_cast<const struct nlmsghdr*>(buf.c_str());
+ EXPECT_EQ(type, netlink_message->nlmsg_type);
+ EXPECT_EQ(flags, netlink_message->nlmsg_flags);
+ EXPECT_GE(buf.size(), netlink_message->nlmsg_len);
+
+ if (send_callback != nullptr) {
+ send_callback(buf.c_str(), buf.size());
+ }
+
+ CHECK_EQ(seq, -1);
+ seq = netlink_message->nlmsg_seq;
+ return buf.size();
+ }));
+
+ EXPECT_CALL(mock_kernel_,
+ recvfrom(kSocketFd, _, 0, MSG_PEEK | MSG_TRUNC, _, _))
+ .WillOnce(Invoke([this, recv_callback](Unused, Unused, Unused, Unused,
+ struct sockaddr* src_addr,
+ socklen_t* addrlen) {
+ auto* nl_addr = reinterpret_cast<struct sockaddr_nl*>(src_addr);
+ nl_addr->nl_family = AF_NETLINK;
+ nl_addr->nl_pid = 0; // from kernel
+ nl_addr->nl_groups = 0; // no multicast
+
+ int ret = recv_callback(reply_packet_, sizeof(reply_packet_), seq);
+ CHECK_LE(ret, sizeof(reply_packet_));
+ return ret;
+ }));
+
+ EXPECT_CALL(mock_kernel_, recvfrom(kSocketFd, _, _, _, _, _))
+ .WillOnce(Invoke([recv_callback](Unused, void* buf, size_t len, Unused,
+ struct sockaddr* src_addr,
+ socklen_t* addrlen) {
+ auto* nl_addr = reinterpret_cast<struct sockaddr_nl*>(src_addr);
+ nl_addr->nl_family = AF_NETLINK;
+ nl_addr->nl_pid = 0; // from kernel
+ nl_addr->nl_groups = 0; // no multicast
+
+ int ret = recv_callback(buf, len, seq);
+ EXPECT_GE(len, ret);
+ seq = -1;
+ return ret;
+ }));
+ }
+
+ char reply_packet_[4096];
+ MockKernel mock_kernel_;
+};
+
+void AddRTA(struct nlmsghdr* netlink_message,
+ uint16_t type,
+ const void* data,
+ size_t len) {
+ auto* next_header_ptr = reinterpret_cast<char*>(netlink_message) +
+ NLMSG_ALIGN(netlink_message->nlmsg_len);
+
+ auto* rta = reinterpret_cast<struct rtattr*>(next_header_ptr);
+ rta->rta_type = type;
+ rta->rta_len = RTA_LENGTH(len);
+ memcpy(RTA_DATA(rta), data, len);
+
+ netlink_message->nlmsg_len =
+ NLMSG_ALIGN(netlink_message->nlmsg_len) + RTA_LENGTH(len);
+}
+
+void CreateIfinfomsg(struct nlmsghdr* netlink_message,
+ const string& interface_name,
+ uint16_t type,
+ int index,
+ unsigned int flags,
+ unsigned int change,
+ uint8_t address[],
+ int address_len,
+ uint8_t broadcast[],
+ int broadcast_len) {
+ auto* interface_info =
+ reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(netlink_message));
+ interface_info->ifi_family = AF_UNSPEC;
+ interface_info->ifi_type = type;
+ interface_info->ifi_index = index;
+ interface_info->ifi_flags = flags;
+ interface_info->ifi_change = change;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg));
+
+ // Add address
+ AddRTA(netlink_message, IFLA_ADDRESS, address, address_len);
+
+ // Add broadcast address
+ AddRTA(netlink_message, IFLA_BROADCAST, broadcast, broadcast_len);
+
+ // Add name
+ AddRTA(netlink_message, IFLA_IFNAME, interface_name.c_str(),
+ interface_name.size());
+}
+
+struct nlmsghdr* CreateNetlinkMessage(void* buf, // NOLINT
+ struct nlmsghdr* previous_netlink_message,
+ uint16_t type,
+ int seq) {
+ auto* next_header_ptr = reinterpret_cast<char*>(buf);
+ if (previous_netlink_message != nullptr) {
+ next_header_ptr = reinterpret_cast<char*>(previous_netlink_message) +
+ NLMSG_ALIGN(previous_netlink_message->nlmsg_len);
+ }
+ auto* netlink_message = reinterpret_cast<nlmsghdr*>(next_header_ptr);
+ netlink_message->nlmsg_len = NLMSG_LENGTH(0);
+ netlink_message->nlmsg_type = type;
+ netlink_message->nlmsg_flags = NLM_F_MULTI;
+ netlink_message->nlmsg_pid = 0; // from the kernel
+ netlink_message->nlmsg_seq = seq;
+
+ return netlink_message;
+}
+
+void CreateIfaddrmsg(struct nlmsghdr* nlm,
+ int interface_index,
+ unsigned char prefixlen,
+ unsigned char flags,
+ unsigned char scope,
+ QuicIpAddress ip) {
+ CHECK(ip.IsInitialized());
+ unsigned char family;
+ switch (ip.address_family()) {
+ case IpAddressFamily::IP_V4:
+ family = AF_INET;
+ break;
+ case IpAddressFamily::IP_V6:
+ family = AF_INET6;
+ break;
+ default:
+ QUIC_BUG << absl::StrCat("unexpected address family: ",
+ ip.address_family());
+ family = AF_UNSPEC;
+ }
+ auto* msg = reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(nlm));
+ msg->ifa_family = family;
+ msg->ifa_prefixlen = prefixlen;
+ msg->ifa_flags = flags;
+ msg->ifa_scope = scope;
+ msg->ifa_index = interface_index;
+ nlm->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg));
+
+ // Add local address
+ AddRTA(nlm, IFA_LOCAL, ip.ToPackedString().c_str(),
+ ip.ToPackedString().size());
+}
+
+void CreateRtmsg(struct nlmsghdr* nlm,
+ unsigned char family,
+ unsigned char destination_length,
+ unsigned char source_length,
+ unsigned char tos,
+ unsigned char table,
+ unsigned char protocol,
+ unsigned char scope,
+ unsigned char type,
+ unsigned int flags,
+ QuicIpAddress destination,
+ int interface_index) {
+ auto* msg = reinterpret_cast<struct rtmsg*>(NLMSG_DATA(nlm));
+ msg->rtm_family = family;
+ msg->rtm_dst_len = destination_length;
+ msg->rtm_src_len = source_length;
+ msg->rtm_tos = tos;
+ msg->rtm_table = table;
+ msg->rtm_protocol = protocol;
+ msg->rtm_scope = scope;
+ msg->rtm_type = type;
+ msg->rtm_flags = flags;
+ nlm->nlmsg_len = NLMSG_LENGTH(sizeof(struct rtmsg));
+
+ // Add destination
+ AddRTA(nlm, RTA_DST, destination.ToPackedString().c_str(),
+ destination.ToPackedString().size());
+
+ // Add egress interface
+ AddRTA(nlm, RTA_OIF, &interface_index, sizeof(interface_index));
+}
+
+TEST_F(NetlinkTest, GetLinkInfoWorks) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ uint8_t hwaddr[] = {'a', 'b', 'c', 'd', 'e', 'f'};
+ uint8_t bcaddr[] = {'c', 'b', 'a', 'f', 'e', 'd'};
+
+ ExpectNetlinkPacket(
+ RTM_GETLINK, NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST,
+ [this, &hwaddr, &bcaddr](void* buf, size_t len, int seq) {
+ int ret = 0;
+
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, RTM_NEWLINK, seq);
+ CreateIfinfomsg(netlink_message, "tun0", /* type = */ 1,
+ /* index = */ 7,
+ /* flags = */ 0,
+ /* change = */ 0xFFFFFFFF, hwaddr, 6, bcaddr, 6);
+ ret += NLMSG_ALIGN(netlink_message->nlmsg_len);
+
+ netlink_message =
+ CreateNetlinkMessage(buf, netlink_message, NLMSG_DONE, seq);
+ ret += NLMSG_ALIGN(netlink_message->nlmsg_len);
+
+ return ret;
+ });
+
+ Netlink::LinkInfo link_info;
+ EXPECT_TRUE(netlink->GetLinkInfo("tun0", &link_info));
+
+ EXPECT_EQ(7, link_info.index);
+ EXPECT_EQ(1, link_info.type);
+
+ for (int i = 0; i < link_info.hardware_address_length; ++i) {
+ EXPECT_EQ(hwaddr[i], link_info.hardware_address[i]);
+ }
+ for (int i = 0; i < link_info.broadcast_address_length; ++i) {
+ EXPECT_EQ(bcaddr[i], link_info.broadcast_address[i]);
+ }
+}
+
+TEST_F(NetlinkTest, GetAddressesWorks) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicUnorderedSet<std::string> addresses = {QuicIpAddress::Any4().ToString(),
+ QuicIpAddress::Any6().ToString()};
+
+ ExpectNetlinkPacket(
+ RTM_GETADDR, NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST,
+ [this, &addresses](void* buf, size_t len, int seq) {
+ int ret = 0;
+
+ struct nlmsghdr* nlm = nullptr;
+
+ for (const auto& address : addresses) {
+ QuicIpAddress ip;
+ ip.FromString(address);
+ nlm = CreateNetlinkMessage(buf, nlm, RTM_NEWADDR, seq);
+ CreateIfaddrmsg(nlm, /* interface_index = */ 7, /* prefixlen = */ 24,
+ /* flags = */ 0, /* scope = */ RT_SCOPE_UNIVERSE, ip);
+
+ ret += NLMSG_ALIGN(nlm->nlmsg_len);
+ }
+
+ // Create IPs with unwanted flags.
+ {
+ QuicIpAddress ip;
+ ip.FromString("10.0.0.1");
+ nlm = CreateNetlinkMessage(buf, nlm, RTM_NEWADDR, seq);
+ CreateIfaddrmsg(nlm, /* interface_index = */ 7, /* prefixlen = */ 16,
+ /* flags = */ IFA_F_OPTIMISTIC, /* scope = */
+ RT_SCOPE_UNIVERSE, ip);
+
+ ret += NLMSG_ALIGN(nlm->nlmsg_len);
+
+ ip.FromString("10.0.0.2");
+ nlm = CreateNetlinkMessage(buf, nlm, RTM_NEWADDR, seq);
+ CreateIfaddrmsg(nlm, /* interface_index = */ 7, /* prefixlen = */ 16,
+ /* flags = */ IFA_F_TENTATIVE, /* scope = */
+ RT_SCOPE_UNIVERSE, ip);
+
+ ret += NLMSG_ALIGN(nlm->nlmsg_len);
+ }
+
+ nlm = CreateNetlinkMessage(buf, nlm, NLMSG_DONE, seq);
+ ret += NLMSG_ALIGN(nlm->nlmsg_len);
+
+ return ret;
+ });
+
+ std::vector<Netlink::AddressInfo> reported_addresses;
+ int num_ipv6_nodad_dadfailed_addresses = 0;
+ EXPECT_TRUE(netlink->GetAddresses(7, IFA_F_TENTATIVE | IFA_F_OPTIMISTIC,
+ &reported_addresses,
+ &num_ipv6_nodad_dadfailed_addresses));
+
+ for (const auto& reported_address : reported_addresses) {
+ EXPECT_TRUE(reported_address.local_address.IsInitialized());
+ EXPECT_FALSE(reported_address.interface_address.IsInitialized());
+ EXPECT_THAT(addresses, Contains(reported_address.local_address.ToString()));
+ addresses.erase(reported_address.local_address.ToString());
+
+ EXPECT_EQ(24, reported_address.prefix_length);
+ }
+
+ EXPECT_TRUE(addresses.empty());
+}
+
+TEST_F(NetlinkTest, ChangeLocalAddressAdd) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress ip = QuicIpAddress::Any6();
+ ExpectNetlinkPacket(
+ RTM_NEWADDR, NLM_F_ACK | NLM_F_REQUEST,
+ [](void* buf, size_t len, int seq) {
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, NLMSG_ERROR, seq);
+ auto* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ // Ack the request
+ err->error = 0;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ return netlink_message->nlmsg_len;
+ },
+ [ip](const void* buf, size_t len) {
+ auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
+ auto* ifa = reinterpret_cast<const struct ifaddrmsg*>(
+ NLMSG_DATA(netlink_message));
+ EXPECT_EQ(19, ifa->ifa_prefixlen);
+ EXPECT_EQ(RT_SCOPE_UNIVERSE, ifa->ifa_scope);
+ EXPECT_EQ(IFA_F_PERMANENT, ifa->ifa_flags);
+ EXPECT_EQ(7, ifa->ifa_index);
+ EXPECT_EQ(AF_INET6, ifa->ifa_family);
+
+ const struct rtattr* rta;
+ int payload_length = IFA_PAYLOAD(netlink_message);
+ int num_rta = 0;
+ for (rta = IFA_RTA(ifa); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case IFA_LOCAL: {
+ EXPECT_EQ(ip.ToPackedString().size(), RTA_PAYLOAD(rta));
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(ip, address);
+ break;
+ }
+ case IFA_CACHEINFO: {
+ EXPECT_EQ(sizeof(struct ifa_cacheinfo), RTA_PAYLOAD(rta));
+ const auto* cache_info =
+ reinterpret_cast<const struct ifa_cacheinfo*>(RTA_DATA(rta));
+ EXPECT_EQ(8, cache_info->ifa_prefered); // common_typos_disable
+ EXPECT_EQ(6, cache_info->ifa_valid);
+ EXPECT_EQ(4, cache_info->cstamp);
+ EXPECT_EQ(2, cache_info->tstamp);
+ break;
+ }
+ default:
+ EXPECT_TRUE(false) << "Seeing rtattr that should not exist";
+ }
+ ++num_rta;
+ }
+ EXPECT_EQ(2, num_rta);
+ });
+
+ struct {
+ struct rtattr rta;
+ struct ifa_cacheinfo cache_info;
+ } additional_rta;
+
+ additional_rta.rta.rta_type = IFA_CACHEINFO;
+ additional_rta.rta.rta_len = RTA_LENGTH(sizeof(struct ifa_cacheinfo));
+ additional_rta.cache_info.ifa_prefered = 8;
+ additional_rta.cache_info.ifa_valid = 6;
+ additional_rta.cache_info.cstamp = 4;
+ additional_rta.cache_info.tstamp = 2;
+
+ EXPECT_TRUE(netlink->ChangeLocalAddress(7, Netlink::Verb::kAdd, ip, 19,
+ IFA_F_PERMANENT, RT_SCOPE_UNIVERSE,
+ {&additional_rta.rta}));
+}
+
+TEST_F(NetlinkTest, ChangeLocalAddressRemove) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress ip = QuicIpAddress::Any4();
+ ExpectNetlinkPacket(
+ RTM_DELADDR, NLM_F_ACK | NLM_F_REQUEST,
+ [](void* buf, size_t len, int seq) {
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, NLMSG_ERROR, seq);
+ auto* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ // Ack the request
+ err->error = 0;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ return netlink_message->nlmsg_len;
+ },
+ [ip](const void* buf, size_t len) {
+ auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
+ auto* ifa = reinterpret_cast<const struct ifaddrmsg*>(
+ NLMSG_DATA(netlink_message));
+ EXPECT_EQ(32, ifa->ifa_prefixlen);
+ EXPECT_EQ(RT_SCOPE_UNIVERSE, ifa->ifa_scope);
+ EXPECT_EQ(0, ifa->ifa_flags);
+ EXPECT_EQ(7, ifa->ifa_index);
+ EXPECT_EQ(AF_INET, ifa->ifa_family);
+
+ const struct rtattr* rta;
+ int payload_length = IFA_PAYLOAD(netlink_message);
+ int num_rta = 0;
+ for (rta = IFA_RTA(ifa); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case IFA_LOCAL: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(in_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(ip, address);
+ break;
+ }
+ default:
+ EXPECT_TRUE(false) << "Seeing rtattr that should not exist";
+ }
+ ++num_rta;
+ }
+ EXPECT_EQ(1, num_rta);
+ });
+
+ EXPECT_TRUE(netlink->ChangeLocalAddress(7, Netlink::Verb::kRemove, ip, 32, 0,
+ RT_SCOPE_UNIVERSE, {}));
+}
+
+TEST_F(NetlinkTest, GetRouteInfoWorks) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress destination;
+ ASSERT_TRUE(destination.FromString("f800::2"));
+ ExpectNetlinkPacket(RTM_GETROUTE, NLM_F_ROOT | NLM_F_MATCH | NLM_F_REQUEST,
+ [destination](void* buf, size_t len, int seq) {
+ int ret = 0;
+ struct nlmsghdr* netlink_message = CreateNetlinkMessage(
+ buf, nullptr, RTM_NEWROUTE, seq);
+ CreateRtmsg(netlink_message, AF_INET6, 48, 0, 0,
+ RT_TABLE_MAIN, RTPROT_STATIC, RT_SCOPE_LINK,
+ RTN_UNICAST, 0, destination, 7);
+ ret += NLMSG_ALIGN(netlink_message->nlmsg_len);
+
+ netlink_message = CreateNetlinkMessage(
+ buf, netlink_message, NLMSG_DONE, seq);
+ ret += NLMSG_ALIGN(netlink_message->nlmsg_len);
+
+ QUIC_LOG(INFO) << "ret: " << ret;
+ return ret;
+ });
+
+ std::vector<Netlink::RoutingRule> routing_rules;
+ EXPECT_TRUE(netlink->GetRouteInfo(&routing_rules));
+
+ ASSERT_EQ(1, routing_rules.size());
+ EXPECT_EQ(RT_SCOPE_LINK, routing_rules[0].scope);
+ EXPECT_EQ(IpRange(destination, 48).ToString(),
+ routing_rules[0].destination_subnet.ToString());
+ EXPECT_FALSE(routing_rules[0].preferred_source.IsInitialized());
+ EXPECT_EQ(7, routing_rules[0].out_interface);
+}
+
+TEST_F(NetlinkTest, ChangeRouteAdd) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress preferred_ip;
+ preferred_ip.FromString("ff80:dead:beef::1");
+ IpRange subnet;
+ subnet.FromString("ff80:dead:beef::/48");
+ int egress_interface_index = 7;
+ ExpectNetlinkPacket(
+ RTM_NEWROUTE, NLM_F_ACK | NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL,
+ [](void* buf, size_t len, int seq) {
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, NLMSG_ERROR, seq);
+ auto* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ // Ack the request
+ err->error = 0;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ return netlink_message->nlmsg_len;
+ },
+ [preferred_ip, subnet, egress_interface_index](const void* buf,
+ size_t len) {
+ auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
+ auto* rtm =
+ reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(netlink_message));
+ EXPECT_EQ(AF_INET6, rtm->rtm_family);
+ EXPECT_EQ(48, rtm->rtm_dst_len);
+ EXPECT_EQ(0, rtm->rtm_src_len);
+ EXPECT_EQ(RT_TABLE_MAIN, rtm->rtm_table);
+ EXPECT_EQ(RTPROT_STATIC, rtm->rtm_protocol);
+ EXPECT_EQ(RT_SCOPE_LINK, rtm->rtm_scope);
+ EXPECT_EQ(RTN_UNICAST, rtm->rtm_type);
+
+ const struct rtattr* rta;
+ int payload_length = RTM_PAYLOAD(netlink_message);
+ int num_rta = 0;
+ for (rta = RTM_RTA(rtm); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case RTA_PREFSRC: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(preferred_ip, address);
+ break;
+ }
+ case RTA_OIF: {
+ ASSERT_EQ(sizeof(int), RTA_PAYLOAD(rta));
+ const auto* interface_index =
+ reinterpret_cast<const int*>(RTA_DATA(rta));
+ EXPECT_EQ(egress_interface_index, *interface_index);
+ break;
+ }
+ case RTA_DST: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(subnet.ToString(),
+ IpRange(address, rtm->rtm_dst_len).ToString());
+ break;
+ }
+ case RTA_TABLE: {
+ ASSERT_EQ(*reinterpret_cast<uint32_t*>(RTA_DATA(rta)),
+ QboneConstants::kQboneRouteTableId);
+ break;
+ }
+ default:
+ EXPECT_TRUE(false) << "Seeing rtattr that should not be sent";
+ }
+ ++num_rta;
+ }
+ EXPECT_EQ(4, num_rta);
+ });
+ EXPECT_TRUE(netlink->ChangeRoute(
+ Netlink::Verb::kAdd, QboneConstants::kQboneRouteTableId, subnet,
+ RT_SCOPE_LINK, preferred_ip, egress_interface_index));
+}
+
+TEST_F(NetlinkTest, ChangeRouteRemove) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress preferred_ip;
+ preferred_ip.FromString("ff80:dead:beef::1");
+ IpRange subnet;
+ subnet.FromString("ff80:dead:beef::/48");
+ int egress_interface_index = 7;
+ ExpectNetlinkPacket(
+ RTM_DELROUTE, NLM_F_ACK | NLM_F_REQUEST,
+ [](void* buf, size_t len, int seq) {
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, NLMSG_ERROR, seq);
+ auto* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ // Ack the request
+ err->error = 0;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ return netlink_message->nlmsg_len;
+ },
+ [preferred_ip, subnet, egress_interface_index](const void* buf,
+ size_t len) {
+ auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
+ auto* rtm =
+ reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(netlink_message));
+ EXPECT_EQ(AF_INET6, rtm->rtm_family);
+ EXPECT_EQ(48, rtm->rtm_dst_len);
+ EXPECT_EQ(0, rtm->rtm_src_len);
+ EXPECT_EQ(RT_TABLE_MAIN, rtm->rtm_table);
+ EXPECT_EQ(RTPROT_UNSPEC, rtm->rtm_protocol);
+ EXPECT_EQ(RT_SCOPE_LINK, rtm->rtm_scope);
+ EXPECT_EQ(RTN_UNICAST, rtm->rtm_type);
+
+ const struct rtattr* rta;
+ int payload_length = RTM_PAYLOAD(netlink_message);
+ int num_rta = 0;
+ for (rta = RTM_RTA(rtm); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case RTA_PREFSRC: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(preferred_ip, address);
+ break;
+ }
+ case RTA_OIF: {
+ ASSERT_EQ(sizeof(int), RTA_PAYLOAD(rta));
+ const auto* interface_index =
+ reinterpret_cast<const int*>(RTA_DATA(rta));
+ EXPECT_EQ(egress_interface_index, *interface_index);
+ break;
+ }
+ case RTA_DST: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(subnet.ToString(),
+ IpRange(address, rtm->rtm_dst_len).ToString());
+ break;
+ }
+ case RTA_TABLE: {
+ ASSERT_EQ(*reinterpret_cast<uint32_t*>(RTA_DATA(rta)),
+ QboneConstants::kQboneRouteTableId);
+ break;
+ }
+ default:
+ EXPECT_TRUE(false) << "Seeing rtattr that should not be sent";
+ }
+ ++num_rta;
+ }
+ EXPECT_EQ(4, num_rta);
+ });
+ EXPECT_TRUE(netlink->ChangeRoute(
+ Netlink::Verb::kRemove, QboneConstants::kQboneRouteTableId, subnet,
+ RT_SCOPE_LINK, preferred_ip, egress_interface_index));
+}
+
+TEST_F(NetlinkTest, ChangeRouteReplace) {
+ auto netlink = QuicMakeUnique<Netlink>(&mock_kernel_);
+
+ QuicIpAddress preferred_ip;
+ preferred_ip.FromString("ff80:dead:beef::1");
+ IpRange subnet;
+ subnet.FromString("ff80:dead:beef::/48");
+ int egress_interface_index = 7;
+ ExpectNetlinkPacket(
+ RTM_NEWROUTE, NLM_F_ACK | NLM_F_REQUEST | NLM_F_CREATE | NLM_F_REPLACE,
+ [](void* buf, size_t len, int seq) {
+ struct nlmsghdr* netlink_message =
+ CreateNetlinkMessage(buf, nullptr, NLMSG_ERROR, seq);
+ auto* err =
+ reinterpret_cast<struct nlmsgerr*>(NLMSG_DATA(netlink_message));
+ // Ack the request
+ err->error = 0;
+ netlink_message->nlmsg_len = NLMSG_LENGTH(sizeof(struct nlmsgerr));
+ return netlink_message->nlmsg_len;
+ },
+ [preferred_ip, subnet, egress_interface_index](const void* buf,
+ size_t len) {
+ auto* netlink_message = reinterpret_cast<const struct nlmsghdr*>(buf);
+ auto* rtm =
+ reinterpret_cast<const struct rtmsg*>(NLMSG_DATA(netlink_message));
+ EXPECT_EQ(AF_INET6, rtm->rtm_family);
+ EXPECT_EQ(48, rtm->rtm_dst_len);
+ EXPECT_EQ(0, rtm->rtm_src_len);
+ EXPECT_EQ(RT_TABLE_MAIN, rtm->rtm_table);
+ EXPECT_EQ(RTPROT_STATIC, rtm->rtm_protocol);
+ EXPECT_EQ(RT_SCOPE_LINK, rtm->rtm_scope);
+ EXPECT_EQ(RTN_UNICAST, rtm->rtm_type);
+
+ const struct rtattr* rta;
+ int payload_length = RTM_PAYLOAD(netlink_message);
+ int num_rta = 0;
+ for (rta = RTM_RTA(rtm); RTA_OK(rta, payload_length);
+ rta = RTA_NEXT(rta, payload_length)) {
+ switch (rta->rta_type) {
+ case RTA_PREFSRC: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(preferred_ip, address);
+ break;
+ }
+ case RTA_OIF: {
+ ASSERT_EQ(sizeof(int), RTA_PAYLOAD(rta));
+ const auto* interface_index =
+ reinterpret_cast<const int*>(RTA_DATA(rta));
+ EXPECT_EQ(egress_interface_index, *interface_index);
+ break;
+ }
+ case RTA_DST: {
+ const auto* raw_address =
+ reinterpret_cast<const char*>(RTA_DATA(rta));
+ ASSERT_EQ(sizeof(struct in6_addr), RTA_PAYLOAD(rta));
+ QuicIpAddress address;
+ address.FromPackedString(raw_address, RTA_PAYLOAD(rta));
+ EXPECT_EQ(subnet.ToString(),
+ IpRange(address, rtm->rtm_dst_len).ToString());
+ break;
+ }
+ case RTA_TABLE: {
+ ASSERT_EQ(*reinterpret_cast<uint32_t*>(RTA_DATA(rta)),
+ QboneConstants::kQboneRouteTableId);
+ break;
+ }
+ default:
+ EXPECT_TRUE(false) << "Seeing rtattr that should not be sent";
+ }
+ ++num_rta;
+ }
+ EXPECT_EQ(4, num_rta);
+ });
+ EXPECT_TRUE(netlink->ChangeRoute(
+ Netlink::Verb::kReplace, QboneConstants::kQboneRouteTableId, subnet,
+ RT_SCOPE_LINK, preferred_ip, egress_interface_index));
+}
+
+} // namespace
+} // namespace quic
diff --git a/quic/qbone/platform/rtnetlink_message.cc b/quic/qbone/platform/rtnetlink_message.cc
new file mode 100644
index 0000000..f35628a
--- /dev/null
+++ b/quic/qbone/platform/rtnetlink_message.cc
@@ -0,0 +1,177 @@
+// 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/platform/rtnetlink_message.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+
+namespace quic {
+
+RtnetlinkMessage::RtnetlinkMessage(uint16_t type,
+ uint16_t flags,
+ uint32_t seq,
+ uint32_t pid,
+ const void* payload_header,
+ size_t payload_header_length) {
+ auto* buf = new uint8_t[NLMSG_SPACE(payload_header_length)];
+ memset(buf, 0, NLMSG_SPACE(payload_header_length));
+
+ auto* message_header = reinterpret_cast<struct nlmsghdr*>(buf);
+ message_header->nlmsg_len = NLMSG_LENGTH(payload_header_length);
+ message_header->nlmsg_type = type;
+ message_header->nlmsg_flags = flags;
+ message_header->nlmsg_seq = seq;
+ message_header->nlmsg_pid = pid;
+
+ if (payload_header != nullptr) {
+ memcpy(NLMSG_DATA(message_header), payload_header, payload_header_length);
+ }
+ message_.push_back({buf, NLMSG_SPACE(payload_header_length)});
+}
+
+RtnetlinkMessage::~RtnetlinkMessage() {
+ for (const auto& iov : message_) {
+ delete[] reinterpret_cast<uint8_t*>(iov.iov_base);
+ }
+}
+
+void RtnetlinkMessage::AppendAttribute(uint16_t type,
+ const void* data,
+ uint16_t data_length) {
+ auto* buf = new uint8_t[RTA_SPACE(data_length)];
+ memset(buf, 0, RTA_SPACE(data_length));
+
+ auto* rta = reinterpret_cast<struct rtattr*>(buf);
+ static_assert(sizeof(uint16_t) == sizeof(rta->rta_len),
+ "struct rtattr uses unsigned short, it's no longer 16bits");
+ static_assert(sizeof(uint16_t) == sizeof(rta->rta_type),
+ "struct rtattr uses unsigned short, it's no longer 16bits");
+
+ rta->rta_len = RTA_LENGTH(data_length);
+ rta->rta_type = type;
+ memcpy(RTA_DATA(rta), data, data_length);
+
+ message_.push_back({buf, RTA_SPACE(data_length)});
+ AdjustMessageLength(rta->rta_len);
+}
+
+std::unique_ptr<struct iovec[]> RtnetlinkMessage::BuildIoVec() const {
+ auto message = QuicMakeUnique<struct iovec[]>(message_.size());
+ int idx = 0;
+ for (const auto& vec : message_) {
+ message[idx++] = vec;
+ }
+ return message;
+}
+
+size_t RtnetlinkMessage::IoVecSize() const {
+ return message_.size();
+}
+
+void RtnetlinkMessage::AdjustMessageLength(size_t additional_data_length) {
+ MessageHeader()->nlmsg_len =
+ NLMSG_ALIGN(MessageHeader()->nlmsg_len) + additional_data_length;
+}
+
+struct nlmsghdr* RtnetlinkMessage::MessageHeader() {
+ return reinterpret_cast<struct nlmsghdr*>(message_[0].iov_base);
+}
+
+LinkMessage LinkMessage::New(RtnetlinkMessage::Operation request_operation,
+ uint16_t flags,
+ uint32_t seq,
+ uint32_t pid,
+ const struct ifinfomsg* interface_info_header) {
+ uint16_t request_type;
+ switch (request_operation) {
+ case RtnetlinkMessage::Operation::NEW:
+ request_type = RTM_NEWLINK;
+ break;
+ case RtnetlinkMessage::Operation::DEL:
+ request_type = RTM_DELLINK;
+ break;
+ case RtnetlinkMessage::Operation::GET:
+ request_type = RTM_GETLINK;
+ break;
+ }
+ bool is_get = request_type == RTM_GETLINK;
+
+ if (is_get) {
+ struct rtgenmsg g = {AF_UNSPEC};
+ return LinkMessage(request_type, flags, seq, pid, &g, sizeof(g));
+ }
+ return LinkMessage(request_type, flags, seq, pid, interface_info_header,
+ sizeof(struct ifinfomsg));
+}
+
+AddressMessage AddressMessage::New(
+ RtnetlinkMessage::Operation request_operation,
+ uint16_t flags,
+ uint32_t seq,
+ uint32_t pid,
+ const struct ifaddrmsg* interface_address_header) {
+ uint16_t request_type;
+ switch (request_operation) {
+ case RtnetlinkMessage::Operation::NEW:
+ request_type = RTM_NEWADDR;
+ break;
+ case RtnetlinkMessage::Operation::DEL:
+ request_type = RTM_DELADDR;
+ break;
+ case RtnetlinkMessage::Operation::GET:
+ request_type = RTM_GETADDR;
+ break;
+ }
+ bool is_get = request_type == RTM_GETADDR;
+
+ if (is_get) {
+ struct rtgenmsg g = {AF_UNSPEC};
+ return AddressMessage(request_type, flags, seq, pid, &g, sizeof(g));
+ }
+ return AddressMessage(request_type, flags, seq, pid, interface_address_header,
+ sizeof(struct ifaddrmsg));
+}
+
+RouteMessage RouteMessage::New(RtnetlinkMessage::Operation request_operation,
+ uint16_t flags,
+ uint32_t seq,
+ uint32_t pid,
+ const struct rtmsg* route_message_header) {
+ uint16_t request_type;
+ switch (request_operation) {
+ case RtnetlinkMessage::Operation::NEW:
+ request_type = RTM_NEWROUTE;
+ break;
+ case RtnetlinkMessage::Operation::DEL:
+ request_type = RTM_DELROUTE;
+ break;
+ case RtnetlinkMessage::Operation::GET:
+ request_type = RTM_GETROUTE;
+ break;
+ }
+ return RouteMessage(request_type, flags, seq, pid, route_message_header,
+ sizeof(struct rtmsg));
+}
+
+RuleMessage RuleMessage::New(RtnetlinkMessage::Operation request_operation,
+ uint16_t flags,
+ uint32_t seq,
+ uint32_t pid,
+ const struct rtmsg* rule_message_header) {
+ uint16_t request_type;
+ switch (request_operation) {
+ case RtnetlinkMessage::Operation::NEW:
+ request_type = RTM_NEWRULE;
+ break;
+ case RtnetlinkMessage::Operation::DEL:
+ request_type = RTM_DELRULE;
+ break;
+ case RtnetlinkMessage::Operation::GET:
+ request_type = RTM_GETRULE;
+ break;
+ }
+ return RuleMessage(request_type, flags, seq, pid, rule_message_header,
+ sizeof(rtmsg));
+}
+} // namespace quic
diff --git a/quic/qbone/platform/rtnetlink_message.h b/quic/qbone/platform/rtnetlink_message.h
new file mode 100644
index 0000000..0412d54
--- /dev/null
+++ b/quic/qbone/platform/rtnetlink_message.h
@@ -0,0 +1,126 @@
+// 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_PLATFORM_RTNETLINK_MESSAGE_H_
+#define QUICHE_QUIC_QBONE_PLATFORM_RTNETLINK_MESSAGE_H_
+
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+#include <stdint.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+
+#include <memory>
+#include <vector>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_logging.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_str_cat.h"
+
+namespace quic {
+
+// This base class is used to construct an array struct iovec that represents a
+// rtnetlink message as defined in man 7 rtnet. Padding for message header
+// alignment to conform NLMSG_* and RTA_* macros is added at the end of each
+// iovec::iov_base.
+class RtnetlinkMessage {
+ public:
+ virtual ~RtnetlinkMessage();
+
+ enum class Operation {
+ NEW,
+ DEL,
+ GET,
+ };
+
+ // Appends a struct rtattr to the message. nlmsg_len and rta_len is handled
+ // properly.
+ // Override this to perform check on type.
+ virtual void AppendAttribute(uint16_t type,
+ const void* data,
+ uint16_t data_length);
+
+ // Builds the array of iovec that can be fed into sendmsg directly.
+ std::unique_ptr<struct iovec[]> BuildIoVec() const;
+
+ // The size of the array of iovec if BuildIovec is called.
+ size_t IoVecSize() const;
+
+ protected:
+ // Subclass should add their own message header immediately after the
+ // nlmsghdr. Make this private to force the creation of such header.
+ RtnetlinkMessage(uint16_t type,
+ uint16_t flags,
+ uint32_t seq,
+ uint32_t pid,
+ const void* payload_header,
+ size_t payload_header_length);
+
+ // Adjusts nlmsg_len in the header assuming additional_data_length is appended
+ // at the end.
+ void AdjustMessageLength(size_t additional_data_length);
+
+ private:
+ // Convenient function for accessing the nlmsghdr.
+ struct nlmsghdr* MessageHeader();
+
+ std::vector<struct iovec> message_;
+};
+
+// Message for manipulating link level configuration as defined in man 7
+// rtnetlink. RTM_NEWLINK, RTM_DELLINK and RTM_GETLINK are supported.
+class LinkMessage : public RtnetlinkMessage {
+ public:
+ static LinkMessage New(RtnetlinkMessage::Operation request_operation,
+ uint16_t flags,
+ uint32_t seq,
+ uint32_t pid,
+ const struct ifinfomsg* interface_info_header);
+
+ private:
+ using RtnetlinkMessage::RtnetlinkMessage;
+};
+
+// Message for manipulating address level configuration as defined in man 7
+// rtnetlink. RTM_NEWADDR, RTM_NEWADDR and RTM_GETADDR are supported.
+class AddressMessage : public RtnetlinkMessage {
+ public:
+ static AddressMessage New(RtnetlinkMessage::Operation request_operation,
+ uint16_t flags,
+ uint32_t seq,
+ uint32_t pid,
+ const struct ifaddrmsg* interface_address_header);
+
+ private:
+ using RtnetlinkMessage::RtnetlinkMessage;
+};
+
+// Message for manipulating routing table as defined in man 7 rtnetlink.
+// RTM_NEWROUTE, RTM_DELROUTE and RTM_GETROUTE are supported.
+class RouteMessage : public RtnetlinkMessage {
+ public:
+ static RouteMessage New(RtnetlinkMessage::Operation request_operation,
+ uint16_t flags,
+ uint32_t seq,
+ uint32_t pid,
+ const struct rtmsg* route_message_header);
+
+ private:
+ using RtnetlinkMessage::RtnetlinkMessage;
+};
+
+class RuleMessage : public RtnetlinkMessage {
+ public:
+ static RuleMessage New(RtnetlinkMessage::Operation request_operation,
+ uint16_t flags,
+ uint32_t seq,
+ uint32_t pid,
+ const struct rtmsg* rule_message_header);
+
+ private:
+ using RtnetlinkMessage::RtnetlinkMessage;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_PLATFORM_RTNETLINK_MESSAGE_H_
diff --git a/quic/qbone/platform/rtnetlink_message_test.cc b/quic/qbone/platform/rtnetlink_message_test.cc
new file mode 100644
index 0000000..b129237
--- /dev/null
+++ b/quic/qbone/platform/rtnetlink_message_test.cc
@@ -0,0 +1,228 @@
+// 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/platform/rtnetlink_message.h"
+
+#include <net/if_arp.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"
+
+namespace quic {
+namespace {
+
+using ::testing::StrEq;
+
+TEST(RtnetlinkMessageTest, LinkMessageCanBeCreatedForGetOperation) {
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_MATCH;
+ uint32_t seq = 42;
+ uint32_t pid = 7;
+ auto message = LinkMessage::New(RtnetlinkMessage::Operation::GET, flags, seq,
+ pid, nullptr);
+
+ // No rtattr appended.
+ EXPECT_EQ(1, message.IoVecSize());
+
+ // nlmsghdr is built properly.
+ auto iov = message.BuildIoVec();
+ EXPECT_EQ(NLMSG_SPACE(sizeof(struct rtgenmsg)), iov[0].iov_len);
+ auto* netlink_message = reinterpret_cast<struct nlmsghdr*>(iov[0].iov_base);
+ EXPECT_EQ(NLMSG_LENGTH(sizeof(struct rtgenmsg)), netlink_message->nlmsg_len);
+ EXPECT_EQ(RTM_GETLINK, netlink_message->nlmsg_type);
+ EXPECT_EQ(flags, netlink_message->nlmsg_flags);
+ EXPECT_EQ(seq, netlink_message->nlmsg_seq);
+ EXPECT_EQ(pid, netlink_message->nlmsg_pid);
+
+ // We actually included rtgenmsg instead of the passed in ifinfomsg since this
+ // is a GET operation.
+ EXPECT_EQ(NLMSG_LENGTH(sizeof(struct rtgenmsg)), netlink_message->nlmsg_len);
+}
+
+TEST(RtnetlinkMessageTest, LinkMessageCanBeCreatedForNewOperation) {
+ struct ifinfomsg interface_info_header = {AF_INET, /* pad */ 0, ARPHRD_TUNNEL,
+ 3, 0, 0xffffffff};
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_MATCH;
+ uint32_t seq = 42;
+ uint32_t pid = 7;
+ auto message = LinkMessage::New(RtnetlinkMessage::Operation::NEW, flags, seq,
+ pid, &interface_info_header);
+
+ string device_name = "device0";
+ message.AppendAttribute(IFLA_IFNAME, device_name.c_str(), device_name.size());
+
+ // One rtattr appended.
+ EXPECT_EQ(2, message.IoVecSize());
+
+ // nlmsghdr is built properly.
+ auto iov = message.BuildIoVec();
+ EXPECT_EQ(NLMSG_ALIGN(NLMSG_LENGTH(sizeof(struct ifinfomsg))),
+ iov[0].iov_len);
+ auto* netlink_message = reinterpret_cast<struct nlmsghdr*>(iov[0].iov_base);
+ EXPECT_EQ(NLMSG_ALIGN(NLMSG_LENGTH(sizeof(struct ifinfomsg))) +
+ RTA_LENGTH(device_name.size()),
+ netlink_message->nlmsg_len);
+ EXPECT_EQ(RTM_NEWLINK, netlink_message->nlmsg_type);
+ EXPECT_EQ(flags, netlink_message->nlmsg_flags);
+ EXPECT_EQ(seq, netlink_message->nlmsg_seq);
+ EXPECT_EQ(pid, netlink_message->nlmsg_pid);
+
+ // ifinfomsg is included properly.
+ auto* parsed_header =
+ reinterpret_cast<struct ifinfomsg*>(NLMSG_DATA(netlink_message));
+ EXPECT_EQ(interface_info_header.ifi_family, parsed_header->ifi_family);
+ EXPECT_EQ(interface_info_header.ifi_type, parsed_header->ifi_type);
+ EXPECT_EQ(interface_info_header.ifi_index, parsed_header->ifi_index);
+ EXPECT_EQ(interface_info_header.ifi_flags, parsed_header->ifi_flags);
+ EXPECT_EQ(interface_info_header.ifi_change, parsed_header->ifi_change);
+
+ // rtattr is handled properly.
+ EXPECT_EQ(RTA_SPACE(device_name.size()), iov[1].iov_len);
+ auto* rta = reinterpret_cast<struct rtattr*>(iov[1].iov_base);
+ EXPECT_EQ(IFLA_IFNAME, rta->rta_type);
+ EXPECT_EQ(RTA_LENGTH(device_name.size()), rta->rta_len);
+ EXPECT_THAT(device_name, StrEq(string(reinterpret_cast<char*>(RTA_DATA(rta)),
+ RTA_PAYLOAD(rta))));
+}
+
+TEST(RtnetlinkMessageTest, AddressMessageCanBeCreatedForGetOperation) {
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_MATCH;
+ uint32_t seq = 42;
+ uint32_t pid = 7;
+ auto message = AddressMessage::New(RtnetlinkMessage::Operation::GET, flags,
+ seq, pid, nullptr);
+
+ // No rtattr appended.
+ EXPECT_EQ(1, message.IoVecSize());
+
+ // nlmsghdr is built properly.
+ auto iov = message.BuildIoVec();
+ EXPECT_EQ(NLMSG_SPACE(sizeof(struct rtgenmsg)), iov[0].iov_len);
+ auto* netlink_message = reinterpret_cast<struct nlmsghdr*>(iov[0].iov_base);
+ EXPECT_EQ(NLMSG_LENGTH(sizeof(struct rtgenmsg)), netlink_message->nlmsg_len);
+ EXPECT_EQ(RTM_GETADDR, netlink_message->nlmsg_type);
+ EXPECT_EQ(flags, netlink_message->nlmsg_flags);
+ EXPECT_EQ(seq, netlink_message->nlmsg_seq);
+ EXPECT_EQ(pid, netlink_message->nlmsg_pid);
+
+ // We actually included rtgenmsg instead of the passed in ifinfomsg since this
+ // is a GET operation.
+ EXPECT_EQ(NLMSG_LENGTH(sizeof(struct rtgenmsg)), netlink_message->nlmsg_len);
+}
+
+TEST(RtnetlinkMessageTest, AddressMessageCanBeCreatedForNewOperation) {
+ struct ifaddrmsg interface_address_header = {AF_INET,
+ /* prefixlen */ 24,
+ /* flags */ 0,
+ /* scope */ RT_SCOPE_LINK,
+ /* index */ 4};
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_MATCH;
+ uint32_t seq = 42;
+ uint32_t pid = 7;
+ auto message = AddressMessage::New(RtnetlinkMessage::Operation::NEW, flags,
+ seq, pid, &interface_address_header);
+
+ QuicIpAddress ip;
+ CHECK(ip.FromString("10.0.100.3"));
+ message.AppendAttribute(IFA_ADDRESS, ip.ToPackedString().c_str(),
+ ip.ToPackedString().size());
+
+ // One rtattr is appended.
+ EXPECT_EQ(2, message.IoVecSize());
+
+ // nlmsghdr is built properly.
+ auto iov = message.BuildIoVec();
+ EXPECT_EQ(NLMSG_ALIGN(NLMSG_LENGTH(sizeof(struct ifaddrmsg))),
+ iov[0].iov_len);
+ auto* netlink_message = reinterpret_cast<struct nlmsghdr*>(iov[0].iov_base);
+ EXPECT_EQ(NLMSG_ALIGN(NLMSG_LENGTH(sizeof(struct ifaddrmsg))) +
+ RTA_LENGTH(ip.ToPackedString().size()),
+ netlink_message->nlmsg_len);
+ EXPECT_EQ(RTM_NEWADDR, netlink_message->nlmsg_type);
+ EXPECT_EQ(flags, netlink_message->nlmsg_flags);
+ EXPECT_EQ(seq, netlink_message->nlmsg_seq);
+ EXPECT_EQ(pid, netlink_message->nlmsg_pid);
+
+ // ifaddrmsg is included properly.
+ auto* parsed_header =
+ reinterpret_cast<struct ifaddrmsg*>(NLMSG_DATA(netlink_message));
+ EXPECT_EQ(interface_address_header.ifa_family, parsed_header->ifa_family);
+ EXPECT_EQ(interface_address_header.ifa_prefixlen,
+ parsed_header->ifa_prefixlen);
+ EXPECT_EQ(interface_address_header.ifa_flags, parsed_header->ifa_flags);
+ EXPECT_EQ(interface_address_header.ifa_scope, parsed_header->ifa_scope);
+ EXPECT_EQ(interface_address_header.ifa_index, parsed_header->ifa_index);
+
+ // rtattr is handled properly.
+ EXPECT_EQ(RTA_SPACE(ip.ToPackedString().size()), iov[1].iov_len);
+ auto* rta = reinterpret_cast<struct rtattr*>(iov[1].iov_base);
+ EXPECT_EQ(IFA_ADDRESS, rta->rta_type);
+ EXPECT_EQ(RTA_LENGTH(ip.ToPackedString().size()), rta->rta_len);
+ EXPECT_THAT(
+ ip.ToPackedString(),
+ StrEq(string(reinterpret_cast<char*>(RTA_DATA(rta)), RTA_PAYLOAD(rta))));
+}
+
+TEST(RtnetlinkMessageTest, RouteMessageCanBeCreatedFromNewOperation) {
+ struct rtmsg route_message_header = {AF_INET6,
+ /* rtm_dst_len */ 48,
+ /* rtm_src_len */ 0,
+ /* rtm_tos */ 0,
+ /* rtm_table */ RT_TABLE_MAIN,
+ /* rtm_protocol */ RTPROT_STATIC,
+ /* rtm_scope */ RT_SCOPE_LINK,
+ /* rtm_type */ RTN_LOCAL,
+ /* rtm_flags */ 0};
+ uint16_t flags = NLM_F_REQUEST | NLM_F_ROOT | NLM_F_MATCH;
+ uint32_t seq = 42;
+ uint32_t pid = 7;
+ auto message = RouteMessage::New(RtnetlinkMessage::Operation::NEW, flags, seq,
+ pid, &route_message_header);
+
+ QuicIpAddress preferred_source;
+ CHECK(preferred_source.FromString("ff80::1"));
+ message.AppendAttribute(RTA_PREFSRC,
+ preferred_source.ToPackedString().c_str(),
+ preferred_source.ToPackedString().size());
+
+ // One rtattr is appended.
+ EXPECT_EQ(2, message.IoVecSize());
+
+ // nlmsghdr is built properly
+ auto iov = message.BuildIoVec();
+ EXPECT_EQ(NLMSG_ALIGN(NLMSG_LENGTH(sizeof(struct rtmsg))), iov[0].iov_len);
+ auto* netlink_message = reinterpret_cast<struct nlmsghdr*>(iov[0].iov_base);
+ EXPECT_EQ(NLMSG_ALIGN(NLMSG_LENGTH(sizeof(struct rtmsg))) +
+ RTA_LENGTH(preferred_source.ToPackedString().size()),
+ netlink_message->nlmsg_len);
+ EXPECT_EQ(RTM_NEWROUTE, netlink_message->nlmsg_type);
+ EXPECT_EQ(flags, netlink_message->nlmsg_flags);
+ EXPECT_EQ(seq, netlink_message->nlmsg_seq);
+ EXPECT_EQ(pid, netlink_message->nlmsg_pid);
+
+ // rtmsg is included properly.
+ auto* parsed_header =
+ reinterpret_cast<struct rtmsg*>(NLMSG_DATA(netlink_message));
+ EXPECT_EQ(route_message_header.rtm_family, parsed_header->rtm_family);
+ EXPECT_EQ(route_message_header.rtm_dst_len, parsed_header->rtm_dst_len);
+ EXPECT_EQ(route_message_header.rtm_src_len, parsed_header->rtm_src_len);
+ EXPECT_EQ(route_message_header.rtm_tos, parsed_header->rtm_tos);
+ EXPECT_EQ(route_message_header.rtm_table, parsed_header->rtm_table);
+ EXPECT_EQ(route_message_header.rtm_protocol, parsed_header->rtm_protocol);
+ EXPECT_EQ(route_message_header.rtm_scope, parsed_header->rtm_scope);
+ EXPECT_EQ(route_message_header.rtm_type, parsed_header->rtm_type);
+ EXPECT_EQ(route_message_header.rtm_flags, parsed_header->rtm_flags);
+
+ // rtattr is handled properly.
+ EXPECT_EQ(RTA_SPACE(preferred_source.ToPackedString().size()),
+ iov[1].iov_len);
+ auto* rta = reinterpret_cast<struct rtattr*>(iov[1].iov_base);
+ EXPECT_EQ(RTA_PREFSRC, rta->rta_type);
+ EXPECT_EQ(RTA_LENGTH(preferred_source.ToPackedString().size()), rta->rta_len);
+ EXPECT_THAT(
+ preferred_source.ToPackedString(),
+ StrEq(string(reinterpret_cast<char*>(RTA_DATA(rta)), RTA_PAYLOAD(rta))));
+}
+
+} // namespace
+} // namespace quic
diff --git a/quic/qbone/platform/tcp_packet.cc b/quic/qbone/platform/tcp_packet.cc
new file mode 100644
index 0000000..56fa88a
--- /dev/null
+++ b/quic/qbone/platform/tcp_packet.cc
@@ -0,0 +1,125 @@
+// 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/platform/tcp_packet.h"
+
+#include <netinet/ip6.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/qbone/platform/internet_checksum.h"
+
+namespace quic {
+namespace {
+
+constexpr size_t kIPv6AddressSize = sizeof(in6_addr);
+constexpr size_t kTcpTtl = 64;
+
+struct TCPv6Packet {
+ ip6_hdr ip_header;
+ tcphdr tcp_header;
+};
+
+struct TCPv6PseudoHeader {
+ uint32_t payload_size{};
+ uint8_t zeros[3] = {0, 0, 0};
+ uint8_t next_header = IPPROTO_TCP;
+};
+
+} // namespace
+
+void CreateTcpResetPacket(
+ quic::QuicStringPiece original_packet,
+ const std::function<void(quic::QuicStringPiece)>& cb) {
+ // By the time this method is called, original_packet should be fairly
+ // strongly validated. However, it's better to be more paranoid than not, so
+ // here are a bunch of very obvious checks.
+ if (QUIC_PREDICT_FALSE(original_packet.size() < sizeof(ip6_hdr))) {
+ return;
+ }
+ auto* ip6_header = reinterpret_cast<const ip6_hdr*>(original_packet.data());
+ if (QUIC_PREDICT_FALSE(ip6_header->ip6_vfc >> 4 != 6)) {
+ return;
+ }
+ if (QUIC_PREDICT_FALSE(ip6_header->ip6_nxt != IPPROTO_TCP)) {
+ return;
+ }
+ if (QUIC_PREDICT_FALSE(QuicEndian::NetToHost16(ip6_header->ip6_plen) <
+ sizeof(tcphdr))) {
+ return;
+ }
+ auto* tcp_header = reinterpret_cast<const tcphdr*>(ip6_header + 1);
+
+ // Now that the original packet has been confirmed to be well-formed, it's
+ // time to make the TCP RST packet.
+ TCPv6Packet tcp_packet{};
+
+ const size_t payload_size = sizeof(tcphdr);
+
+ // Set version to 6.
+ tcp_packet.ip_header.ip6_vfc = 0x6 << 4;
+ // Set the payload size, protocol and TTL.
+ tcp_packet.ip_header.ip6_plen = QuicEndian::HostToNet16(payload_size);
+ tcp_packet.ip_header.ip6_nxt = IPPROTO_TCP;
+ tcp_packet.ip_header.ip6_hops = kTcpTtl;
+ // Since the TCP RST is impersonating the endpoint, flip the source and
+ // destination addresses from the original packet.
+ tcp_packet.ip_header.ip6_src = ip6_header->ip6_dst;
+ tcp_packet.ip_header.ip6_dst = ip6_header->ip6_src;
+
+ // The same is true about the TCP ports
+ tcp_packet.tcp_header.dest = tcp_header->source;
+ tcp_packet.tcp_header.source = tcp_header->dest;
+
+ // There are no extensions in this header, so size is trivial
+ tcp_packet.tcp_header.doff = sizeof(tcphdr) >> 2;
+ // Checksum is 0 before it is computed
+ tcp_packet.tcp_header.check = 0;
+
+ // Per RFC 793, TCP RST comes in one of 3 flavors:
+ //
+ // * connection CLOSED
+ // * connection in non-synchronized state (LISTEN, SYN-SENT, SYN-RECEIVED)
+ // * connection in synchronized state (ESTABLISHED, FIN-WAIT-1, etc.)
+ //
+ // QBONE is acting like a firewall, so the RFC text of interest is the CLOSED
+ // state. Note, however, that it is possible for a connection to actually be
+ // in the FIN-WAIT-1 state on the remote end, but the processing logic does
+ // not change.
+ tcp_packet.tcp_header.rst = 1;
+
+ // If the incoming segment has an ACK field, the reset takes its sequence
+ // number from the ACK field of the segment,
+ if (tcp_header->ack) {
+ tcp_packet.tcp_header.seq = tcp_header->ack_seq;
+ } else {
+ // Otherwise the reset has sequence number zero and the ACK field is set to
+ // the sum of the sequence number and segment length of the incoming segment
+ tcp_packet.tcp_header.ack = 1;
+ tcp_packet.tcp_header.seq = 0;
+ tcp_packet.tcp_header.ack_seq =
+ QuicEndian::HostToNet32(QuicEndian::NetToHost32(tcp_header->seq) + 1);
+ }
+
+ TCPv6PseudoHeader pseudo_header{};
+ pseudo_header.payload_size = QuicEndian::HostToNet32(payload_size);
+
+ InternetChecksum checksum;
+ // Pseudoheader.
+ checksum.Update(tcp_packet.ip_header.ip6_src.s6_addr, kIPv6AddressSize);
+ checksum.Update(tcp_packet.ip_header.ip6_dst.s6_addr, kIPv6AddressSize);
+ checksum.Update(reinterpret_cast<char*>(&pseudo_header),
+ sizeof(pseudo_header));
+ // TCP header.
+ checksum.Update(reinterpret_cast<const char*>(&tcp_packet.tcp_header),
+ sizeof(tcp_packet.tcp_header));
+ // There is no body.
+ tcp_packet.tcp_header.check = checksum.Value();
+
+ const char* packet = reinterpret_cast<char*>(&tcp_packet);
+
+ cb(QuicStringPiece(packet, sizeof(tcp_packet)));
+}
+
+} // namespace quic
diff --git a/quic/qbone/platform/tcp_packet.h b/quic/qbone/platform/tcp_packet.h
new file mode 100644
index 0000000..cf33f03
--- /dev/null
+++ b/quic/qbone/platform/tcp_packet.h
@@ -0,0 +1,25 @@
+// 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_PLATFORM_TCP_PACKET_H_
+#define QUICHE_QUIC_QBONE_PLATFORM_TCP_PACKET_H_
+
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+
+#include <functional>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// Creates an TCPv6 RST packet, returning a packed string representation of the
+// packet to |cb|.
+void CreateTcpResetPacket(quic::QuicStringPiece original_packet,
+ const std::function<void(quic::QuicStringPiece)>& cb);
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_PLATFORM_TCP_PACKET_H_
diff --git a/quic/qbone/platform/tcp_packet_test.cc b/quic/qbone/platform/tcp_packet_test.cc
new file mode 100644
index 0000000..53a2c3f
--- /dev/null
+++ b/quic/qbone/platform/tcp_packet_test.cc
@@ -0,0 +1,116 @@
+// 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/platform/tcp_packet.h"
+
+#include <netinet/ip6.h>
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+
+namespace quic {
+namespace {
+
+// clang-format off
+constexpr uint8_t kReferenceTCPSYNPacket[] = {
+ // START IPv6 Header
+ // IPv6 with zero ToS and flow label
+ 0x60, 0x00, 0x00, 0x00,
+ // Payload is 40 bytes
+ 0x00, 0x28,
+ // Next header is TCP (6)
+ 0x06,
+ // Hop limit is 64
+ 0x40,
+ // Source address of ::1
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // Destination address of ::1
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // END IPv6 Header
+ // START TCPv6 Header
+ // Source port
+ 0xac, 0x1e,
+ // Destination port
+ 0x27, 0x0f,
+ // Sequence number
+ 0x4b, 0x01, 0xe8, 0x99,
+ // Acknowledgement Sequence number,
+ 0x00, 0x00, 0x00, 0x00,
+ // Offset
+ 0xa0,
+ // Flags
+ 0x02,
+ // Window
+ 0xaa, 0xaa,
+ // Checksum
+ 0x2e, 0x21,
+ // Urgent
+ 0x00, 0x00,
+ // END TCPv6 Header
+ // Options
+ 0x02, 0x04, 0xff, 0xc4, 0x04, 0x02, 0x08, 0x0a,
+ 0x1b, 0xb8, 0x52, 0xa1, 0x00, 0x00, 0x00, 0x00,
+ 0x01, 0x03, 0x03, 0x07,
+};
+
+constexpr uint8_t kReferenceTCPRSTPacket[] = {
+ // START IPv6 Header
+ // IPv6 with zero ToS and flow label
+ 0x60, 0x00, 0x00, 0x00,
+ // Payload is 20 bytes
+ 0x00, 0x14,
+ // Next header is TCP (6)
+ 0x06,
+ // Hop limit is 64
+ 0x40,
+ // Source address of ::1
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // Destination address of ::1
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // END IPv6 Header
+ // START TCPv6 Header
+ // Source port
+ 0x27, 0x0f,
+ // Destination port
+ 0xac, 0x1e,
+ // Sequence number
+ 0x00, 0x00, 0x00, 0x00,
+ // Acknowledgement Sequence number,
+ 0x4b, 0x01, 0xe8, 0x9a,
+ // Offset
+ 0x50,
+ // Flags
+ 0x14,
+ // Window
+ 0x00, 0x00,
+ // Checksum
+ 0xa9, 0x05,
+ // Urgent
+ 0x00, 0x00,
+ // END TCPv6 Header
+};
+// clang-format on
+
+} // namespace
+
+TEST(TcpPacketTest, CreatedPacketMatchesReference) {
+ QuicStringPiece syn =
+ QuicStringPiece(reinterpret_cast<const char*>(kReferenceTCPSYNPacket),
+ sizeof(kReferenceTCPSYNPacket));
+ QuicStringPiece expected_packet =
+ QuicStringPiece(reinterpret_cast<const char*>(kReferenceTCPRSTPacket),
+ sizeof(kReferenceTCPRSTPacket));
+ CreateTcpResetPacket(syn, [&expected_packet](QuicStringPiece packet) {
+ QUIC_LOG(INFO) << QuicTextUtils::HexDump(packet);
+ ASSERT_EQ(packet, expected_packet);
+ });
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_client.cc b/quic/qbone/qbone_client.cc
new file mode 100644
index 0000000..f062d3f
--- /dev/null
+++ b/quic/qbone/qbone_client.cc
@@ -0,0 +1,98 @@
+// 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/qbone_client.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_epoll.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_exported_stats.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_stream.h"
+
+namespace quic {
+namespace {
+std::unique_ptr<QuicClientBase::NetworkHelper> CreateNetworkHelper(
+ QuicEpollServer* epoll_server,
+ QboneClient* client) {
+ std::unique_ptr<QuicClientBase::NetworkHelper> helper =
+ QuicMakeUnique<QuicClientEpollNetworkHelper>(epoll_server, client);
+ testing::testvalue::Adjust("QboneClient/network_helper", &helper);
+ return helper;
+}
+} // namespace
+
+QboneClient::QboneClient(QuicSocketAddress server_address,
+ const QuicServerId& server_id,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicSession::Visitor* session_owner,
+ const QuicConfig& config,
+ QuicEpollServer* epoll_server,
+ std::unique_ptr<ProofVerifier> proof_verifier,
+ QbonePacketWriter* qbone_writer,
+ QboneClientControlStream::Handler* qbone_handler)
+ : QuicClientBase(
+ server_id,
+ supported_versions,
+ config,
+ new QuicEpollConnectionHelper(epoll_server, QuicAllocator::SIMPLE),
+ new QuicEpollAlarmFactory(epoll_server),
+ CreateNetworkHelper(epoll_server, this),
+ std::move(proof_verifier)),
+ qbone_writer_(qbone_writer),
+ qbone_handler_(qbone_handler),
+ session_owner_(session_owner) {
+ set_server_address(server_address);
+ crypto_config()->set_alpn("qbone");
+}
+
+QboneClient::~QboneClient() {
+ ResetSession();
+}
+
+QboneClientSession* QboneClient::qbone_session() {
+ return static_cast<QboneClientSession*>(QuicClientBase::session());
+}
+
+void QboneClient::ProcessPacketFromNetwork(QuicStringPiece packet) {
+ qbone_session()->ProcessPacketFromNetwork(packet);
+}
+
+int QboneClient::GetNumSentClientHellosFromSession() {
+ return qbone_session()->GetNumSentClientHellos();
+}
+
+int QboneClient::GetNumReceivedServerConfigUpdatesFromSession() {
+ return qbone_session()->GetNumReceivedServerConfigUpdates();
+}
+
+void QboneClient::ResendSavedData() {
+ // no op.
+}
+
+void QboneClient::ClearDataToResend() {
+ // no op.
+}
+
+bool QboneClient::HasActiveRequests() {
+ return qbone_session()->HasActiveRequests();
+}
+
+class QboneClientSessionWithConnection : public QboneClientSession {
+ public:
+ using QboneClientSession::QboneClientSession;
+
+ ~QboneClientSessionWithConnection() override { delete connection(); }
+};
+
+// Takes ownership of |connection|.
+std::unique_ptr<QuicSession> QboneClient::CreateQuicClientSession(
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection) {
+ return QuicMakeUnique<QboneClientSessionWithConnection>(
+ connection, crypto_config(), session_owner(), *config(),
+ supported_versions, server_id(), qbone_writer_, qbone_handler_);
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_client.h b/quic/qbone/qbone_client.h
new file mode 100644
index 0000000..a0fe4fc
--- /dev/null
+++ b/quic/qbone/qbone_client.h
@@ -0,0 +1,74 @@
+// 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_QBONE_CLIENT_H_
+#define QUICHE_QUIC_QBONE_QBONE_CLIENT_H_
+
+#include "net/third_party/quiche/src/quic/qbone/qbone_client_interface.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_client_session.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_writer.h"
+#include "net/third_party/quiche/src/quic/tools/quic_client_base.h"
+#include "net/third_party/quiche/src/quic/tools/quic_client_epoll_network_helper.h"
+
+namespace quic {
+// A QboneClient encapsulates connecting to a server via an epoll server
+// and setting up a Qbone tunnel. See the QboneTestClient in qbone_client_test
+// for usage.
+class QboneClient : public QuicClientBase, public QboneClientInterface {
+ public:
+ // Note that the epoll server, qbone writer, and handler are owned
+ // by the caller.
+ QboneClient(QuicSocketAddress server_address,
+ const QuicServerId& server_id,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicSession::Visitor* session_owner,
+ const QuicConfig& config,
+ QuicEpollServer* epoll_server,
+ std::unique_ptr<ProofVerifier> proof_verifier,
+ QbonePacketWriter* qbone_writer,
+ QboneClientControlStream::Handler* qbone_handler);
+ ~QboneClient() override;
+ QboneClientSession* qbone_session();
+
+ // From QboneClientInterface. Accepts a given packet from the network and
+ // sends the packet down to the QBONE connection.
+ void ProcessPacketFromNetwork(QuicStringPiece packet) override;
+
+ protected:
+ int GetNumSentClientHellosFromSession() override;
+ int GetNumReceivedServerConfigUpdatesFromSession() override;
+
+ // This client does not resend saved data. This will be a no-op.
+ void ResendSavedData() override;
+
+ // This client does not resend saved data. This will be a no-op.
+ void ClearDataToResend() override;
+
+ // Takes ownership of |connection|.
+ std::unique_ptr<QuicSession> CreateQuicClientSession(
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection) override;
+
+ QbonePacketWriter* qbone_writer() { return qbone_writer_; }
+
+ QboneClientControlStream::Handler* qbone_control_handler() {
+ return qbone_handler_;
+ }
+
+ QuicSession::Visitor* session_owner() {
+ return session_owner_;
+ }
+
+ bool HasActiveRequests() override;
+
+ private:
+ QbonePacketWriter* qbone_writer_;
+ QboneClientControlStream::Handler* qbone_handler_;
+
+ QuicSession::Visitor* session_owner_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_CLIENT_H_
diff --git a/quic/qbone/qbone_client_interface.h b/quic/qbone/qbone_client_interface.h
new file mode 100644
index 0000000..28d88ac
--- /dev/null
+++ b/quic/qbone/qbone_client_interface.h
@@ -0,0 +1,25 @@
+// 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_QBONE_CLIENT_INTERFACE_H_
+#define QUICHE_QUIC_QBONE_QBONE_CLIENT_INTERFACE_H_
+
+#include <cstdint>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+// An interface that includes methods to interact with a QBONE client.
+class QboneClientInterface {
+ public:
+ virtual ~QboneClientInterface() {}
+ // Accepts a given packet from the network and sends the packet down to the
+ // QBONE connection.
+ virtual void ProcessPacketFromNetwork(QuicStringPiece packet) = 0;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_CLIENT_INTERFACE_H_
diff --git a/quic/qbone/qbone_client_session.cc b/quic/qbone/qbone_client_session.cc
new file mode 100644
index 0000000..7bd401d
--- /dev/null
+++ b/quic/qbone/qbone_client_session.cc
@@ -0,0 +1,88 @@
+// 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/qbone_client_session.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h"
+
+namespace quic {
+
+QboneClientSession::QboneClientSession(
+ QuicConnection* connection,
+ QuicCryptoClientConfig* quic_crypto_client_config,
+ QuicSession::Visitor* owner,
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ const QuicServerId& server_id,
+ QbonePacketWriter* writer,
+ QboneClientControlStream::Handler* handler)
+ : QboneSessionBase(connection, owner, config, supported_versions, writer),
+ server_id_(server_id),
+ quic_crypto_client_config_(quic_crypto_client_config),
+ handler_(handler) {}
+
+QboneClientSession::~QboneClientSession() {}
+
+std::unique_ptr<QuicCryptoStream> QboneClientSession::CreateCryptoStream() {
+ return QuicMakeUnique<QuicCryptoClientStream>(
+ server_id_, this, nullptr, quic_crypto_client_config_, this);
+}
+
+void QboneClientSession::Initialize() {
+ // Initialize must be called first, as that's what generates the crypto
+ // stream.
+ QboneSessionBase::Initialize();
+ static_cast<QuicCryptoClientStreamBase*>(GetMutableCryptoStream())
+ ->CryptoConnect();
+ // Register the reserved control stream.
+ QuicStreamId next_id = GetNextOutgoingBidirectionalStreamId();
+ DCHECK_EQ(next_id, QboneConstants::GetControlStreamId(
+ connection()->transport_version()));
+ auto control_stream =
+ QuicMakeUnique<QboneClientControlStream>(this, handler_);
+ control_stream_ = control_stream.get();
+ RegisterStaticStream(std::move(control_stream),
+ /*stream_already_counted = */ false);
+}
+
+int QboneClientSession::GetNumSentClientHellos() const {
+ return static_cast<const QuicCryptoClientStreamBase*>(GetCryptoStream())
+ ->num_sent_client_hellos();
+}
+
+int QboneClientSession::GetNumReceivedServerConfigUpdates() const {
+ return static_cast<const QuicCryptoClientStreamBase*>(GetCryptoStream())
+ ->num_scup_messages_received();
+}
+
+bool QboneClientSession::SendServerRequest(const QboneServerRequest& request) {
+ if (!control_stream_) {
+ QUIC_BUG << "Cannot send server request before control stream is created.";
+ return false;
+ }
+ return control_stream_->SendRequest(request);
+}
+
+void QboneClientSession::ProcessPacketFromNetwork(QuicStringPiece packet) {
+ SendPacketToPeer(packet);
+}
+
+void QboneClientSession::ProcessPacketFromPeer(QuicStringPiece packet) {
+ writer_->WritePacketToNetwork(packet.data(), packet.size());
+}
+
+void QboneClientSession::OnProofValid(
+ const QuicCryptoClientConfig::CachedState& cached) {}
+
+void QboneClientSession::OnProofVerifyDetailsAvailable(
+ const ProofVerifyDetails& verify_details) {}
+
+bool QboneClientSession::HasActiveRequests() const {
+ return (stream_map().size() - num_incoming_static_streams() -
+ num_outgoing_static_streams()) > 0;
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_client_session.h b/quic/qbone/qbone_client_session.h
new file mode 100644
index 0000000..5dcf2ac
--- /dev/null
+++ b/quic/qbone/qbone_client_session.h
@@ -0,0 +1,76 @@
+// 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_QBONE_CLIENT_SESSION_H_
+#define QUICHE_QUIC_QBONE_QBONE_CLIENT_SESSION_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_crypto_client_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_control.pb.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_control_stream.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_writer.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_session_base.h"
+
+namespace quic {
+
+class QUIC_EXPORT_PRIVATE QboneClientSession
+ : public QboneSessionBase,
+ public QuicCryptoClientStream::ProofHandler {
+ public:
+ QboneClientSession(QuicConnection* connection,
+ QuicCryptoClientConfig* quic_crypto_client_config,
+ QuicSession::Visitor* owner,
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ const QuicServerId& server_id,
+ QbonePacketWriter* writer,
+ QboneClientControlStream::Handler* handler);
+ QboneClientSession(const QboneClientSession&) = delete;
+ QboneClientSession& operator=(const QboneClientSession&) = delete;
+ ~QboneClientSession() override;
+
+ // QuicSession overrides. This will initiate the crypto stream.
+ void Initialize() override;
+
+ // Returns the number of client hello messages that have been sent on the
+ // crypto stream. If the handshake has completed then this is one greater
+ // than the number of round-trips needed for the handshake.
+ int GetNumSentClientHellos() const;
+ int GetNumReceivedServerConfigUpdates() const;
+
+ bool SendServerRequest(const QboneServerRequest& request);
+
+ void ProcessPacketFromNetwork(QuicStringPiece packet) override;
+ void ProcessPacketFromPeer(QuicStringPiece packet) override;
+
+ // Returns true if there are active requests on this session.
+ bool HasActiveRequests() const;
+
+ protected:
+ // QboneSessionBase interface implementation.
+ std::unique_ptr<QuicCryptoStream> CreateCryptoStream() override;
+
+ // ProofHandler interface implementation.
+ void OnProofValid(const QuicCryptoClientConfig::CachedState& cached) override;
+ void OnProofVerifyDetailsAvailable(
+ const ProofVerifyDetails& verify_details) override;
+
+ QuicServerId server_id() { return server_id_; }
+ QuicCryptoClientConfig* crypto_client_config() {
+ return quic_crypto_client_config_;
+ }
+
+ private:
+ QuicServerId server_id_;
+ // Config for QUIC crypto client stream, used by the client.
+ QuicCryptoClientConfig* quic_crypto_client_config_;
+ // Passed to the control stream.
+ QboneClientControlStream::Handler* handler_;
+ // The unowned control stream.
+ QboneClientControlStream* control_stream_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_CLIENT_SESSION_H_
diff --git a/quic/qbone/qbone_client_test.cc b/quic/qbone/qbone_client_test.cc
new file mode 100644
index 0000000..78e533c
--- /dev/null
+++ b/quic/qbone/qbone_client_test.cc
@@ -0,0 +1,258 @@
+// 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.
+
+// Sets up a dispatcher and sends requests via the QboneClient.
+
+#include "net/third_party/quiche/src/quic/qbone/qbone_client.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_default_packet_writer.h"
+#include "net/third_party/quiche/src/quic/core/quic_dispatcher.h"
+#include "net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_epoll_connection_helper.h"
+#include "net/third_party/quiche/src/quic/core/quic_packet_reader.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_mutex.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_port_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_socket_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test_loopback.h"
+#include "net/quic/platform/impl/quic_socket_utils.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_processor_test_tools.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_server_session.h"
+#include "net/third_party/quiche/src/quic/test_tools/crypto_test_utils.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_server_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/server_thread.h"
+#include "net/third_party/quiche/src/quic/tools/quic_memory_cache_backend.h"
+#include "net/third_party/quiche/src/quic/tools/quic_server.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+string TestPacketIn(const string& body) {
+ return PrependIPv6HeaderForTest(body, 5);
+}
+
+string TestPacketOut(const string& body) {
+ return PrependIPv6HeaderForTest(body, 4);
+}
+
+class DataSavingQbonePacketWriter : public QbonePacketWriter {
+ public:
+ void WritePacketToNetwork(const char* packet, size_t size) override {
+ QuicWriterMutexLock lock(&mu_);
+ data_.push_back(string(packet, size));
+ }
+
+ std::vector<string> data() {
+ QuicWriterMutexLock lock(&mu_);
+ return data_;
+ }
+
+ private:
+ QuicMutex mu_;
+ std::vector<string> data_;
+};
+
+// A subclass of a qbone session that will own the connection passed in.
+class ConnectionOwningQboneServerSession : public QboneServerSession {
+ public:
+ ConnectionOwningQboneServerSession(
+ const ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ Visitor* owner,
+ const QuicConfig& config,
+ const QuicCryptoServerConfig* quic_crypto_server_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ QbonePacketWriter* writer)
+ : QboneServerSession(supported_versions,
+ connection,
+ owner,
+ config,
+ quic_crypto_server_config,
+ compressed_certs_cache,
+ writer,
+ TestLoopback6(),
+ TestLoopback6(),
+ 64,
+ nullptr),
+ connection_(connection) {}
+
+ private:
+ // Note that we don't expect the QboneServerSession or any of its parent
+ // classes to do anything with the connection_ in their destructors.
+ std::unique_ptr<QuicConnection> connection_;
+};
+
+class QuicQboneDispatcher : public QuicDispatcher {
+ public:
+ QuicQboneDispatcher(
+ const QuicConfig* config,
+ const QuicCryptoServerConfig* crypto_config,
+ QuicVersionManager* version_manager,
+ std::unique_ptr<QuicConnectionHelperInterface> helper,
+ std::unique_ptr<QuicCryptoServerStream::Helper> session_helper,
+ std::unique_ptr<QuicAlarmFactory> alarm_factory,
+ QbonePacketWriter* writer)
+ : QuicDispatcher(config,
+ crypto_config,
+ version_manager,
+ std::move(helper),
+ std::move(session_helper),
+ std::move(alarm_factory),
+ kQuicDefaultConnectionIdLength),
+ writer_(writer) {}
+
+ QuicSession* CreateQuicSession(
+ QuicConnectionId id,
+ const QuicSocketAddress& client,
+ QuicStringPiece alpn,
+ const quic::ParsedQuicVersion& version) override {
+ CHECK_EQ(alpn, "qbone");
+ QuicConnection* connection =
+ new QuicConnection(id, client, helper(), alarm_factory(), writer(),
+ /* owns_writer= */ false, Perspective::IS_SERVER,
+ ParsedQuicVersionVector{version});
+ // The connection owning wrapper owns the connection created.
+ QboneServerSession* session = new ConnectionOwningQboneServerSession(
+ GetSupportedVersions(), connection, this, config(), crypto_config(),
+ compressed_certs_cache(), writer_);
+ session->Initialize();
+ return session;
+ }
+
+ QuicConnectionId GenerateNewServerConnectionId(
+ ParsedQuicVersion version,
+ QuicConnectionId connection_id) const override {
+ char connection_id_bytes[kQuicDefaultConnectionIdLength] = {};
+ return QuicConnectionId(connection_id_bytes, sizeof(connection_id_bytes));
+ }
+
+ private:
+ QbonePacketWriter* writer_;
+};
+
+class QboneTestServer : public QuicServer {
+ public:
+ explicit QboneTestServer(std::unique_ptr<ProofSource> proof_source)
+ : QuicServer(std::move(proof_source), &response_cache_) {}
+ QuicDispatcher* CreateQuicDispatcher() override {
+ QuicEpollAlarmFactory alarm_factory(epoll_server());
+ return new QuicQboneDispatcher(
+ &config(), &crypto_config(), version_manager(),
+ std::unique_ptr<QuicEpollConnectionHelper>(
+ new QuicEpollConnectionHelper(epoll_server(),
+ QuicAllocator::BUFFER_POOL)),
+ std::unique_ptr<QuicCryptoServerStream::Helper>(
+ new QboneCryptoServerStreamHelper()),
+ std::unique_ptr<QuicEpollAlarmFactory>(
+ new QuicEpollAlarmFactory(epoll_server())),
+ &writer_);
+ }
+
+ std::vector<string> data() { return writer_.data(); }
+
+ void WaitForDataSize(int n) {
+ while (data().size() != n) {
+ }
+ }
+
+ private:
+ quic::QuicMemoryCacheBackend response_cache_;
+ DataSavingQbonePacketWriter writer_;
+};
+
+class QboneTestClient : public QboneClient {
+ public:
+ QboneTestClient(QuicSocketAddress server_address,
+ const QuicServerId& server_id,
+ const ParsedQuicVersionVector& supported_versions,
+ QuicEpollServer* epoll_server,
+ std::unique_ptr<ProofVerifier> proof_verifier)
+ : QboneClient(server_address,
+ server_id,
+ supported_versions,
+ /*session_owner=*/nullptr,
+ QuicConfig(),
+ epoll_server,
+ std::move(proof_verifier),
+ &qbone_writer_,
+ nullptr) {}
+
+ ~QboneTestClient() override {}
+
+ void SendData(const string& data) {
+ qbone_session()->ProcessPacketFromNetwork(data);
+ }
+
+ void WaitForWriteToFlush() {
+ while (connected() && session()->HasDataToWrite()) {
+ WaitForEvents();
+ }
+ }
+
+ void WaitForDataSize(int n) {
+ while (data().size() != n) {
+ WaitForEvents();
+ }
+ }
+
+ std::vector<string> data() { return qbone_writer_.data(); }
+
+ private:
+ DataSavingQbonePacketWriter qbone_writer_;
+};
+
+TEST(QboneClientTest, SendDataFromClient) {
+ SetQuicReloadableFlag(quic_use_parse_public_header, true);
+ auto server = new QboneTestServer(crypto_test_utils::ProofSourceForTesting());
+ QuicSocketAddress server_address(TestLoopback(), QuicPickUnusedPortOrDie());
+ ServerThread server_thread(server, server_address);
+ server_thread.Initialize();
+ server_thread.Start();
+
+ QuicEpollServer epoll_server;
+ QboneTestClient client(
+ server_address,
+ QuicServerId("test.example.com", server_address.port(), false),
+ AllSupportedVersions(), &epoll_server,
+ crypto_test_utils::ProofVerifierForTesting());
+ ASSERT_TRUE(client.Initialize());
+ ASSERT_TRUE(client.Connect());
+ ASSERT_TRUE(client.WaitForCryptoHandshakeConfirmed());
+ client.SendData(TestPacketIn("hello"));
+ client.SendData(TestPacketIn("world"));
+ client.WaitForWriteToFlush();
+ server->WaitForDataSize(2);
+ EXPECT_THAT(server->data()[0], testing::Eq(TestPacketOut("hello")));
+ EXPECT_THAT(server->data()[1], testing::Eq(TestPacketOut("world")));
+ auto server_session =
+ static_cast<QboneServerSession*>(QuicServerPeer::GetDispatcher(server)
+ ->session_map()
+ .begin()
+ ->second.get());
+ string long_data(QboneConstants::kMaxQbonePacketBytes - sizeof(ip6_hdr) - 1,
+ 'A');
+ // Pretend the server gets data.
+ server_thread.Schedule([&server_session, &long_data]() {
+ server_session->ProcessPacketFromNetwork(
+ TestPacketIn("Somethingsomething"));
+ server_session->ProcessPacketFromNetwork(TestPacketIn(long_data));
+ server_session->ProcessPacketFromNetwork(TestPacketIn(long_data));
+ });
+ client.WaitForDataSize(3);
+ EXPECT_THAT(client.data()[0],
+ testing::Eq(TestPacketOut("Somethingsomething")));
+ EXPECT_THAT(client.data()[1], testing::Eq(TestPacketOut(long_data)));
+ EXPECT_THAT(client.data()[2], testing::Eq(TestPacketOut(long_data)));
+
+ client.Disconnect();
+ server_thread.Quit();
+ server_thread.Join();
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/quic/qbone/qbone_constants.cc b/quic/qbone/qbone_constants.cc
new file mode 100644
index 0000000..a83f74e
--- /dev/null
+++ b/quic/qbone/qbone_constants.cc
@@ -0,0 +1,36 @@
+// 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/qbone_constants.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+
+namespace quic {
+
+constexpr char QboneConstants::kQboneAlpn[];
+const QuicByteCount QboneConstants::kMaxQbonePacketBytes;
+const uint32_t QboneConstants::kQboneRouteTableId;
+
+QuicStreamId QboneConstants::GetControlStreamId(QuicTransportVersion version) {
+ return QuicUtils::GetFirstBidirectionalStreamId(version,
+ Perspective::IS_CLIENT);
+}
+
+const QuicIpAddress* QboneConstants::TerminatorLocalAddress() {
+ static auto* terminator_address = []() {
+ QuicIpAddress* address = new QuicIpAddress;
+ // 0x71 0x62 0x6f 0x6e 0x65 is 'qbone' in ascii.
+ address->FromString("fe80::71:626f:6e65");
+ return address;
+ }();
+ return terminator_address;
+}
+
+const IpRange* QboneConstants::TerminatorLocalAddressRange() {
+ static auto* range =
+ new quic::IpRange(*quic::QboneConstants::TerminatorLocalAddress(), 128);
+ return range;
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_constants.h b/quic/qbone/qbone_constants.h
new file mode 100644
index 0000000..1fa2688
--- /dev/null
+++ b/quic/qbone/qbone_constants.h
@@ -0,0 +1,32 @@
+// 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_QBONE_CONSTANTS_H_
+#define QUICHE_QUIC_QBONE_QBONE_CONSTANTS_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/ip_range.h"
+
+namespace quic {
+
+struct QboneConstants {
+ // Qbone's ALPN
+ static constexpr char kQboneAlpn[] = "qbone";
+ // The maximum number of bytes allowed in a qbone packet.
+ static const QuicByteCount kMaxQbonePacketBytes = 2000;
+ // The table id for Qbone's routing table. 'bone' in ascii.
+ static const uint32_t kQboneRouteTableId = 0x626F6E65;
+ // The stream ID of the control channel.
+ static QuicStreamId GetControlStreamId(QuicTransportVersion version);
+ // The link-local address of the Terminator
+ static const QuicIpAddress* TerminatorLocalAddress();
+ // The IPRange containing the TerminatorLocalAddress
+ static const IpRange* TerminatorLocalAddressRange();
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_CONSTANTS_H_
diff --git a/quic/qbone/qbone_control.proto b/quic/qbone/qbone_control.proto
new file mode 100644
index 0000000..f0090d6
--- /dev/null
+++ b/quic/qbone/qbone_control.proto
@@ -0,0 +1,13 @@
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package quic;
+
+message QboneServerRequest {
+ extensions 1000 to max;
+};
+
+message QboneClientRequest {
+ extensions 1000 to max;
+};
diff --git a/quic/qbone/qbone_control_placeholder.proto b/quic/qbone/qbone_control_placeholder.proto
new file mode 100644
index 0000000..375b015
--- /dev/null
+++ b/quic/qbone/qbone_control_placeholder.proto
@@ -0,0 +1,20 @@
+syntax = "proto2";
+
+option optimize_for = LITE_RUNTIME;
+
+package quic;
+
+import "net/third_party/quiche/src/quic/qbone/qbone_control.proto";
+
+// These provide fields for QboneServerRequest and QboneClientRequest that are
+// used to test the control channel. Once the control channel actually has real
+// data to pass they can be removed.
+// TODO(b/62139999): Remove this file in favor of testing actual configuration.
+
+extend QboneServerRequest {
+ optional string server_placeholder = 179838467;
+}
+
+extend QboneClientRequest {
+ optional string client_placeholder = 179838467;
+}
diff --git a/quic/qbone/qbone_control_stream.cc b/quic/qbone/qbone_control_stream.cc
new file mode 100644
index 0000000..6272f3f
--- /dev/null
+++ b/quic/qbone/qbone_control_stream.cc
@@ -0,0 +1,67 @@
+// 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/qbone_control_stream.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h"
+
+namespace quic {
+
+namespace {
+static constexpr size_t kRequestSizeBytes = sizeof(uint16_t);
+} // namespace
+
+QboneControlStreamBase::QboneControlStreamBase(QuicSession* session)
+ : QuicStream(QboneConstants::GetControlStreamId(
+ session->connection()->transport_version()),
+ session,
+ /*is_static=*/true,
+ BIDIRECTIONAL),
+ pending_message_size_(0) {}
+
+void QboneControlStreamBase::OnDataAvailable() {
+ sequencer()->Read(&buffer_);
+ while (true) {
+ if (pending_message_size_ == 0) {
+ // Start of a message.
+ if (buffer_.size() < kRequestSizeBytes) {
+ return;
+ }
+ memcpy(&pending_message_size_, buffer_.data(), kRequestSizeBytes);
+ buffer_.erase(0, kRequestSizeBytes);
+ }
+ // Continuation of a message.
+ if (buffer_.size() < pending_message_size_) {
+ return;
+ }
+ string tmp = buffer_.substr(0, pending_message_size_);
+ buffer_.erase(0, pending_message_size_);
+ pending_message_size_ = 0;
+ OnMessage(tmp);
+ }
+}
+
+bool QboneControlStreamBase::SendMessage(const proto2::Message& proto) {
+ string tmp;
+ if (!proto.SerializeToString(&tmp)) {
+ QUIC_BUG << "Failed to serialize QboneControlRequest";
+ return false;
+ }
+ if (tmp.size() > kuint16max) {
+ QUIC_BUG << "QboneControlRequest too large: " << tmp.size() << " > "
+ << kuint16max;
+ return false;
+ }
+ uint16_t size = tmp.size();
+ char size_str[kRequestSizeBytes];
+ memcpy(size_str, &size, kRequestSizeBytes);
+ WriteOrBufferData(QuicStringPiece(size_str, kRequestSizeBytes), false,
+ nullptr);
+ WriteOrBufferData(tmp, false, nullptr);
+ return true;
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_control_stream.h b/quic/qbone/qbone_control_stream.h
new file mode 100644
index 0000000..6e3dead
--- /dev/null
+++ b/quic/qbone/qbone_control_stream.h
@@ -0,0 +1,75 @@
+// 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_QBONE_CONTROL_STREAM_H_
+#define QUICHE_QUIC_QBONE_QBONE_CONTROL_STREAM_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_control.pb.h"
+
+namespace quic {
+
+class QboneSessionBase;
+
+class QUIC_EXPORT_PRIVATE QboneControlStreamBase : public QuicStream {
+ public:
+ explicit QboneControlStreamBase(QuicSession* session);
+
+ void OnDataAvailable() override;
+
+ protected:
+ virtual void OnMessage(const string& data) = 0;
+ bool SendMessage(const proto2::Message& proto);
+
+ private:
+ uint16_t pending_message_size_;
+ string buffer_;
+};
+
+template <class T>
+class QUIC_EXPORT_PRIVATE QboneControlHandler {
+ public:
+ virtual ~QboneControlHandler() { }
+
+ virtual void OnControlRequest(const T& request) = 0;
+ virtual void OnControlError() = 0;
+};
+
+template <class Incoming, class Outgoing>
+class QUIC_EXPORT_PRIVATE QboneControlStream : public QboneControlStreamBase {
+ public:
+ using Handler = QboneControlHandler<Incoming>;
+
+ QboneControlStream(QuicSession* session, Handler* handler)
+ : QboneControlStreamBase(session), handler_(handler) {}
+
+ bool SendRequest(const Outgoing& request) { return SendMessage(request); }
+
+ protected:
+ void OnMessage(const string& data) override {
+ Incoming request;
+ if (!request.ParseFromString(data)) {
+ QUIC_LOG(ERROR) << "Failed to parse incoming request";
+ if (handler_ != nullptr) {
+ handler_->OnControlError();
+ }
+ return;
+ }
+ if (handler_ != nullptr) {
+ handler_->OnControlRequest(request);
+ }
+ }
+
+ private:
+ Handler* handler_;
+};
+
+using QboneServerControlStream =
+ QboneControlStream<QboneServerRequest, QboneClientRequest>;
+using QboneClientControlStream =
+ QboneControlStream<QboneClientRequest, QboneServerRequest>;
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_CONTROL_STREAM_H_
diff --git a/quic/qbone/qbone_packet_exchanger.cc b/quic/qbone/qbone_packet_exchanger.cc
new file mode 100644
index 0000000..3f3a5f9
--- /dev/null
+++ b/quic/qbone/qbone_packet_exchanger.cc
@@ -0,0 +1,70 @@
+// 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/qbone_packet_exchanger.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+
+namespace quic {
+
+bool QbonePacketExchanger::ReadAndDeliverPacket(
+ QboneClientInterface* qbone_client) {
+ bool blocked = false;
+ string error;
+ std::unique_ptr<QuicData> packet = ReadPacket(&blocked, &error);
+ if (packet == nullptr) {
+ if (!blocked) {
+ visitor_->OnReadError(error);
+ }
+ return false;
+ }
+ qbone_client->ProcessPacketFromNetwork(packet->AsStringPiece());
+ return true;
+}
+
+void QbonePacketExchanger::WritePacketToNetwork(const char* packet,
+ size_t size) {
+ bool blocked = false;
+ string error;
+ if (packet_queue_.empty() && !write_blocked_) {
+ if (WritePacket(packet, size, &blocked, &error)) {
+ return;
+ }
+ if (!blocked) {
+ visitor_->OnWriteError(error);
+ return;
+ }
+ write_blocked_ = true;
+ }
+
+ // Drop the packet on the floor if the queue if full.
+ if (packet_queue_.size() >= max_pending_packets_) {
+ return;
+ }
+
+ auto data_copy = new char[size];
+ memcpy(data_copy, packet, size);
+ packet_queue_.push_back(
+ QuicMakeUnique<QuicData>(data_copy, size, /* owns_buffer = */ true));
+}
+
+void QbonePacketExchanger::SetWritable() {
+ write_blocked_ = false;
+ while (!packet_queue_.empty()) {
+ bool blocked = false;
+ string error;
+ if (WritePacket(packet_queue_.front()->data(),
+ packet_queue_.front()->length(), &blocked, &error)) {
+ packet_queue_.pop_front();
+ } else {
+ if (!blocked) {
+ visitor_->OnWriteError(error);
+ }
+ write_blocked_ = blocked;
+ return;
+ }
+ }
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_packet_exchanger.h b/quic/qbone/qbone_packet_exchanger.h
new file mode 100644
index 0000000..8620a49
--- /dev/null
+++ b/quic/qbone/qbone_packet_exchanger.h
@@ -0,0 +1,80 @@
+// 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_QBONE_PACKET_EXCHANGER_H_
+#define QUICHE_QUIC_QBONE_QBONE_PACKET_EXCHANGER_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_packets.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_client_interface.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_writer.h"
+
+namespace quic {
+
+// Handles reading and writing on the local network and exchange packets between
+// the local network with a Qbone connection.
+class QbonePacketExchanger : public QbonePacketWriter {
+ public:
+ // The owner might want to receive notifications when read or write fails.
+ class Visitor {
+ public:
+ virtual ~Visitor() {}
+ virtual void OnReadError(const string& error) {}
+ virtual void OnWriteError(const string& error) {}
+ };
+ // Does not take ownership of visitor.
+ QbonePacketExchanger(Visitor* visitor, size_t max_pending_packets)
+ : visitor_(visitor), max_pending_packets_(max_pending_packets) {}
+
+ QbonePacketExchanger(const QbonePacketExchanger&) = delete;
+ QbonePacketExchanger& operator=(const QbonePacketExchanger&) = delete;
+
+ QbonePacketExchanger(QbonePacketExchanger&&) = delete;
+ QbonePacketExchanger& operator=(QbonePacketExchanger&&) = delete;
+
+ ~QbonePacketExchanger() = default;
+
+ // Returns true if there may be more packets to read.
+ // Implementations handles the actual raw read and delivers the packet to
+ // qbone_client.
+ bool ReadAndDeliverPacket(QboneClientInterface* qbone_client);
+
+ // From QbonePacketWriter.
+ // Writes a packet to the local network. If the write would be blocked, the
+ // packet will be queued if the queue is smaller than max_pending_packets_.
+ void WritePacketToNetwork(const char* packet, size_t size) override;
+
+ // The caller signifies that the local network is no longer blocked.
+ void SetWritable();
+
+ private:
+ // The actual implementation that reads a packet from the local network.
+ // Returns the packet if one is successfully read. This might nullptr when a)
+ // there is no packet to read, b) the read failed. In the former case, blocked
+ // is set to true. error contains the error message.
+ virtual std::unique_ptr<QuicData> ReadPacket(bool* blocked,
+ string* error) = 0;
+
+ // The actual implementation that writes a packet to the local network.
+ // Returns true if the write succeeds. blocked will be set to true if the
+ // write failure is caused by the local network being blocked. error contains
+ // the error message.
+ virtual bool WritePacket(const char* packet,
+ size_t size,
+ bool* blocked,
+ string* error) = 0;
+
+ std::list<std::unique_ptr<QuicData>> packet_queue_;
+
+ Visitor* visitor_;
+
+ // The maximum number of packets that could be queued up when writing to local
+ // network is blocked.
+ size_t max_pending_packets_;
+
+ bool write_blocked_ = false;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_PACKET_EXCHANGER_H_
diff --git a/quic/qbone/qbone_packet_exchanger_test.cc b/quic/qbone/qbone_packet_exchanger_test.cc
new file mode 100644
index 0000000..4e63b99
--- /dev/null
+++ b/quic/qbone/qbone_packet_exchanger_test.cc
@@ -0,0 +1,253 @@
+// 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/qbone_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_test.h"
+#include "net/third_party/quiche/src/quic/qbone/mock_qbone_client.h"
+
+namespace quic {
+namespace {
+
+using ::testing::StrEq;
+using ::testing::StrictMock;
+
+const size_t kMaxPendingPackets = 2;
+
+class MockVisitor : public QbonePacketExchanger::Visitor {
+ public:
+ MOCK_METHOD1(OnReadError, void(const string&));
+ MOCK_METHOD1(OnWriteError, void(const string&));
+};
+
+class FakeQbonePacketExchanger : public QbonePacketExchanger {
+ public:
+ using QbonePacketExchanger::QbonePacketExchanger;
+
+ // Adds a packet to the end of list of packets to be returned by ReadPacket.
+ // When the list is empty, ReadPacket returns nullptr to signify error as
+ // defined by QbonePacketExchanger. If SetReadError is not called or called
+ // with empty error string, ReadPacket sets blocked to true.
+ void AddPacketToBeRead(std::unique_ptr<QuicData> packet) {
+ packets_to_be_read_.push_back(std::move(packet));
+ }
+
+ // Sets the error to be returned by ReadPacket when the list of packets is
+ // empty. If error is empty string, blocked is set by ReadPacket.
+ void SetReadError(const string& error) { read_error_ = error; }
+
+ // Force WritePacket to fail with the given status. WritePacket returns true
+ // when blocked == true and error is empty.
+ void ForceWriteFailure(bool blocked, const string& error) {
+ write_blocked_ = blocked;
+ write_error_ = error;
+ }
+
+ // Packets that have been successfully written by WritePacket.
+ const std::vector<string>& packets_written() const {
+ return packets_written_;
+ }
+
+ private:
+ // Implements QbonePacketExchanger::ReadPacket.
+ std::unique_ptr<QuicData> ReadPacket(bool* blocked, string* error) override {
+ *blocked = false;
+
+ if (packets_to_be_read_.empty()) {
+ *blocked = read_error_.empty();
+ *error = read_error_;
+ return nullptr;
+ }
+
+ std::unique_ptr<QuicData> packet = std::move(packets_to_be_read_.front());
+ packets_to_be_read_.pop_front();
+ return packet;
+ }
+
+ // Implements QbonePacketExchanger::WritePacket.
+ bool WritePacket(const char* packet,
+ size_t size,
+ bool* blocked,
+ string* error) override {
+ *blocked = false;
+
+ if (write_blocked_ || !write_error_.empty()) {
+ *blocked = write_blocked_;
+ *error = write_error_;
+ return false;
+ }
+
+ packets_written_.push_back(string(packet, size));
+ return true;
+ }
+
+ string read_error_;
+ std::list<std::unique_ptr<QuicData>> packets_to_be_read_;
+
+ string write_error_;
+ bool write_blocked_ = false;
+ std::vector<string> packets_written_;
+};
+
+TEST(QbonePacketExchangerTest,
+ ReadAndDeliverPacketDeliversPacketToQboneClient) {
+ StrictMock<MockVisitor> visitor;
+ FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets);
+ StrictMock<MockQboneClient> client;
+
+ string packet = "data";
+ exchanger.AddPacketToBeRead(
+ QuicMakeUnique<QuicData>(packet.data(), packet.length()));
+ EXPECT_CALL(client, ProcessPacketFromNetwork(StrEq("data")));
+
+ EXPECT_TRUE(exchanger.ReadAndDeliverPacket(&client));
+}
+
+TEST(QbonePacketExchangerTest,
+ ReadAndDeliverPacketNotifiesVisitorOnReadFailure) {
+ MockVisitor visitor;
+ FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets);
+ MockQboneClient client;
+
+ // Force read error.
+ string io_error = "I/O error";
+ exchanger.SetReadError(io_error);
+ EXPECT_CALL(visitor, OnReadError(StrEq(io_error))).Times(1);
+
+ EXPECT_FALSE(exchanger.ReadAndDeliverPacket(&client));
+}
+
+TEST(QbonePacketExchangerTest,
+ ReadAndDeliverPacketDoesNotNotifyVisitorOnBlockedIO) {
+ MockVisitor visitor;
+ FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets);
+ MockQboneClient client;
+
+ // No more packets to read.
+ EXPECT_FALSE(exchanger.ReadAndDeliverPacket(&client));
+}
+
+TEST(QbonePacketExchangerTest,
+ WritePacketToNetworkWritesDirectlyToNetworkWhenNotBlocked) {
+ MockVisitor visitor;
+ FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets);
+ MockQboneClient client;
+
+ string packet = "data";
+ exchanger.WritePacketToNetwork(packet.data(), packet.length());
+
+ ASSERT_EQ(exchanger.packets_written().size(), 1);
+ EXPECT_THAT(exchanger.packets_written()[0], StrEq(packet));
+}
+
+TEST(QbonePacketExchangerTest,
+ WritePacketToNetworkQueuesPacketsAndProcessThemLater) {
+ MockVisitor visitor;
+ FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets);
+ MockQboneClient client;
+
+ // Force write to be blocked so that packets are queued.
+ exchanger.ForceWriteFailure(true, "");
+ std::vector<string> packets = {"packet0", "packet1"};
+ for (int i = 0; i < packets.size(); i++) {
+ exchanger.WritePacketToNetwork(packets[i].data(), packets[i].length());
+ }
+
+ // Nothing should have been written because of blockage.
+ ASSERT_TRUE(exchanger.packets_written().empty());
+
+ // Remove blockage and start proccessing queued packets.
+ exchanger.ForceWriteFailure(false, "");
+ exchanger.SetWritable();
+
+ // Queued packets are processed.
+ ASSERT_EQ(exchanger.packets_written().size(), 2);
+ for (int i = 0; i < packets.size(); i++) {
+ EXPECT_THAT(exchanger.packets_written()[i], StrEq(packets[i]));
+ }
+}
+
+TEST(QbonePacketExchangerTest,
+ SetWritableContinuesProcessingPacketIfPreviousCallBlocked) {
+ MockVisitor visitor;
+ FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets);
+ MockQboneClient client;
+
+ // Force write to be blocked so that packets are queued.
+ exchanger.ForceWriteFailure(true, "");
+ std::vector<string> packets = {"packet0", "packet1"};
+ for (int i = 0; i < packets.size(); i++) {
+ exchanger.WritePacketToNetwork(packets[i].data(), packets[i].length());
+ }
+
+ // Nothing should have been written because of blockage.
+ ASSERT_TRUE(exchanger.packets_written().empty());
+
+ // Start processing packets, but since writes are still blocked, nothing
+ // should have been written.
+ exchanger.SetWritable();
+ ASSERT_TRUE(exchanger.packets_written().empty());
+
+ // Remove blockage and start processing packets again.
+ exchanger.ForceWriteFailure(false, "");
+ exchanger.SetWritable();
+
+ ASSERT_EQ(exchanger.packets_written().size(), 2);
+ for (int i = 0; i < packets.size(); i++) {
+ EXPECT_THAT(exchanger.packets_written()[i], StrEq(packets[i]));
+ }
+}
+
+TEST(QbonePacketExchangerTest, WritePacketToNetworkDropsPacketIfQueueIfFull) {
+ std::vector<string> packets = {"packet0", "packet1", "packet2"};
+ size_t queue_size = packets.size() - 1;
+ MockVisitor visitor;
+ // exchanger has smaller queue than number of packets.
+ FakeQbonePacketExchanger exchanger(&visitor, queue_size);
+ MockQboneClient client;
+
+ exchanger.ForceWriteFailure(true, "");
+ for (int i = 0; i < packets.size(); i++) {
+ exchanger.WritePacketToNetwork(packets[i].data(), packets[i].length());
+ }
+
+ // Blocked writes cause packets to be queued or dropped.
+ ASSERT_TRUE(exchanger.packets_written().empty());
+
+ exchanger.ForceWriteFailure(false, "");
+ exchanger.SetWritable();
+
+ ASSERT_EQ(exchanger.packets_written().size(), queue_size);
+ for (int i = 0; i < queue_size; i++) {
+ EXPECT_THAT(exchanger.packets_written()[i], StrEq(packets[i]));
+ }
+}
+
+TEST(QbonePacketExchangerTest, WriteErrorsGetNotified) {
+ MockVisitor visitor;
+ FakeQbonePacketExchanger exchanger(&visitor, kMaxPendingPackets);
+ MockQboneClient client;
+ string packet = "data";
+
+ // Write error is delivered to visitor during WritePacketToNetwork.
+ string io_error = "I/O error";
+ exchanger.ForceWriteFailure(false, io_error);
+ EXPECT_CALL(visitor, OnWriteError(StrEq(io_error))).Times(1);
+ exchanger.WritePacketToNetwork(packet.data(), packet.length());
+ ASSERT_TRUE(exchanger.packets_written().empty());
+
+ // Write error is delivered to visitor during SetWritable.
+ exchanger.ForceWriteFailure(true, "");
+ exchanger.WritePacketToNetwork(packet.data(), packet.length());
+
+ string sys_error = "sys error";
+ exchanger.ForceWriteFailure(false, sys_error);
+ EXPECT_CALL(visitor, OnWriteError(StrEq(sys_error))).Times(1);
+ exchanger.SetWritable();
+ ASSERT_TRUE(exchanger.packets_written().empty());
+}
+
+} // namespace
+} // namespace quic
diff --git a/quic/qbone/qbone_packet_processor.cc b/quic/qbone/qbone_packet_processor.cc
new file mode 100644
index 0000000..db7a138
--- /dev/null
+++ b/quic/qbone/qbone_packet_processor.cc
@@ -0,0 +1,269 @@
+// 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/qbone_packet_processor.h"
+
+#include <cstring>
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_bug_tracker.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/qbone/platform/icmp_packet.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/internet_checksum.h"
+#include "net/third_party/quiche/src/quic/qbone/platform/tcp_packet.h"
+
+namespace {
+
+constexpr size_t kIPv6AddressSize = 16;
+constexpr size_t kIPv6MinPacketSize = 1280;
+constexpr size_t kIcmpTtl = 64;
+constexpr size_t kICMPv6DestinationUnreachableDueToSourcePolicy = 5;
+
+} // namespace
+
+namespace quic {
+
+const QuicIpAddress QbonePacketProcessor::kInvalidIpAddress =
+ QuicIpAddress::Any6();
+
+QbonePacketProcessor::QbonePacketProcessor(QuicIpAddress self_ip,
+ QuicIpAddress client_ip,
+ size_t client_ip_subnet_length,
+ OutputInterface* output,
+ StatsInterface* stats)
+ : client_ip_(client_ip),
+ output_(output),
+ stats_(stats),
+ filter_(new Filter) {
+ memcpy(self_ip_.s6_addr, self_ip.ToPackedString().data(), kIPv6AddressSize);
+ DCHECK_LE(client_ip_subnet_length, kIPv6AddressSize * 8);
+ client_ip_subnet_length_ = client_ip_subnet_length;
+
+ DCHECK(IpAddressFamily::IP_V6 == self_ip.address_family());
+ DCHECK(IpAddressFamily::IP_V6 == client_ip.address_family());
+ DCHECK(self_ip != kInvalidIpAddress);
+}
+
+QbonePacketProcessor::OutputInterface::~OutputInterface() {}
+QbonePacketProcessor::StatsInterface::~StatsInterface() {}
+QbonePacketProcessor::Filter::~Filter() {}
+
+QbonePacketProcessor::ProcessingResult
+QbonePacketProcessor::Filter::FilterPacket(Direction direction,
+ QuicStringPiece full_packet,
+ QuicStringPiece payload,
+ icmp6_hdr* icmp_header,
+ OutputInterface* output) {
+ return ProcessingResult::OK;
+}
+
+void QbonePacketProcessor::ProcessPacket(string* packet, Direction direction) {
+ if (QUIC_PREDICT_FALSE(!IsValid())) {
+ QUIC_BUG << "QuicPacketProcessor is invoked in an invalid state.";
+ stats_->OnPacketDroppedSilently(direction);
+ return;
+ }
+
+ uint8_t transport_protocol;
+ char* transport_data;
+ icmp6_hdr icmp_header;
+ memset(&icmp_header, 0, sizeof(icmp_header));
+ ProcessingResult result = ProcessIPv6HeaderAndFilter(
+ packet, direction, &transport_protocol, &transport_data, &icmp_header);
+
+ switch (result) {
+ case ProcessingResult::OK:
+ switch (direction) {
+ case Direction::FROM_CLIENT:
+ output_->SendPacketToNetwork(*packet);
+ break;
+ case Direction::FROM_NETWORK:
+ output_->SendPacketToClient(*packet);
+ break;
+ }
+ stats_->OnPacketForwarded(direction);
+ break;
+ case ProcessingResult::SILENT_DROP:
+ stats_->OnPacketDroppedSilently(direction);
+ break;
+ case ProcessingResult::DEFER:
+ stats_->OnPacketDeferred(direction);
+ break;
+ case ProcessingResult::ICMP:
+ SendIcmpResponse(&icmp_header, *packet, direction);
+ stats_->OnPacketDroppedWithIcmp(direction);
+ break;
+ case ProcessingResult::ICMP_AND_TCP_RESET:
+ SendIcmpResponse(&icmp_header, *packet, direction);
+ stats_->OnPacketDroppedWithIcmp(direction);
+ SendTcpReset(*packet, direction);
+ stats_->OnPacketDroppedWithTcpReset(direction);
+ break;
+ }
+}
+
+QbonePacketProcessor::ProcessingResult
+QbonePacketProcessor::ProcessIPv6HeaderAndFilter(string* packet,
+ Direction direction,
+ uint8_t* transport_protocol,
+ char** transport_data,
+ icmp6_hdr* icmp_header) {
+ ProcessingResult result = ProcessIPv6Header(
+ packet, direction, transport_protocol, transport_data, icmp_header);
+
+ if (result == ProcessingResult::OK) {
+ char* packet_data = &*packet->begin();
+ size_t header_size = *transport_data - packet_data;
+ // Sanity-check the bounds.
+ if (packet_data >= *transport_data || header_size > packet->size() ||
+ header_size < kIPv6HeaderSize) {
+ QUIC_BUG << "Invalid pointers encountered in "
+ "QbonePacketProcessor::ProcessPacket. Dropping the packet";
+ return ProcessingResult::SILENT_DROP;
+ }
+
+ result = filter_->FilterPacket(
+ direction, *packet,
+ QuicStringPiece(*transport_data, packet->size() - header_size),
+ icmp_header, output_);
+ }
+
+ // Do not send ICMP error messages in response to ICMP errors.
+ if (result == ProcessingResult::ICMP) {
+ const uint8_t* header = reinterpret_cast<const uint8_t*>(packet->data());
+
+ constexpr size_t kIPv6NextHeaderOffset = 6;
+ constexpr size_t kIcmpMessageTypeOffset = kIPv6HeaderSize + 0;
+ constexpr size_t kIcmpMessageTypeMaxError = 127;
+ if (
+ // Check size.
+ packet->size() >= (kIPv6HeaderSize + kICMPv6HeaderSize) &&
+ // Check that the packet is in fact ICMP.
+ header[kIPv6NextHeaderOffset] == IPPROTO_ICMPV6 &&
+ // Check that ICMP message type is an error.
+ header[kIcmpMessageTypeOffset] < kIcmpMessageTypeMaxError) {
+ result = ProcessingResult::SILENT_DROP;
+ }
+ }
+
+ return result;
+}
+
+QbonePacketProcessor::ProcessingResult QbonePacketProcessor::ProcessIPv6Header(
+ string* packet,
+ Direction direction,
+ uint8_t* transport_protocol,
+ char** transport_data,
+ icmp6_hdr* icmp_header) {
+ // Check if the packet is big enough to have IPv6 header.
+ if (packet->size() < kIPv6HeaderSize) {
+ QUIC_DVLOG(1) << "Dropped malformed packet: IPv6 header too short";
+ return ProcessingResult::SILENT_DROP;
+ }
+
+ // Check version field.
+ ip6_hdr* header = reinterpret_cast<ip6_hdr*>(&*packet->begin());
+ if (header->ip6_vfc >> 4 != 6) {
+ QUIC_DVLOG(1) << "Dropped malformed packet: IP version is not IPv6";
+ return ProcessingResult::SILENT_DROP;
+ }
+
+ // Check payload size.
+ const size_t declared_payload_size =
+ QuicEndian::NetToHost16(header->ip6_plen);
+ const size_t actual_payload_size = packet->size() - kIPv6HeaderSize;
+ if (declared_payload_size != actual_payload_size) {
+ QUIC_DVLOG(1)
+ << "Dropped malformed packet: incorrect packet length specified";
+ return ProcessingResult::SILENT_DROP;
+ }
+
+ // Check that the address of the client is in the packet.
+ QuicIpAddress address_to_check;
+ uint8_t address_reject_code;
+ bool ip_parse_result;
+ switch (direction) {
+ case Direction::FROM_CLIENT:
+ // Expect the source IP to match the client.
+ ip_parse_result = address_to_check.FromPackedString(
+ reinterpret_cast<const char*>(&header->ip6_src),
+ sizeof(header->ip6_src));
+ address_reject_code = kICMPv6DestinationUnreachableDueToSourcePolicy;
+ break;
+ case Direction::FROM_NETWORK:
+ // Expect the destination IP to match the client.
+ ip_parse_result = address_to_check.FromPackedString(
+ reinterpret_cast<const char*>(&header->ip6_dst),
+ sizeof(header->ip6_src));
+ address_reject_code = ICMP6_DST_UNREACH_NOROUTE;
+ break;
+ }
+ DCHECK(ip_parse_result);
+ if (!client_ip_.InSameSubnet(address_to_check, client_ip_subnet_length_)) {
+ QUIC_DVLOG(1)
+ << "Dropped packet: source/destination address is not client's";
+ icmp_header->icmp6_type = ICMP6_DST_UNREACH;
+ icmp_header->icmp6_code = address_reject_code;
+ return ProcessingResult::ICMP;
+ }
+
+ // Check and decrement TTL.
+ if (header->ip6_hops <= 1) {
+ icmp_header->icmp6_type = ICMP6_TIME_EXCEEDED;
+ icmp_header->icmp6_code = ICMP6_TIME_EXCEED_TRANSIT;
+ return ProcessingResult::ICMP;
+ }
+ header->ip6_hops--;
+
+ // Check and extract IP headers.
+ switch (header->ip6_nxt) {
+ case IPPROTO_TCP:
+ case IPPROTO_UDP:
+ case IPPROTO_ICMPV6:
+ *transport_protocol = header->ip6_nxt;
+ *transport_data = (&*packet->begin()) + kIPv6HeaderSize;
+ break;
+ default:
+ icmp_header->icmp6_type = ICMP6_PARAM_PROB;
+ icmp_header->icmp6_code = ICMP6_PARAMPROB_NEXTHEADER;
+ return ProcessingResult::ICMP;
+ }
+
+ return ProcessingResult::OK;
+}
+
+void QbonePacketProcessor::SendIcmpResponse(icmp6_hdr* icmp_header,
+ QuicStringPiece original_packet,
+ Direction original_direction) {
+ in6_addr dst;
+ // TODO(b/70339814): ensure this is actually a unicast address.
+ memcpy(dst.s6_addr, &original_packet[8], kIPv6AddressSize);
+
+ CreateIcmpPacket(self_ip_, dst, *icmp_header, original_packet,
+ [this, original_direction](QuicStringPiece packet) {
+ SendResponse(original_direction, packet);
+ });
+}
+
+void QbonePacketProcessor::SendTcpReset(QuicStringPiece original_packet,
+ Direction original_direction) {
+ CreateTcpResetPacket(original_packet,
+ [this, original_direction](QuicStringPiece packet) {
+ SendResponse(original_direction, packet);
+ });
+}
+
+void QbonePacketProcessor::SendResponse(Direction original_direction,
+ QuicStringPiece packet) {
+ switch (original_direction) {
+ case Direction::FROM_CLIENT:
+ output_->SendPacketToClient(packet);
+ break;
+ case Direction::FROM_NETWORK:
+ output_->SendPacketToNetwork(packet);
+ break;
+ }
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_packet_processor.h b/quic/qbone/qbone_packet_processor.h
new file mode 100644
index 0000000..4476771
--- /dev/null
+++ b/quic/qbone/qbone_packet_processor.h
@@ -0,0 +1,198 @@
+// 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_QBONE_PACKET_PROCESSOR_H_
+#define QUICHE_QUIC_QBONE_QBONE_PACKET_PROCESSOR_H_
+
+#include <netinet/icmp6.h>
+#include <netinet/ip6.h>
+
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ip_address.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+
+namespace quic {
+
+enum : size_t {
+ kIPv6HeaderSize = 40,
+ kICMPv6HeaderSize = sizeof(icmp6_hdr),
+ kTotalICMPv6HeaderSize = kIPv6HeaderSize + kICMPv6HeaderSize,
+};
+
+// QBONE packet processor accepts packets destined in either direction
+// (client-to-network or network-to-client). It inspects them and makes
+// decisions on whether they should be forwarded or dropped, replying with ICMP
+// messages as appropriate.
+class QbonePacketProcessor {
+ public:
+ enum class Direction {
+ // Packet is going from the QBONE client into the network behind the QBONE.
+ FROM_CLIENT = 0,
+ // Packet is going from the network begin QBONE to the client.
+ FROM_NETWORK = 1
+ };
+
+ enum class ProcessingResult {
+ OK = 0,
+ SILENT_DROP = 1,
+ ICMP = 2,
+ // Equivalent to |SILENT_DROP| at the moment, but indicates that the
+ // downstream filter has buffered the packet and deferred its processing.
+ // The packet may be emitted at a later time.
+ DEFER = 3,
+ // In addition to sending an ICMP message, also send a TCP RST. This option
+ // requires the incoming packet to have been a valid TCP packet, as a TCP
+ // RST requires information from the current connection state to be
+ // well-formed.
+ ICMP_AND_TCP_RESET = 4,
+ };
+
+ class OutputInterface {
+ public:
+ virtual ~OutputInterface();
+
+ virtual void SendPacketToClient(QuicStringPiece packet) = 0;
+ virtual void SendPacketToNetwork(QuicStringPiece packet) = 0;
+ };
+
+ class StatsInterface {
+ public:
+ virtual ~StatsInterface();
+
+ virtual void OnPacketForwarded(Direction direction) = 0;
+ virtual void OnPacketDroppedSilently(Direction direction) = 0;
+ virtual void OnPacketDroppedWithIcmp(Direction direction) = 0;
+ virtual void OnPacketDroppedWithTcpReset(Direction direction) = 0;
+ virtual void OnPacketDeferred(Direction direction) = 0;
+ };
+
+ // Allows to implement a custom packet filter on top of the filtering done by
+ // the packet processor itself.
+ class Filter {
+ public:
+ virtual ~Filter();
+ // The main interface function. The following arguments are supplied:
+ // - |direction|, to indicate direction of the packet.
+ // - |full_packet|, which includes the IPv6 header and possibly the IPv6
+ // options that were understood by the processor.
+ // - |payload|, the contents of the IPv6 packet, i.e. a TCP, a UDP or an
+ // ICMP packet.
+ // - |icmp_header|, an output argument which allows the filter to specify
+ // the ICMP message with which the packet is to be rejected.
+ // The method is called only on packets which were already verified as valid
+ // IPv6 packets.
+ //
+ // The implementer of this method has four options to return:
+ // - OK will cause the filter to pass the packet through
+ // - SILENT_DROP will cause the filter to drop the packet silently
+ // - ICMP will cause the filter to drop the packet and send an ICMP
+ // response.
+ // - DEFER will cause the packet to be not forwarded; the filter is
+ // responsible for sending (or not sending) it later using |output|.
+ //
+ // Note that |output| should not be used except in the DEFER case, as the
+ // processor will perform the necessary writes itself.
+ virtual ProcessingResult FilterPacket(Direction direction,
+ QuicStringPiece full_packet,
+ QuicStringPiece payload,
+ icmp6_hdr* icmp_header,
+ OutputInterface* output);
+
+ protected:
+ // Helper methods that allow to easily extract information that is required
+ // for filtering from the |ipv6_header| argument. All of those assume that
+ // the header is of valid size, which is true for everything passed into
+ // FilterPacket().
+ inline uint8_t TransportProtocolFromHeader(QuicStringPiece ipv6_header) {
+ return ipv6_header[6];
+ }
+ inline QuicIpAddress SourceIpFromHeader(QuicStringPiece ipv6_header) {
+ QuicIpAddress address;
+ address.FromPackedString(&ipv6_header[8],
+ QuicIpAddress::kIPv6AddressSize);
+ return address;
+ }
+ inline QuicIpAddress DestinationIpFromHeader(QuicStringPiece ipv6_header) {
+ QuicIpAddress address;
+ address.FromPackedString(&ipv6_header[24],
+ QuicIpAddress::kIPv6AddressSize);
+ return address;
+ }
+ };
+
+ // |self_ip| is the IP address from which the processor will originate ICMP
+ // messages. |client_ip| is the expected IP address of the client, used for
+ // packet validation.
+ //
+ // |output| and |stats| are the visitor interfaces used by the processor.
+ // |output| gets notified whenever the processor decides to send a packet, and
+ // |stats| gets notified about any decisions that processor makes, without a
+ // reference to which packet that decision was made about.
+ QbonePacketProcessor(QuicIpAddress self_ip,
+ QuicIpAddress client_ip,
+ size_t client_ip_subnet_length,
+ OutputInterface* output,
+ StatsInterface* stats);
+ QbonePacketProcessor(const QbonePacketProcessor&) = delete;
+ QbonePacketProcessor& operator=(const QbonePacketProcessor&) = delete;
+
+ // Accepts an IPv6 packet and handles it accordingly by either forwarding it,
+ // replying with an ICMP packet or silently dropping it. |packet| will be
+ // modified in the process, by having the TTL field decreased.
+ void ProcessPacket(string* packet, Direction direction);
+
+ void set_filter(std::unique_ptr<Filter> filter) {
+ filter_ = std::move(filter);
+ }
+
+ void set_client_ip(QuicIpAddress client_ip) { client_ip_ = client_ip; }
+ void set_client_ip_subnet_length(size_t client_ip_subnet_length) {
+ client_ip_subnet_length_ = client_ip_subnet_length;
+ }
+
+ static const QuicIpAddress kInvalidIpAddress;
+
+ protected:
+ // Processes the header and returns what should be done with the packet.
+ // After that, calls an external packet filter if registered. TTL of the
+ // packet may be decreased in the process.
+ ProcessingResult ProcessIPv6HeaderAndFilter(string* packet,
+ Direction direction,
+ uint8_t* transport_protocol,
+ char** transport_data,
+ icmp6_hdr* icmp_header);
+
+ void SendIcmpResponse(icmp6_hdr* icmp_header,
+ QuicStringPiece original_packet,
+ Direction original_direction);
+
+ void SendTcpReset(QuicStringPiece original_packet,
+ Direction original_direction);
+
+ inline bool IsValid() const { return client_ip_ != kInvalidIpAddress; }
+
+ // IP address of the server. Used to send ICMP messages.
+ in6_addr self_ip_;
+ // IP address range of the VPN client.
+ QuicIpAddress client_ip_;
+ size_t client_ip_subnet_length_;
+
+ OutputInterface* output_;
+ StatsInterface* stats_;
+ std::unique_ptr<Filter> filter_;
+
+ private:
+ // Performs basic sanity and permission checks on the packet, and decreases
+ // the TTL.
+ ProcessingResult ProcessIPv6Header(string* packet,
+ Direction direction,
+ uint8_t* transport_protocol,
+ char** transport_data,
+ icmp6_hdr* icmp_header);
+
+ void SendResponse(Direction original_direction, QuicStringPiece packet);
+};
+
+} // namespace quic
+#endif // QUICHE_QUIC_QBONE_QBONE_PACKET_PROCESSOR_H_
diff --git a/quic/qbone/qbone_packet_processor_test.cc b/quic/qbone/qbone_packet_processor_test.cc
new file mode 100644
index 0000000..256f5b0
--- /dev/null
+++ b/quic/qbone/qbone_packet_processor_test.cc
@@ -0,0 +1,283 @@
+// 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/qbone_packet_processor.h"
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_processor_test_tools.h"
+
+namespace quic {
+namespace {
+
+using Direction = QbonePacketProcessor::Direction;
+using ProcessingResult = QbonePacketProcessor::ProcessingResult;
+using OutputInterface = QbonePacketProcessor::OutputInterface;
+using ::testing::_;
+using ::testing::Return;
+
+// clang-format off
+static const char kReferenceClientPacketData[] = {
+ // IPv6 with zero TOS and flow label.
+ 0x60, 0x00, 0x00, 0x00,
+ // Payload size is 8 bytes.
+ 0x00, 0x08,
+ // Next header is UDP
+ 17,
+ // TTL is 50.
+ 50,
+ // IP address of the sender is fd00:0:0:1::1
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // IP address of the receiver is fd00:0:0:5::1
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // Source port 12345
+ 0x30, 0x39,
+ // Destination port 443
+ 0x01, 0xbb,
+ // UDP content length is zero
+ 0x00, 0x00,
+ // Checksum is not actually checked in any of the tests, so we leave it as
+ // zero
+ 0x00, 0x00,
+};
+
+static const char kReferenceNetworkPacketData[] = {
+ // IPv6 with zero TOS and flow label.
+ 0x60, 0x00, 0x00, 0x00,
+ // Payload size is 8 bytes.
+ 0x00, 0x08,
+ // Next header is UDP
+ 17,
+ // TTL is 50.
+ 50,
+ // IP address of the sender is fd00:0:0:5::1
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // IP address of the receiver is fd00:0:0:1::1
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // Source port 443
+ 0x01, 0xbb,
+ // Destination port 12345
+ 0x30, 0x39,
+ // UDP content length is zero
+ 0x00, 0x00,
+ // Checksum is not actually checked in any of the tests, so we leave it as
+ // zero
+ 0x00, 0x00,
+};
+
+static const char kReferenceClientSubnetPacketData[] = {
+ // IPv6 with zero TOS and flow label.
+ 0x60, 0x00, 0x00, 0x00,
+ // Payload size is 8 bytes.
+ 0x00, 0x08,
+ // Next header is UDP
+ 17,
+ // TTL is 50.
+ 50,
+ // IP address of the sender is fd00:0:0:2::1, which is within the /62 of the
+ // client.
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // IP address of the receiver is fd00:0:0:5::1
+ 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,
+ // Source port 12345
+ 0x30, 0x39,
+ // Destination port 443
+ 0x01, 0xbb,
+ // UDP content length is zero
+ 0x00, 0x00,
+ // Checksum is not actually checked in any of the tests, so we leave it as
+ // zero
+ 0x00, 0x00,
+};
+
+// clang-format on
+
+static const QuicStringPiece kReferenceClientPacket(
+ kReferenceClientPacketData,
+ arraysize(kReferenceClientPacketData));
+
+static const QuicStringPiece kReferenceNetworkPacket(
+ kReferenceNetworkPacketData,
+ arraysize(kReferenceNetworkPacketData));
+
+static const QuicStringPiece kReferenceClientSubnetPacket(
+ kReferenceClientSubnetPacketData,
+ arraysize(kReferenceClientSubnetPacketData));
+
+MATCHER_P(IsIcmpMessage,
+ icmp_type,
+ "Checks whether the argument is an ICMP message of supplied type") {
+ if (arg.size() < kTotalICMPv6HeaderSize) {
+ return false;
+ }
+
+ return arg[40] == icmp_type;
+}
+
+class MockPacketFilter : public QbonePacketProcessor::Filter {
+ public:
+ MOCK_METHOD5(FilterPacket,
+ ProcessingResult(Direction,
+ QuicStringPiece,
+ QuicStringPiece,
+ icmp6_hdr*,
+ OutputInterface*));
+};
+
+class QbonePacketProcessorTest : public QuicTest {
+ protected:
+ QbonePacketProcessorTest() {
+ CHECK(client_ip_.FromString("fd00:0:0:1::1"));
+ CHECK(self_ip_.FromString("fd00:0:0:4::1"));
+ CHECK(network_ip_.FromString("fd00:0:0:5::1"));
+
+ processor_ = QuicMakeUnique<QbonePacketProcessor>(
+ self_ip_, client_ip_, /*client_ip_subnet_length=*/62, &output_,
+ &stats_);
+ }
+
+ void SendPacketFromClient(QuicStringPiece packet) {
+ string packet_buffer(packet.data(), packet.size());
+ processor_->ProcessPacket(&packet_buffer, Direction::FROM_CLIENT);
+ }
+
+ void SendPacketFromNetwork(QuicStringPiece packet) {
+ string packet_buffer(packet.data(), packet.size());
+ processor_->ProcessPacket(&packet_buffer, Direction::FROM_NETWORK);
+ }
+
+ QuicIpAddress client_ip_;
+ QuicIpAddress self_ip_;
+ QuicIpAddress network_ip_;
+
+ std::unique_ptr<QbonePacketProcessor> processor_;
+ testing::StrictMock<MockPacketProcessorOutput> output_;
+ testing::StrictMock<MockPacketProcessorStats> stats_;
+};
+
+TEST_F(QbonePacketProcessorTest, EmptyPacket) {
+ EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_CLIENT));
+ SendPacketFromClient("");
+
+ EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_NETWORK));
+ SendPacketFromNetwork("");
+}
+
+TEST_F(QbonePacketProcessorTest, RandomGarbage) {
+ EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_CLIENT));
+ SendPacketFromClient(string(1280, 'a'));
+
+ EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_NETWORK));
+ SendPacketFromNetwork(string(1280, 'a'));
+}
+
+TEST_F(QbonePacketProcessorTest, RandomGarbageWithCorrectLengthFields) {
+ string packet(40, 'a');
+ packet[4] = 0;
+ packet[5] = 0;
+
+ EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_CLIENT));
+ EXPECT_CALL(output_, SendPacketToClient(IsIcmpMessage(ICMP6_DST_UNREACH)));
+ SendPacketFromClient(packet);
+}
+
+TEST_F(QbonePacketProcessorTest, GoodPacketFromClient) {
+ EXPECT_CALL(stats_, OnPacketForwarded(Direction::FROM_CLIENT));
+ EXPECT_CALL(output_, SendPacketToNetwork(_));
+ SendPacketFromClient(kReferenceClientPacket);
+}
+
+TEST_F(QbonePacketProcessorTest, GoodPacketFromClientSubnet) {
+ EXPECT_CALL(stats_, OnPacketForwarded(Direction::FROM_CLIENT));
+ EXPECT_CALL(output_, SendPacketToNetwork(_));
+ SendPacketFromClient(kReferenceClientSubnetPacket);
+}
+
+TEST_F(QbonePacketProcessorTest, GoodPacketFromNetwork) {
+ EXPECT_CALL(stats_, OnPacketForwarded(Direction::FROM_NETWORK));
+ EXPECT_CALL(output_, SendPacketToClient(_));
+ SendPacketFromNetwork(kReferenceNetworkPacket);
+}
+
+TEST_F(QbonePacketProcessorTest, GoodPacketFromNetworkWrongDirection) {
+ EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_CLIENT));
+ EXPECT_CALL(output_, SendPacketToClient(IsIcmpMessage(ICMP6_DST_UNREACH)));
+ SendPacketFromClient(kReferenceNetworkPacket);
+}
+
+TEST_F(QbonePacketProcessorTest, TtlExpired) {
+ string packet(kReferenceNetworkPacket);
+ packet[7] = 1;
+
+ EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_NETWORK));
+ EXPECT_CALL(output_, SendPacketToNetwork(IsIcmpMessage(ICMP6_TIME_EXCEEDED)));
+ SendPacketFromNetwork(packet);
+}
+
+TEST_F(QbonePacketProcessorTest, UnknownProtocol) {
+ string packet(kReferenceNetworkPacket);
+ packet[6] = IPPROTO_SCTP;
+
+ EXPECT_CALL(stats_, OnPacketDroppedWithIcmp(Direction::FROM_NETWORK));
+ EXPECT_CALL(output_, SendPacketToNetwork(IsIcmpMessage(ICMP6_PARAM_PROB)));
+ SendPacketFromNetwork(packet);
+}
+
+TEST_F(QbonePacketProcessorTest, FilterFromClient) {
+ auto filter = QuicMakeUnique<MockPacketFilter>();
+ EXPECT_CALL(*filter, FilterPacket(_, _, _, _, _))
+ .WillRepeatedly(Return(ProcessingResult::SILENT_DROP));
+ processor_->set_filter(std::move(filter));
+
+ EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_CLIENT));
+ SendPacketFromClient(kReferenceClientPacket);
+}
+
+class TestFilter : public QbonePacketProcessor::Filter {
+ public:
+ TestFilter(QuicIpAddress client_ip, QuicIpAddress network_ip)
+ : client_ip_(client_ip), network_ip_(network_ip) {}
+ ProcessingResult FilterPacket(Direction direction,
+ QuicStringPiece full_packet,
+ QuicStringPiece payload,
+ icmp6_hdr* icmp_header,
+ OutputInterface* output) override {
+ EXPECT_EQ(kIPv6HeaderSize, full_packet.size() - payload.size());
+ EXPECT_EQ(IPPROTO_UDP, TransportProtocolFromHeader(full_packet));
+ EXPECT_EQ(client_ip_, SourceIpFromHeader(full_packet));
+ EXPECT_EQ(network_ip_, DestinationIpFromHeader(full_packet));
+
+ called_++;
+ return ProcessingResult::SILENT_DROP;
+ }
+
+ int called() const { return called_; }
+
+ private:
+ int called_ = 0;
+
+ QuicIpAddress client_ip_;
+ QuicIpAddress network_ip_;
+};
+
+// Verify that the parameters are passed correctly into the filter, and that the
+// helper functions of the filter class work.
+TEST_F(QbonePacketProcessorTest, FilterHelperFunctions) {
+ auto filter_owned = QuicMakeUnique<TestFilter>(client_ip_, network_ip_);
+ TestFilter* filter = filter_owned.get();
+ processor_->set_filter(std::move(filter_owned));
+
+ EXPECT_CALL(stats_, OnPacketDroppedSilently(Direction::FROM_CLIENT));
+ SendPacketFromClient(kReferenceClientPacket);
+ ASSERT_EQ(1, filter->called());
+}
+
+} // namespace
+} // namespace quic
diff --git a/quic/qbone/qbone_packet_processor_test_tools.cc b/quic/qbone/qbone_packet_processor_test_tools.cc
new file mode 100644
index 0000000..9f9b623
--- /dev/null
+++ b/quic/qbone/qbone_packet_processor_test_tools.cc
@@ -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.
+
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_processor_test_tools.h"
+
+#include <netinet/ip6.h>
+
+namespace quic {
+
+string PrependIPv6HeaderForTest(const string& body, int hops) {
+ ip6_hdr header;
+ memset(&header, 0, sizeof(header));
+
+ header.ip6_vfc = 6 << 4;
+ header.ip6_plen = htons(body.size());
+ header.ip6_nxt = IPPROTO_UDP;
+ header.ip6_hops = hops;
+ header.ip6_src = in6addr_loopback;
+ header.ip6_dst = in6addr_loopback;
+
+ string packet(sizeof(header) + body.size(), '\0');
+ memcpy(&packet[0], &header, sizeof(header));
+ memcpy(&packet[sizeof(header)], body.data(), body.size());
+ return packet;
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_packet_processor_test_tools.h b/quic/qbone/qbone_packet_processor_test_tools.h
new file mode 100644
index 0000000..646dc42
--- /dev/null
+++ b/quic/qbone/qbone_packet_processor_test_tools.h
@@ -0,0 +1,37 @@
+// 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_QBONE_PACKET_PROCESSOR_TEST_TOOLS_H_
+#define QUICHE_QUIC_QBONE_QBONE_PACKET_PROCESSOR_TEST_TOOLS_H_
+
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_processor.h"
+
+namespace quic {
+
+class MockPacketProcessorOutput : public QbonePacketProcessor::OutputInterface {
+ public:
+ MockPacketProcessorOutput() {}
+
+ MOCK_METHOD1(SendPacketToClient, void(QuicStringPiece));
+ MOCK_METHOD1(SendPacketToNetwork, void(QuicStringPiece));
+};
+
+class MockPacketProcessorStats : public QbonePacketProcessor::StatsInterface {
+ public:
+ MockPacketProcessorStats() {}
+
+ MOCK_METHOD1(OnPacketForwarded, void(QbonePacketProcessor::Direction));
+ MOCK_METHOD1(OnPacketDroppedSilently, void(QbonePacketProcessor::Direction));
+ MOCK_METHOD1(OnPacketDroppedWithIcmp, void(QbonePacketProcessor::Direction));
+ MOCK_METHOD1(OnPacketDroppedWithTcpReset,
+ void(QbonePacketProcessor::Direction));
+ MOCK_METHOD1(OnPacketDeferred, void(QbonePacketProcessor::Direction));
+};
+
+string PrependIPv6HeaderForTest(const string& body, int hops);
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_PACKET_PROCESSOR_TEST_TOOLS_H_
diff --git a/quic/qbone/qbone_packet_writer.h b/quic/qbone/qbone_packet_writer.h
new file mode 100644
index 0000000..1ed8a46
--- /dev/null
+++ b/quic/qbone/qbone_packet_writer.h
@@ -0,0 +1,24 @@
+// 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_QBONE_PACKET_WRITER_H_
+#define QUICHE_QUIC_QBONE_QBONE_PACKET_WRITER_H_
+
+#include <cstring>
+
+namespace quic {
+
+// QbonePacketWriter expects only one function to be defined,
+// WritePacketToNetwork, which is called when a packet is received via QUIC
+// and should be sent out on the network. This is the complete packet,
+// and not just a fragment.
+class QbonePacketWriter {
+ public:
+ virtual ~QbonePacketWriter() {}
+ virtual void WritePacketToNetwork(const char* packet, size_t size) = 0;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_PACKET_WRITER_H_
diff --git a/quic/qbone/qbone_server_session.cc b/quic/qbone/qbone_server_session.cc
new file mode 100644
index 0000000..9b2ebdd
--- /dev/null
+++ b/quic/qbone/qbone_server_session.cc
@@ -0,0 +1,95 @@
+// 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/qbone_server_session.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_connection_id.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h"
+
+namespace quic {
+
+bool QboneCryptoServerStreamHelper::CanAcceptClientHello(
+ const CryptoHandshakeMessage& chlo,
+ const QuicSocketAddress& client_address,
+ const QuicSocketAddress& peer_address,
+ const QuicSocketAddress& self_address,
+ string* error_details) const {
+ absl::string_view alpn;
+ chlo.GetStringPiece(quic::kALPN, &alpn);
+ if (alpn != QboneConstants::kQboneAlpn) {
+ *error_details = "ALPN-indicated protocol is not qbone";
+ return false;
+ }
+ return true;
+}
+
+QboneServerSession::QboneServerSession(
+ const quic::ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ Visitor* owner,
+ const QuicConfig& config,
+ const QuicCryptoServerConfig* quic_crypto_server_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ QbonePacketWriter* writer,
+ QuicIpAddress self_ip,
+ QuicIpAddress client_ip,
+ size_t client_ip_subnet_length,
+ QboneServerControlStream::Handler* handler)
+ : QboneSessionBase(connection, owner, config, supported_versions, writer),
+ processor_(self_ip, client_ip, client_ip_subnet_length, this, this),
+ quic_crypto_server_config_(quic_crypto_server_config),
+ compressed_certs_cache_(compressed_certs_cache),
+ handler_(handler) {}
+
+QboneServerSession::~QboneServerSession() {}
+
+std::unique_ptr<QuicCryptoStream> QboneServerSession::CreateCryptoStream() {
+ return QuicMakeUnique<QuicCryptoServerStream>(quic_crypto_server_config_,
+ compressed_certs_cache_, this,
+ &stream_helper_);
+}
+
+void QboneServerSession::Initialize() {
+ QboneSessionBase::Initialize();
+ // Register the reserved control stream.
+ auto control_stream =
+ QuicMakeUnique<QboneServerControlStream>(this, handler_);
+ control_stream_ = control_stream.get();
+ RegisterStaticStream(std::move(control_stream),
+ /*stream_already_counted = */ false);
+}
+
+bool QboneServerSession::SendClientRequest(const QboneClientRequest& request) {
+ if (!control_stream_) {
+ QUIC_BUG << "Cannot send client request before control stream is created.";
+ return false;
+ }
+ return control_stream_->SendRequest(request);
+}
+
+void QboneServerSession::ProcessPacketFromNetwork(QuicStringPiece packet) {
+ string buffer = string(packet);
+ processor_.ProcessPacket(&buffer,
+ QbonePacketProcessor::Direction::FROM_NETWORK);
+}
+
+void QboneServerSession::ProcessPacketFromPeer(QuicStringPiece packet) {
+ string buffer = string(packet);
+ processor_.ProcessPacket(&buffer,
+ QbonePacketProcessor::Direction::FROM_CLIENT);
+}
+
+void QboneServerSession::SendPacketToClient(QuicStringPiece packet) {
+ SendPacketToPeer(packet);
+}
+
+void QboneServerSession::SendPacketToNetwork(QuicStringPiece packet) {
+ DCHECK(writer_ != nullptr);
+ writer_->WritePacketToNetwork(packet.data(), packet.size());
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_server_session.h b/quic/qbone/qbone_server_session.h
new file mode 100644
index 0000000..9536f87
--- /dev/null
+++ b/quic/qbone/qbone_server_session.h
@@ -0,0 +1,92 @@
+// 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_QBONE_SERVER_SESSION_H_
+#define QUICHE_QUIC_QBONE_QBONE_SERVER_SESSION_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_control.pb.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_control_stream.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_processor.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_writer.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_session_base.h"
+
+namespace quic {
+
+// A helper class is used by the QuicCryptoServerStream.
+class QboneCryptoServerStreamHelper : public QuicCryptoServerStream::Helper {
+ public:
+ // This will look for the qbone alpn.
+ bool CanAcceptClientHello(const CryptoHandshakeMessage& chlo,
+ const QuicSocketAddress& client_address,
+ const QuicSocketAddress& peer_address,
+ const QuicSocketAddress& self_address,
+ string* error_details) const override;
+};
+
+class QUIC_EXPORT_PRIVATE QboneServerSession
+ : public QboneSessionBase,
+ public QbonePacketProcessor::OutputInterface,
+ public QbonePacketProcessor::StatsInterface {
+ public:
+ QboneServerSession(const quic::ParsedQuicVersionVector& supported_versions,
+ QuicConnection* connection,
+ Visitor* owner,
+ const QuicConfig& config,
+ const QuicCryptoServerConfig* quic_crypto_server_config,
+ QuicCompressedCertsCache* compressed_certs_cache,
+ QbonePacketWriter* writer,
+ QuicIpAddress self_ip,
+ QuicIpAddress client_ip,
+ size_t client_ip_subnet_length,
+ QboneServerControlStream::Handler* handler);
+ QboneServerSession(const QboneServerSession&) = delete;
+ QboneServerSession& operator=(const QboneServerSession&) = delete;
+ ~QboneServerSession() override;
+
+ void Initialize() override;
+
+ virtual bool SendClientRequest(const QboneClientRequest& request);
+
+ void ProcessPacketFromNetwork(QuicStringPiece packet) override;
+ void ProcessPacketFromPeer(QuicStringPiece packet) override;
+
+ // QbonePacketProcessor::OutputInterface implementation.
+ void SendPacketToClient(QuicStringPiece packet) override;
+ void SendPacketToNetwork(QuicStringPiece packet) override;
+
+ // QbonePacketProcessor::StatsInterface implementation.
+ void OnPacketForwarded(QbonePacketProcessor::Direction direction) override {}
+ void OnPacketDroppedSilently(
+ QbonePacketProcessor::Direction direction) override {}
+ void OnPacketDroppedWithIcmp(
+ QbonePacketProcessor::Direction direction) override {}
+ void OnPacketDroppedWithTcpReset(
+ QbonePacketProcessor::Direction direction) override {}
+ void OnPacketDeferred(QbonePacketProcessor::Direction direction) override {}
+
+ protected:
+ // QboneSessionBase interface implementation.
+ std::unique_ptr<QuicCryptoStream> CreateCryptoStream() override;
+ // The packet processor.
+ QbonePacketProcessor processor_;
+
+ private:
+ // Config for QUIC crypto server stream, used by the server.
+ const QuicCryptoServerConfig* quic_crypto_server_config_;
+ // Used by QUIC crypto server stream to track most recently compressed certs.
+ QuicCompressedCertsCache* compressed_certs_cache_;
+ // This helper is needed when create QuicCryptoServerStream.
+ QboneCryptoServerStreamHelper stream_helper_;
+ // Passed to the control stream.
+ QboneServerControlStream::Handler* handler_;
+ // The unowned control stream.
+ QboneServerControlStream* control_stream_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_SERVER_SESSION_H_
diff --git a/quic/qbone/qbone_session_base.cc b/quic/qbone/qbone_session_base.cc
new file mode 100644
index 0000000..da2f4ec
--- /dev/null
+++ b/quic/qbone/qbone_session_base.cc
@@ -0,0 +1,148 @@
+// 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/qbone_session_base.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_data_reader.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_exported_stats.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h"
+
+namespace quic {
+
+#define ENDPOINT \
+ (perspective() == Perspective::IS_SERVER ? "Server: " : "Client: ")
+
+QboneSessionBase::QboneSessionBase(
+ QuicConnection* connection,
+ Visitor* owner,
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ QbonePacketWriter* writer)
+ : QuicSession(connection, owner, config, supported_versions) {
+ set_writer(writer);
+ const uint32_t max_streams =
+ (std::numeric_limits<uint32_t>::max() / kMaxAvailableStreamsMultiplier) -
+ 1;
+ this->config()->SetMaxIncomingBidirectionalStreamsToSend(max_streams);
+ if (VersionHasIetfQuicFrames(transport_version())) {
+ this->config()->SetMaxIncomingUnidirectionalStreamsToSend(max_streams);
+ }
+ write_blocked_streams()->SwitchWriteScheduler(
+ spdy::WriteSchedulerType::LIFO, connection->transport_version());
+}
+
+QboneSessionBase::~QboneSessionBase() {
+ // Clear out the streams before leaving this destructor to avoid calling
+ // QuicSession::UnregisterStreamPriority
+ stream_map().clear();
+ closed_streams()->clear();
+}
+
+void QboneSessionBase::Initialize() {
+ crypto_stream_ = CreateCryptoStream();
+ QuicSession::Initialize();
+}
+
+const QuicCryptoStream* QboneSessionBase::GetCryptoStream() const {
+ return crypto_stream_.get();
+}
+
+QuicCryptoStream* QboneSessionBase::GetMutableCryptoStream() {
+ return crypto_stream_.get();
+}
+
+QuicStream* QboneSessionBase::CreateOutgoingStream() {
+ return ActivateDataStream(
+ CreateDataStream(GetNextOutgoingUnidirectionalStreamId()));
+}
+
+void QboneSessionBase::CloseStream(QuicStreamId stream_id) {
+ if (IsClosedStream(stream_id)) {
+ // When CloseStream has been called recursively (via
+ // QuicStream::OnClose), the stream is already closed so return.
+ return;
+ }
+ QuicSession::CloseStream(stream_id);
+}
+
+void QboneSessionBase::OnStreamFrame(const QuicStreamFrame& frame) {
+ if (frame.offset == 0 && frame.fin && frame.data_length > 0) {
+ ++num_ephemeral_packets_;
+ ProcessPacketFromPeer(
+ QuicStringPiece(frame.data_buffer, frame.data_length));
+ flow_controller()->AddBytesConsumed(frame.data_length);
+ return;
+ }
+ QuicSession::OnStreamFrame(frame);
+}
+
+QuicStream* QboneSessionBase::CreateIncomingStream(QuicStreamId id) {
+ return ActivateDataStream(CreateDataStream(id));
+}
+
+QuicStream* QboneSessionBase::CreateIncomingStream(PendingStream* /*pending*/) {
+ QUIC_NOTREACHED();
+ return nullptr;
+}
+
+bool QboneSessionBase::ShouldKeepConnectionAlive() const {
+ // Qbone connections stay alive until they're explicitly closed.
+ return true;
+}
+
+std::unique_ptr<QuicStream> QboneSessionBase::CreateDataStream(
+ QuicStreamId id) {
+ if (crypto_stream_ == nullptr || !crypto_stream_->encryption_established()) {
+ // Encryption not active so no stream created
+ return nullptr;
+ }
+
+ if (IsIncomingStream(id)) {
+ ++num_streamed_packets_;
+ return QuicMakeUnique<QboneReadOnlyStream>(id, this);
+ }
+
+ return QuicMakeUnique<QboneWriteOnlyStream>(id, this);
+}
+
+QuicStream* QboneSessionBase::ActivateDataStream(
+ std::unique_ptr<QuicStream> stream) {
+ // Transfer ownership of the data stream to the session via ActivateStream().
+ QuicStream* raw = stream.get();
+ if (stream) {
+ // Make QuicSession take ownership of the stream.
+ ActivateStream(std::move(stream));
+ }
+ return raw;
+}
+
+void QboneSessionBase::SendPacketToPeer(QuicStringPiece packet) {
+ // Qbone streams are ephemeral.
+ QuicStream* stream = CreateOutgoingStream();
+ if (!stream) {
+ QUIC_BUG << "Failed to create an outgoing QBONE stream.";
+ return;
+ }
+
+ QboneWriteOnlyStream* qbone_stream =
+ static_cast<QboneWriteOnlyStream*>(stream);
+ qbone_stream->WritePacketToQuicStream(packet);
+}
+
+uint64_t QboneSessionBase::GetNumEphemeralPackets() const {
+ return num_ephemeral_packets_;
+}
+
+uint64_t QboneSessionBase::GetNumStreamedPackets() const {
+ return num_streamed_packets_;
+}
+
+void QboneSessionBase::set_writer(QbonePacketWriter* writer) {
+ writer_ = writer;
+ testing::testvalue::Adjust("quic_QbonePacketWriter", &writer_);
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_session_base.h b/quic/qbone/qbone_session_base.h
new file mode 100644
index 0000000..5bfc9be
--- /dev/null
+++ b/quic/qbone/qbone_session_base.h
@@ -0,0 +1,93 @@
+// 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_QBONE_SESSION_BASE_H_
+#define QUICHE_QUIC_QBONE_QBONE_SESSION_BASE_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_crypto_server_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_crypto_stream.h"
+#include "net/third_party/quiche/src/quic/core/quic_error_codes.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_writer.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_stream.h"
+
+namespace quic {
+
+class QUIC_EXPORT_PRIVATE QboneSessionBase : public QuicSession {
+ public:
+ QboneSessionBase(QuicConnection* connection,
+ Visitor* owner,
+ const QuicConfig& config,
+ const ParsedQuicVersionVector& supported_versions,
+ QbonePacketWriter* writer);
+ QboneSessionBase(const QboneSessionBase&) = delete;
+ QboneSessionBase& operator=(const QboneSessionBase&) = delete;
+ ~QboneSessionBase() override;
+
+ // Overrides from QuicSession.
+ // This will ensure that the crypto session is created.
+ void Initialize() override;
+ // This will ensure that we keep track of stream ids that can be
+ // write blocked.
+ void CloseStream(QuicStreamId stream_id) override;
+ // This will check if the packet is wholly contained.
+ void OnStreamFrame(const QuicStreamFrame& frame) override;
+
+ virtual void ProcessPacketFromNetwork(QuicStringPiece packet) = 0;
+ virtual void ProcessPacketFromPeer(QuicStringPiece packet) = 0;
+
+ // Returns the number of qbone network packets that were received
+ // that fit into a single QuicStreamFrame and elided the creation of
+ // a QboneReadOnlyStream.
+ uint64_t GetNumEphemeralPackets() const;
+
+ // Returns the number of qbone network packets that were via
+ // multiple packets, requiring the creation of a QboneReadOnlyStream.
+ uint64_t GetNumStreamedPackets() const;
+
+ void set_writer(QbonePacketWriter* writer);
+
+ protected:
+ virtual std::unique_ptr<QuicCryptoStream> CreateCryptoStream() = 0;
+
+ // QuicSession interface implementation.
+ QuicCryptoStream* GetMutableCryptoStream() override;
+ const QuicCryptoStream* GetCryptoStream() const override;
+ QuicStream* CreateIncomingStream(QuicStreamId id) override;
+ QuicStream* CreateIncomingStream(PendingStream* pending) override;
+ bool ShouldKeepConnectionAlive() const override;
+
+ bool MaybeIncreaseLargestPeerStreamId(const QuicStreamId stream_id) override {
+ return true;
+ }
+
+ QuicStream* CreateOutgoingStream();
+ std::unique_ptr<QuicStream> CreateDataStream(QuicStreamId id);
+ // Activates a QuicStream. The session takes ownership of the stream, but
+ // returns an unowned pointer to the stream for convenience.
+ QuicStream* ActivateDataStream(std::unique_ptr<QuicStream> stream);
+
+ // Accepts a given packet from the network and writes it out
+ // to the QUIC stream. This will create an ephemeral stream per
+ // packet. This function will return true if a stream was created
+ // and the packet sent. It will return false if the stream could not
+ // be created.
+ void SendPacketToPeer(QuicStringPiece packet);
+
+ QbonePacketWriter* writer_;
+
+ private:
+ // Used for the crypto handshake.
+ std::unique_ptr<QuicCryptoStream> crypto_stream_;
+
+ uint64_t num_ephemeral_packets_ = 0;
+ uint64_t num_streamed_packets_ = 0;
+ QuicUnorderedSet<QuicStreamId> reliable_streams_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_SESSION_BASE_H_
diff --git a/quic/qbone/qbone_session_test.cc b/quic/qbone/qbone_session_test.cc
new file mode 100644
index 0000000..80bf08f
--- /dev/null
+++ b/quic/qbone/qbone_session_test.cc
@@ -0,0 +1,519 @@
+// 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/core/proto/crypto_server_config_proto.h"
+#include "net/third_party/quiche/src/quic/core/quic_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/core/quic_epoll_alarm_factory.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_port_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test_loopback.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_client_session.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_control_placeholder.pb.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_packet_processor_test_tools.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_server_session.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_clock.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_connection_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_session_peer.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+
+namespace quic {
+namespace test {
+namespace {
+
+using ::testing::_;
+using ::testing::Contains;
+using ::testing::ElementsAre;
+using ::testing::Eq;
+using ::testing::Invoke;
+using ::testing::NiceMock;
+using ::testing::Not;
+
+string TestPacketIn(const string& body) {
+ return PrependIPv6HeaderForTest(body, 5);
+}
+
+string TestPacketOut(const string& body) {
+ return PrependIPv6HeaderForTest(body, 4);
+}
+
+// Used by QuicCryptoServerConfig to provide server credentials, returning a
+// canned response equal to |success|.
+class FakeProofSource : public ProofSource {
+ public:
+ explicit FakeProofSource(bool success) : success_(success) {}
+
+ // ProofSource override.
+ void GetProof(const QuicSocketAddress& server_address,
+ const string& hostname,
+ const string& server_config,
+ QuicTransportVersion transport_version,
+ QuicStringPiece chlo_hash,
+ std::unique_ptr<Callback> callback) override {
+ QuicReferenceCountedPointer<ProofSource::Chain> chain =
+ GetCertChain(server_address, hostname);
+ QuicCryptoProof proof;
+ if (success_) {
+ proof.signature = "Signature";
+ proof.leaf_cert_scts = "Time";
+ }
+ callback->Run(success_, chain, proof, nullptr /* details */);
+ }
+
+ QuicReferenceCountedPointer<Chain> GetCertChain(
+ const QuicSocketAddress& server_address,
+ const string& hostname) override {
+ if (!success_) {
+ return QuicReferenceCountedPointer<Chain>();
+ }
+ std::vector<string> certs;
+ certs.push_back("Required to establish handshake");
+ return QuicReferenceCountedPointer<ProofSource::Chain>(
+ new ProofSource::Chain(certs));
+ }
+
+ void ComputeTlsSignature(
+ const QuicSocketAddress& server_address,
+ const string& hostname,
+ uint16_t signature_algorithm,
+ QuicStringPiece in,
+ std::unique_ptr<SignatureCallback> callback) override {
+ callback->Run(true, "Signature");
+ }
+
+ private:
+ // Whether or not obtaining proof source succeeds.
+ bool success_;
+};
+
+// Used by QuicCryptoClientConfig to verify server credentials, returning a
+// canned response of QUIC_SUCCESS if |success| is true.
+class FakeProofVerifier : public ProofVerifier {
+ public:
+ explicit FakeProofVerifier(bool success) : success_(success) {}
+
+ // ProofVerifier override
+ QuicAsyncStatus VerifyProof(
+ const string& hostname,
+ const uint16_t port,
+ const string& server_config,
+ QuicTransportVersion transport_version,
+ QuicStringPiece chlo_hash,
+ const std::vector<string>& certs,
+ const string& cert_sct,
+ const string& signature,
+ const ProofVerifyContext* context,
+ string* error_details,
+ std::unique_ptr<ProofVerifyDetails>* verify_details,
+ std::unique_ptr<ProofVerifierCallback> callback) override {
+ return success_ ? QUIC_SUCCESS : QUIC_FAILURE;
+ }
+
+ QuicAsyncStatus VerifyCertChain(
+ const string& hostname,
+ const std::vector<string>& certs,
+ const std::string& ocsp_response,
+ const std::string& cert_sct,
+ const ProofVerifyContext* context,
+ string* error_details,
+ std::unique_ptr<ProofVerifyDetails>* details,
+ std::unique_ptr<ProofVerifierCallback> callback) override {
+ return success_ ? QUIC_SUCCESS : QUIC_FAILURE;
+ }
+
+ std::unique_ptr<ProofVerifyContext> CreateDefaultContext() override {
+ return nullptr;
+ }
+
+ private:
+ // Whether or not proof verification succeeds.
+ bool success_;
+};
+
+class DataSavingQbonePacketWriter : public QbonePacketWriter {
+ public:
+ void WritePacketToNetwork(const char* packet, size_t size) override {
+ data_.push_back(string(packet, size));
+ }
+
+ const std::vector<string>& data() { return data_; }
+
+ private:
+ std::vector<string> data_;
+};
+
+template <class T>
+class DataSavingQboneControlHandler : public QboneControlHandler<T> {
+ public:
+ void OnControlRequest(const T& request) override { data_.push_back(request); }
+
+ void OnControlError() override { error_ = true; }
+
+ const std::vector<T>& data() { return data_; }
+ bool error() { return error_; }
+
+ private:
+ std::vector<T> data_;
+ bool error_ = false;
+};
+
+// Single-threaded scheduled task runner based on a MockClock.
+//
+// Simulates asynchronous execution on a single thread by holding scheduled
+// tasks until Run() is called. Performs no synchronization, assumes that
+// Schedule() and Run() are called on the same thread.
+class FakeTaskRunner {
+ public:
+ explicit FakeTaskRunner(MockQuicConnectionHelper* helper)
+ : tasks_([this](const TaskType& l, const TaskType& r) {
+ // Items at a later time should run after items at an earlier time.
+ // Priority queue comparisons should return true if l appears after r.
+ return l->time() > r->time();
+ }),
+ helper_(helper) {}
+
+ // Runs all tasks in time order. Executes tasks scheduled at
+ // the same in an arbitrary order.
+ void Run() {
+ while (!tasks_.empty()) {
+ tasks_.top()->Run();
+ tasks_.pop();
+ }
+ }
+
+ private:
+ class InnerTask {
+ public:
+ InnerTask(std::function<void()> task, QuicTime time)
+ : task_(std::move(task)), time_(time) {}
+
+ void Cancel() { cancelled_ = true; }
+
+ void Run() {
+ if (!cancelled_) {
+ task_();
+ }
+ }
+
+ QuicTime time() const { return time_; }
+
+ private:
+ bool cancelled_ = false;
+ std::function<void()> task_;
+ QuicTime time_;
+ };
+
+ public:
+ // Schedules a function to run immediately and advances the time.
+ void Schedule(std::function<void()> task) {
+ tasks_.push(std::shared_ptr<InnerTask>(
+ new InnerTask(std::move(task), helper_->GetClock()->Now())));
+ helper_->AdvanceTime(QuicTime::Delta::FromMilliseconds(1));
+ }
+
+ private:
+ using TaskType = std::shared_ptr<InnerTask>;
+ std::priority_queue<TaskType,
+ std::vector<TaskType>,
+ std::function<bool(const TaskType&, const TaskType&)>>
+ tasks_;
+ MockQuicConnectionHelper* helper_;
+};
+
+class QboneSessionTest : public QuicTest {
+ public:
+ QboneSessionTest() : runner_(&helper_), compressed_certs_cache_(100) {}
+
+ ~QboneSessionTest() override {
+ delete client_connection_;
+ delete server_connection_;
+ }
+
+ const MockClock* GetClock() const {
+ return static_cast<const MockClock*>(helper_.GetClock());
+ }
+
+ // The parameters are used to control whether the handshake will success or
+ // not.
+ void CreateClientAndServerSessions(bool client_handshake_success = true,
+ bool server_handshake_success = true,
+ bool send_qbone_alpn = true) {
+ // Quic crashes if packets are sent at time 0, and the clock defaults to 0.
+ helper_.AdvanceTime(QuicTime::Delta::FromMilliseconds(1000));
+ alarm_factory_ = QuicMakeUnique<QuicEpollAlarmFactory>(&epoll_server_);
+ client_writer_ = QuicMakeUnique<DataSavingQbonePacketWriter>();
+ server_writer_ = QuicMakeUnique<DataSavingQbonePacketWriter>();
+ client_handler_ =
+ QuicMakeUnique<DataSavingQboneControlHandler<QboneClientRequest>>();
+ server_handler_ =
+ QuicMakeUnique<DataSavingQboneControlHandler<QboneServerRequest>>();
+ QuicSocketAddress server_address(TestLoopback(), QuicPickUnusedPortOrDie());
+ QuicSocketAddress client_address;
+ if (server_address.host().address_family() == IpAddressFamily::IP_V4) {
+ client_address = QuicSocketAddress(QuicIpAddress::Any4(), 0);
+ } else {
+ client_address = QuicSocketAddress(QuicIpAddress::Any6(), 0);
+ }
+
+ {
+ client_connection_ = new QuicConnection(
+ TestConnectionId(), server_address, &helper_, alarm_factory_.get(),
+ new NiceMock<MockPacketWriter>(), true, Perspective::IS_CLIENT,
+ ParsedVersionOfIndex(AllSupportedVersions(), 0));
+ client_connection_->SetSelfAddress(client_address);
+ QuicConfig config;
+ client_crypto_config_ = QuicMakeUnique<QuicCryptoClientConfig>(
+ QuicMakeUnique<FakeProofVerifier>(client_handshake_success));
+ if (send_qbone_alpn) {
+ client_crypto_config_->set_alpn("qbone");
+ }
+ client_peer_ = QuicMakeUnique<QboneClientSession>(
+ client_connection_, client_crypto_config_.get(),
+ /*owner=*/nullptr, config,
+ ParsedVersionOfIndex(AllSupportedVersions(), 0),
+ QuicServerId("test.example.com", 1234, false), client_writer_.get(),
+ client_handler_.get());
+ }
+
+ {
+ server_connection_ = new QuicConnection(
+ TestConnectionId(), client_address, &helper_, alarm_factory_.get(),
+ new NiceMock<MockPacketWriter>(), true, Perspective::IS_SERVER,
+ ParsedVersionOfIndex(AllSupportedVersions(), 0));
+ server_connection_->SetSelfAddress(server_address);
+ QuicConfig config;
+ server_crypto_config_ = QuicMakeUnique<QuicCryptoServerConfig>(
+ "TESTING", QuicRandom::GetInstance(),
+ std::unique_ptr<FakeProofSource>(
+ new FakeProofSource(server_handshake_success)),
+ KeyExchangeSource::Default());
+ QuicCryptoServerConfig::ConfigOptions options;
+ QuicServerConfigProtobuf primary_config =
+ server_crypto_config_->GenerateConfig(QuicRandom::GetInstance(),
+ GetClock(), options);
+ std::unique_ptr<CryptoHandshakeMessage> message(
+ server_crypto_config_->AddConfig(std::move(primary_config),
+ GetClock()->WallNow()));
+
+ server_peer_ = QuicMakeUnique<QboneServerSession>(
+ AllSupportedVersions(), server_connection_, nullptr, config,
+ server_crypto_config_.get(), &compressed_certs_cache_,
+ server_writer_.get(), TestLoopback6(), TestLoopback6(), 64,
+ server_handler_.get());
+ }
+
+ // Hook everything up!
+ MockPacketWriter* client_writer = static_cast<MockPacketWriter*>(
+ QuicConnectionPeer::GetWriter(client_peer_->connection()));
+ ON_CALL(*client_writer, WritePacket(_, _, _, _, _))
+ .WillByDefault(Invoke([this](const char* buffer, size_t buf_len,
+ const QuicIpAddress& self_address,
+ const QuicSocketAddress& peer_address,
+ PerPacketOptions* options) {
+ char* copy = new char[1024 * 1024];
+ memcpy(copy, buffer, buf_len);
+ runner_.Schedule([this, copy, buf_len] {
+ QuicReceivedPacket packet(copy, buf_len, GetClock()->Now());
+ server_peer_->ProcessUdpPacket(server_connection_->self_address(),
+ client_connection_->self_address(),
+ packet);
+ delete[] copy;
+ });
+ return WriteResult(WRITE_STATUS_OK, buf_len);
+ }));
+ MockPacketWriter* server_writer = static_cast<MockPacketWriter*>(
+ QuicConnectionPeer::GetWriter(server_peer_->connection()));
+ ON_CALL(*server_writer, WritePacket(_, _, _, _, _))
+ .WillByDefault(Invoke([this](const char* buffer, size_t buf_len,
+ const QuicIpAddress& self_address,
+ const QuicSocketAddress& peer_address,
+ PerPacketOptions* options) {
+ char* copy = new char[1024 * 1024];
+ memcpy(copy, buffer, buf_len);
+ runner_.Schedule([this, copy, buf_len] {
+ QuicReceivedPacket packet(copy, buf_len, GetClock()->Now());
+ client_peer_->ProcessUdpPacket(client_connection_->self_address(),
+ server_connection_->self_address(),
+ packet);
+ delete[] copy;
+ });
+ return WriteResult(WRITE_STATUS_OK, buf_len);
+ }));
+ }
+
+ void StartHandshake() {
+ server_peer_->Initialize();
+ client_peer_->Initialize();
+ runner_.Run();
+ }
+
+ // Test handshake establishment and sending/receiving of data for two
+ // directions.
+ void TestStreamConnection() {
+ ASSERT_TRUE(server_peer_->IsCryptoHandshakeConfirmed());
+ ASSERT_TRUE(client_peer_->IsCryptoHandshakeConfirmed());
+ ASSERT_TRUE(server_peer_->IsEncryptionEstablished());
+ ASSERT_TRUE(client_peer_->IsEncryptionEstablished());
+
+ // Create an outgoing stream from the client and say hello.
+ QUIC_LOG(INFO) << "Sending client -> server";
+ client_peer_->ProcessPacketFromNetwork(TestPacketIn("hello"));
+ client_peer_->ProcessPacketFromNetwork(TestPacketIn("world"));
+ runner_.Run();
+ // The server should see the data, the client hasn't received
+ // anything yet.
+ EXPECT_THAT(server_writer_->data(),
+ ElementsAre(TestPacketOut("hello"), TestPacketOut("world")));
+ EXPECT_TRUE(client_writer_->data().empty());
+ EXPECT_EQ(0u, server_peer_->GetNumActiveStreams());
+ EXPECT_EQ(0u, client_peer_->GetNumActiveStreams());
+
+ // Let's pretend some service responds.
+ QUIC_LOG(INFO) << "Sending server -> client";
+ server_peer_->ProcessPacketFromNetwork(TestPacketIn("Hello Again"));
+ server_peer_->ProcessPacketFromNetwork(TestPacketIn("Again"));
+ runner_.Run();
+ EXPECT_THAT(server_writer_->data(),
+ ElementsAre(TestPacketOut("hello"), TestPacketOut("world")));
+ EXPECT_THAT(
+ client_writer_->data(),
+ ElementsAre(TestPacketOut("Hello Again"), TestPacketOut("Again")));
+ EXPECT_EQ(0u, server_peer_->GetNumActiveStreams());
+ EXPECT_EQ(0u, client_peer_->GetNumActiveStreams());
+
+ // Try to send long payloads that are larger than the QUIC MTU but
+ // smaller than the QBONE max size.
+ // This should trigger the non-ephemeral stream code path.
+ string long_data(QboneConstants::kMaxQbonePacketBytes - sizeof(ip6_hdr) - 1,
+ 'A');
+ QUIC_LOG(INFO) << "Sending server -> client long data";
+ server_peer_->ProcessPacketFromNetwork(TestPacketIn(long_data));
+ runner_.Run();
+ EXPECT_THAT(client_writer_->data(), Contains(TestPacketOut(long_data)));
+ EXPECT_THAT(server_writer_->data(),
+ Not(Contains(TestPacketOut(long_data))));
+ EXPECT_EQ(0u, server_peer_->GetNumActiveStreams());
+ EXPECT_EQ(0u, client_peer_->GetNumActiveStreams());
+
+ QUIC_LOG(INFO) << "Sending client -> server long data";
+ client_peer_->ProcessPacketFromNetwork(TestPacketIn(long_data));
+ runner_.Run();
+ EXPECT_THAT(server_writer_->data(), Contains(TestPacketOut(long_data)));
+ EXPECT_THAT(client_peer_->GetNumSentClientHellos(), Eq(2));
+ EXPECT_THAT(client_peer_->GetNumReceivedServerConfigUpdates(), Eq(0));
+ EXPECT_THAT(client_peer_->GetNumEphemeralPackets(), Eq(2));
+ EXPECT_THAT(client_peer_->GetNumStreamedPackets(), Eq(1));
+ EXPECT_THAT(server_peer_->GetNumEphemeralPackets(), Eq(2));
+ EXPECT_THAT(server_peer_->GetNumStreamedPackets(), Eq(1));
+
+ // All streams are ephemeral and should be gone.
+ EXPECT_EQ(0u, server_peer_->GetNumActiveStreams());
+ EXPECT_EQ(0u, client_peer_->GetNumActiveStreams());
+ }
+
+ // Test that client and server are not connected after handshake failure.
+ void TestDisconnectAfterFailedHandshake() {
+ EXPECT_FALSE(client_peer_->IsEncryptionEstablished());
+ EXPECT_FALSE(client_peer_->IsCryptoHandshakeConfirmed());
+
+ EXPECT_FALSE(server_peer_->IsEncryptionEstablished());
+ EXPECT_FALSE(server_peer_->IsCryptoHandshakeConfirmed());
+ }
+
+ protected:
+ QuicEpollServer epoll_server_;
+ std::unique_ptr<QuicAlarmFactory> alarm_factory_;
+ FakeTaskRunner runner_;
+ MockQuicConnectionHelper helper_;
+ QuicConnection* client_connection_;
+ QuicConnection* server_connection_;
+ QuicCompressedCertsCache compressed_certs_cache_;
+
+ std::unique_ptr<QuicCryptoClientConfig> client_crypto_config_;
+ std::unique_ptr<QuicCryptoServerConfig> server_crypto_config_;
+ std::unique_ptr<DataSavingQbonePacketWriter> client_writer_;
+ std::unique_ptr<DataSavingQbonePacketWriter> server_writer_;
+ std::unique_ptr<DataSavingQboneControlHandler<QboneClientRequest>>
+ client_handler_;
+ std::unique_ptr<DataSavingQboneControlHandler<QboneServerRequest>>
+ server_handler_;
+
+ std::unique_ptr<QboneServerSession> server_peer_;
+ std::unique_ptr<QboneClientSession> client_peer_;
+};
+
+TEST_F(QboneSessionTest, StreamConnection) {
+ CreateClientAndServerSessions();
+ StartHandshake();
+ TestStreamConnection();
+}
+
+TEST_F(QboneSessionTest, ClientRejection) {
+ CreateClientAndServerSessions(false /*client_handshake_success*/,
+ true /*server_handshake_success*/,
+ true /*send_qbone_alpn*/);
+ StartHandshake();
+ TestDisconnectAfterFailedHandshake();
+}
+
+TEST_F(QboneSessionTest, BadAlpn) {
+ CreateClientAndServerSessions(true /*client_handshake_success*/,
+ true /*server_handshake_success*/,
+ false /*send_qbone_alpn*/);
+ StartHandshake();
+ TestDisconnectAfterFailedHandshake();
+}
+
+TEST_F(QboneSessionTest, ServerRejection) {
+ CreateClientAndServerSessions(true /*client_handshake_success*/,
+ false /*server_handshake_success*/,
+ true /*send_qbone_alpn*/);
+ StartHandshake();
+ TestDisconnectAfterFailedHandshake();
+}
+
+// Test that data streams are not created before handshake.
+TEST_F(QboneSessionTest, CannotCreateDataStreamBeforeHandshake) {
+ CreateClientAndServerSessions();
+ EXPECT_QUIC_BUG(client_peer_->ProcessPacketFromNetwork(TestPacketIn("hello")),
+ "Failed to create an outgoing QBONE stream");
+ EXPECT_QUIC_BUG(server_peer_->ProcessPacketFromNetwork(TestPacketIn("hello")),
+ "Failed to create an outgoing QBONE stream");
+ EXPECT_EQ(0u, server_peer_->GetNumActiveStreams());
+ EXPECT_EQ(0u, client_peer_->GetNumActiveStreams());
+}
+
+TEST_F(QboneSessionTest, ControlRequests) {
+ CreateClientAndServerSessions();
+ StartHandshake();
+ EXPECT_TRUE(client_handler_->data().empty());
+ EXPECT_FALSE(client_handler_->error());
+ EXPECT_TRUE(server_handler_->data().empty());
+ EXPECT_FALSE(server_handler_->error());
+
+ QboneClientRequest client_request;
+ client_request.SetExtension(client_placeholder, "hello from the server");
+ EXPECT_TRUE(server_peer_->SendClientRequest(client_request));
+ runner_.Run();
+ ASSERT_FALSE(client_handler_->data().empty());
+ EXPECT_THAT(client_handler_->data()[0].GetExtension(client_placeholder),
+ Eq("hello from the server"));
+ EXPECT_FALSE(client_handler_->error());
+
+ QboneServerRequest server_request;
+ server_request.SetExtension(server_placeholder, "hello from the client");
+ EXPECT_TRUE(client_peer_->SendServerRequest(server_request));
+ runner_.Run();
+ ASSERT_FALSE(server_handler_->data().empty());
+ EXPECT_THAT(server_handler_->data()[0].GetExtension(server_placeholder),
+ Eq("hello from the client"));
+ EXPECT_FALSE(server_handler_->error());
+}
+
+} // namespace
+} // namespace test
+} // namespace quic
diff --git a/quic/qbone/qbone_stream.cc b/quic/qbone/qbone_stream.cc
new file mode 100644
index 0000000..b7ac007
--- /dev/null
+++ b/quic/qbone/qbone_stream.cc
@@ -0,0 +1,61 @@
+// 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/qbone_stream.h"
+
+#include "net/third_party/quiche/src/quic/core/quic_data_reader.h"
+#include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
+#include "net/third_party/quiche/src/quic/core/quic_types.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_string_piece.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_text_utils.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_session_base.h"
+
+namespace quic {
+
+QboneWriteOnlyStream::QboneWriteOnlyStream(QuicStreamId id,
+ QuicSession* session)
+ : QuicStream(id, session, /*is_static=*/false, WRITE_UNIDIRECTIONAL) {
+ // QBONE uses a LIFO queue to try to always make progress. An individual
+ // packet may persist for upto to 10 seconds in memory.
+ MaybeSetTtl(QuicTime::Delta::FromSeconds(10));
+}
+
+void QboneWriteOnlyStream::WritePacketToQuicStream(QuicStringPiece packet) {
+ // Streams are one way and ephemeral. This function should only be
+ // called once.
+ WriteOrBufferData(packet, /* fin= */ true, nullptr);
+}
+
+QboneReadOnlyStream::QboneReadOnlyStream(QuicStreamId id,
+ QboneSessionBase* session)
+ : QuicStream(id,
+ session,
+ /*is_static=*/false,
+ READ_UNIDIRECTIONAL),
+ session_(session) {
+ // QBONE uses a LIFO queue to try to always make progress. An individual
+ // packet may persist for upto to 10 seconds in memory.
+ MaybeSetTtl(QuicTime::Delta::FromSeconds(10));
+}
+
+QboneReadOnlyStream::~QboneReadOnlyStream() {}
+
+void QboneReadOnlyStream::OnDataAvailable() {
+ // Read in data and buffer it, attempt to frame to see if there's a packet.
+ sequencer()->Read(&buffer_);
+ if (sequencer()->IsClosed()) {
+ session_->ProcessPacketFromPeer(buffer_);
+ OnFinRead();
+ return;
+ }
+ if (buffer_.size() > QboneConstants::kMaxQbonePacketBytes) {
+ if (!rst_sent()) {
+ Reset(QUIC_BAD_APPLICATION_PAYLOAD);
+ }
+ StopReading();
+ }
+}
+
+} // namespace quic
diff --git a/quic/qbone/qbone_stream.h b/quic/qbone/qbone_stream.h
new file mode 100644
index 0000000..7368005
--- /dev/null
+++ b/quic/qbone/qbone_stream.h
@@ -0,0 +1,55 @@
+// 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_QBONE_STREAM_H_
+#define QUICHE_QUIC_QBONE_QBONE_STREAM_H_
+
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_stream.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_export.h"
+
+namespace quic {
+
+class QboneSessionBase;
+
+// QboneWriteOnlyStream is responsible for sending data for a single
+// packet to the other side.
+// Note that the stream will be created HalfClosed (reads will be closed).
+class QUIC_EXPORT_PRIVATE QboneWriteOnlyStream : public QuicStream {
+ public:
+ QboneWriteOnlyStream(QuicStreamId id, QuicSession* session);
+
+ // QuicStream implementation. Qbone writers are ephemeral and don't
+ // read any data.
+ void OnDataAvailable() override {}
+
+ // Write a network packet over the quic stream.
+ void WritePacketToQuicStream(QuicStringPiece packet);
+};
+
+// QboneReadOnlyStream will be used if we find an incoming stream that
+// isn't fully contained. It will buffer the data when available and
+// attempt to parse it as a packet to send to the network when a FIN
+// is found.
+// Note that the stream will be created HalfClosed (writes will be closed).
+class QUIC_EXPORT_PRIVATE QboneReadOnlyStream : public QuicStream {
+ public:
+ QboneReadOnlyStream(QuicStreamId id, QboneSessionBase* session);
+
+ ~QboneReadOnlyStream() override;
+
+ // QuicStream overrides.
+ // OnDataAvailable is called when there is data in the quic stream buffer.
+ // This will copy the buffer locally and attempt to parse it to write out
+ // packets to the network.
+ void OnDataAvailable() override;
+
+ private:
+ string buffer_;
+ QboneSessionBase* session_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_QBONE_QBONE_STREAM_H_
diff --git a/quic/qbone/qbone_stream_test.cc b/quic/qbone/qbone_stream_test.cc
new file mode 100644
index 0000000..1920aa5
--- /dev/null
+++ b/quic/qbone/qbone_stream_test.cc
@@ -0,0 +1,249 @@
+// 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/qbone_stream.h"
+
+#include "net/third_party/quiche/src/quic/core/crypto/quic_random.h"
+#include "net/third_party/quiche/src/quic/core/quic_session.h"
+#include "net/third_party/quiche/src/quic/core/quic_simple_buffer_allocator.h"
+#include "net/third_party/quiche/src/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_ptr_util.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_test_loopback.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_constants.h"
+#include "net/third_party/quiche/src/quic/qbone/qbone_session_base.h"
+#include "net/third_party/quiche/src/quic/test_tools/mock_clock.h"
+#include "net/third_party/quiche/src/quic/test_tools/quic_test_utils.h"
+#include "net/third_party/quiche/src/spdy/core/spdy_protocol.h"
+
+namespace quic {
+
+namespace {
+
+using ::testing::_;
+using ::testing::StrictMock;
+
+// MockQuicSession that does not create streams and writes data from
+// QuicStream to a string.
+class MockQuicSession : public QboneSessionBase {
+ public:
+ MockQuicSession(QuicConnection* connection, const QuicConfig& config)
+ : QboneSessionBase(connection,
+ nullptr /*visitor*/,
+ config,
+ CurrentSupportedVersions(),
+ nullptr /*writer*/) {}
+
+ ~MockQuicSession() override {}
+
+ // Writes outgoing data from QuicStream to a string.
+ QuicConsumedData WritevData(QuicStream* stream,
+ QuicStreamId id,
+ size_t write_length,
+ QuicStreamOffset offset,
+ StreamSendingState state) override {
+ if (!writable_) {
+ return QuicConsumedData(0, false);
+ }
+
+ return QuicConsumedData(write_length, state != StreamSendingState::NO_FIN);
+ }
+
+ QboneReadOnlyStream* CreateIncomingStream(QuicStreamId id) override {
+ return nullptr;
+ }
+
+ const QuicCryptoStream* GetCryptoStream() const override { return nullptr; }
+ QuicCryptoStream* GetMutableCryptoStream() override { return nullptr; }
+
+ // Called by QuicStream when they want to close stream.
+ MOCK_METHOD3(SendRstStream,
+ void(QuicStreamId, QuicRstStreamErrorCode, QuicStreamOffset));
+
+ // Sets whether data is written to buffer, or else if this is write blocked.
+ void set_writable(bool writable) { writable_ = writable; }
+
+ // Tracks whether the stream is write blocked and its priority.
+ void RegisterReliableStream(QuicStreamId stream_id) {
+ // The priority effectively does not matter. Put all streams on the same
+ // priority.
+ write_blocked_streams()->RegisterStream(
+ stream_id,
+ /*is_static_stream=*/false,
+ /* precedence= */ spdy::SpdyStreamPrecedence(3));
+ }
+
+ // The session take ownership of the stream.
+ void ActivateReliableStream(std::unique_ptr<QuicStream> stream) {
+ ActivateStream(std::move(stream));
+ }
+
+ std::unique_ptr<QuicCryptoStream> CreateCryptoStream() override {
+ return nullptr;
+ }
+
+ MOCK_METHOD1(ProcessPacketFromPeer, void(QuicStringPiece));
+ MOCK_METHOD1(ProcessPacketFromNetwork, void(QuicStringPiece));
+
+ private:
+ // Whether data is written to write_buffer_.
+ bool writable_ = true;
+};
+
+// Packet writer that does nothing. This is required for QuicConnection but
+// isn't used for writing data.
+class DummyPacketWriter : public QuicPacketWriter {
+ public:
+ DummyPacketWriter() {}
+
+ // QuicPacketWriter overrides.
+ WriteResult WritePacket(const char* buffer,
+ size_t buf_len,
+ const QuicIpAddress& self_address,
+ const QuicSocketAddress& peer_address,
+ PerPacketOptions* options) override {
+ return WriteResult(WRITE_STATUS_ERROR, 0);
+ }
+
+ bool IsWriteBlocked() const override { return false; };
+
+ void SetWritable() override {}
+
+ QuicByteCount GetMaxPacketSize(
+ const QuicSocketAddress& peer_address) const override {
+ return 0;
+ }
+
+ bool SupportsReleaseTime() const override { return false; }
+
+ bool IsBatchMode() const override { return false; }
+
+ char* GetNextWriteLocation(const QuicIpAddress& self_address,
+ const QuicSocketAddress& peer_address) override {
+ return nullptr;
+ }
+
+ WriteResult Flush() override { return WriteResult(WRITE_STATUS_OK, 0); }
+};
+
+class QboneReadOnlyStreamTest : public ::testing::Test,
+ public QuicConnectionHelperInterface {
+ public:
+ void CreateReliableQuicStream() {
+ // Arbitrary values for QuicConnection.
+ Perspective perspective = Perspective::IS_SERVER;
+ bool owns_writer = true;
+
+ alarm_factory_ = QuicMakeUnique<test::MockAlarmFactory>();
+
+ connection_.reset(new QuicConnection(
+ test::TestConnectionId(0), QuicSocketAddress(TestLoopback(), 0),
+ this /*QuicConnectionHelperInterface*/, alarm_factory_.get(),
+ new DummyPacketWriter(), owns_writer, perspective,
+ ParsedVersionOfIndex(CurrentSupportedVersions(), 0)));
+ clock_.AdvanceTime(QuicTime::Delta::FromSeconds(1));
+ session_ = QuicMakeUnique<StrictMock<MockQuicSession>>(connection_.get(),
+ QuicConfig());
+ stream_ = new QboneReadOnlyStream(kStreamId, session_.get());
+ session_->ActivateReliableStream(
+ std::unique_ptr<QboneReadOnlyStream>(stream_));
+ }
+
+ ~QboneReadOnlyStreamTest() override {}
+
+ const QuicClock* GetClock() const override { return &clock_; }
+
+ QuicRandom* GetRandomGenerator() override {
+ return QuicRandom::GetInstance();
+ }
+
+ QuicBufferAllocator* GetStreamSendBufferAllocator() override {
+ return &buffer_allocator_;
+ }
+
+ protected:
+ // The QuicSession will take the ownership.
+ QboneReadOnlyStream* stream_;
+ std::unique_ptr<StrictMock<MockQuicSession>> session_;
+ std::unique_ptr<QuicAlarmFactory> alarm_factory_;
+ std::unique_ptr<QuicConnection> connection_;
+ // Used to implement the QuicConnectionHelperInterface.
+ SimpleBufferAllocator buffer_allocator_;
+ MockClock clock_;
+ const QuicStreamId kStreamId = QuicUtils::GetFirstUnidirectionalStreamId(
+ CurrentSupportedVersions()[0].transport_version,
+ Perspective::IS_CLIENT);
+};
+
+// Read an entire string.
+TEST_F(QboneReadOnlyStreamTest, ReadDataWhole) {
+ string packet = "Stuff";
+ CreateReliableQuicStream();
+ QuicStreamFrame frame(kStreamId, true, 0, packet);
+ EXPECT_CALL(*session_, ProcessPacketFromPeer("Stuff"));
+ stream_->OnStreamFrame(frame);
+}
+
+// Test buffering.
+TEST_F(QboneReadOnlyStreamTest, ReadBuffered) {
+ CreateReliableQuicStream();
+ string packet = "Stuf";
+ {
+ QuicStreamFrame frame(kStreamId, false, 0, packet);
+ stream_->OnStreamFrame(frame);
+ }
+ // We didn't write 5 bytes yet...
+
+ packet = "f";
+ EXPECT_CALL(*session_, ProcessPacketFromPeer("Stuff"));
+ {
+ QuicStreamFrame frame(kStreamId, true, 4, packet);
+ stream_->OnStreamFrame(frame);
+ }
+}
+
+TEST_F(QboneReadOnlyStreamTest, ReadOutOfOrder) {
+ CreateReliableQuicStream();
+ string packet = "f";
+ {
+ QuicStreamFrame frame(kStreamId, true, 4, packet);
+ stream_->OnStreamFrame(frame);
+ }
+
+ packet = "S";
+ {
+ QuicStreamFrame frame(kStreamId, false, 0, packet);
+ stream_->OnStreamFrame(frame);
+ }
+
+ packet = "tuf";
+ EXPECT_CALL(*session_, ProcessPacketFromPeer("Stuff"));
+ {
+ QuicStreamFrame frame(kStreamId, false, 1, packet);
+ stream_->OnStreamFrame(frame);
+ }
+}
+
+// Test buffering too many bytes.
+TEST_F(QboneReadOnlyStreamTest, ReadBufferedTooLarge) {
+ CreateReliableQuicStream();
+ string packet = "0123456789";
+ int iterations = (QboneConstants::kMaxQbonePacketBytes / packet.size()) + 2;
+ EXPECT_CALL(*session_,
+ SendRstStream(kStreamId, QUIC_BAD_APPLICATION_PAYLOAD, _));
+ for (int i = 0; i < iterations; ++i) {
+ QuicStreamFrame frame(kStreamId, i == (iterations - 1), i * packet.size(),
+ packet);
+ if (!stream_->reading_stopped()) {
+ stream_->OnStreamFrame(frame);
+ }
+ }
+ // We should have nothing written to the network and the stream
+ // should have stopped reading.
+ EXPECT_TRUE(stream_->reading_stopped());
+}
+
+} // namespace
+
+} // namespace quic