gfe-relnote: Default-initialize QUIC BBRv2 loss event threshold for exiting STARTUP from a flag. Protected by --gfe2_reloadable_flag_quic_default_to_bbr_v2.
PiperOrigin-RevId: 264298542
Change-Id: I304ab19e4820dec51d3f8ef53762a393f6b175fd
diff --git a/quic/qbone/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