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/core/congestion_control/bbr2_misc.h b/quic/core/congestion_control/bbr2_misc.h index 0dee438..4ad351e 100644 --- a/quic/core/congestion_control/bbr2_misc.h +++ b/quic/core/congestion_control/bbr2_misc.h
@@ -87,7 +87,8 @@ QuicRoundTripCount startup_full_bw_rounds = 3; // The minimum number of loss marking events to exit STARTUP. - int64_t startup_full_loss_count = 8; + int64_t startup_full_loss_count = + GetQuicFlag(FLAGS_quic_bbr2_default_startup_full_loss_count); /* * DRAIN parameters.
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