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