Add IP/UDP packet generation to QUIC test tools

PiperOrigin-RevId: 590635487
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 430c8c8..bd02a2f 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -847,6 +847,7 @@
     "quic/test_tools/simulator/test_harness.h",
     "quic/test_tools/simulator/traffic_policer.h",
     "quic/test_tools/test_certificates.h",
+    "quic/test_tools/test_ip_packets.h",
     "quic/test_tools/test_ticket_crypter.h",
     "quic/test_tools/web_transport_resets_backend.h",
     "quic/test_tools/web_transport_test_tools.h",
@@ -941,6 +942,7 @@
     "quic/test_tools/simulator/test_harness.cc",
     "quic/test_tools/simulator/traffic_policer.cc",
     "quic/test_tools/test_certificates.cc",
+    "quic/test_tools/test_ip_packets.cc",
     "quic/test_tools/test_ticket_crypter.cc",
     "quic/test_tools/web_transport_resets_backend.cc",
     "spdy/test_tools/mock_spdy_framer_visitor.cc",
@@ -1293,6 +1295,7 @@
     "quic/test_tools/simple_session_notifier_test.cc",
     "quic/test_tools/simulator/quic_endpoint_test.cc",
     "quic/test_tools/simulator/simulator_test.cc",
+    "quic/test_tools/test_ip_packets_test.cc",
     "quic/tools/connect_tunnel_test.cc",
     "quic/tools/connect_udp_tunnel_test.cc",
     "quic/tools/quic_memory_cache_backend_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index 2df04d0..49ae31f 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -847,6 +847,7 @@
     "src/quiche/quic/test_tools/simulator/test_harness.h",
     "src/quiche/quic/test_tools/simulator/traffic_policer.h",
     "src/quiche/quic/test_tools/test_certificates.h",
+    "src/quiche/quic/test_tools/test_ip_packets.h",
     "src/quiche/quic/test_tools/test_ticket_crypter.h",
     "src/quiche/quic/test_tools/web_transport_resets_backend.h",
     "src/quiche/quic/test_tools/web_transport_test_tools.h",
@@ -941,6 +942,7 @@
     "src/quiche/quic/test_tools/simulator/test_harness.cc",
     "src/quiche/quic/test_tools/simulator/traffic_policer.cc",
     "src/quiche/quic/test_tools/test_certificates.cc",
+    "src/quiche/quic/test_tools/test_ip_packets.cc",
     "src/quiche/quic/test_tools/test_ticket_crypter.cc",
     "src/quiche/quic/test_tools/web_transport_resets_backend.cc",
     "src/quiche/spdy/test_tools/mock_spdy_framer_visitor.cc",
@@ -1294,6 +1296,7 @@
     "src/quiche/quic/test_tools/simple_session_notifier_test.cc",
     "src/quiche/quic/test_tools/simulator/quic_endpoint_test.cc",
     "src/quiche/quic/test_tools/simulator/simulator_test.cc",
+    "src/quiche/quic/test_tools/test_ip_packets_test.cc",
     "src/quiche/quic/tools/connect_tunnel_test.cc",
     "src/quiche/quic/tools/connect_udp_tunnel_test.cc",
     "src/quiche/quic/tools/quic_memory_cache_backend_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index 4b1ae54..252c803 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -846,6 +846,7 @@
     "quiche/quic/test_tools/simulator/test_harness.h",
     "quiche/quic/test_tools/simulator/traffic_policer.h",
     "quiche/quic/test_tools/test_certificates.h",
+    "quiche/quic/test_tools/test_ip_packets.h",
     "quiche/quic/test_tools/test_ticket_crypter.h",
     "quiche/quic/test_tools/web_transport_resets_backend.h",
     "quiche/quic/test_tools/web_transport_test_tools.h",
@@ -940,6 +941,7 @@
     "quiche/quic/test_tools/simulator/test_harness.cc",
     "quiche/quic/test_tools/simulator/traffic_policer.cc",
     "quiche/quic/test_tools/test_certificates.cc",
+    "quiche/quic/test_tools/test_ip_packets.cc",
     "quiche/quic/test_tools/test_ticket_crypter.cc",
     "quiche/quic/test_tools/web_transport_resets_backend.cc",
     "quiche/spdy/test_tools/mock_spdy_framer_visitor.cc",
@@ -1293,6 +1295,7 @@
     "quiche/quic/test_tools/simple_session_notifier_test.cc",
     "quiche/quic/test_tools/simulator/quic_endpoint_test.cc",
     "quiche/quic/test_tools/simulator/simulator_test.cc",
+    "quiche/quic/test_tools/test_ip_packets_test.cc",
     "quiche/quic/tools/connect_tunnel_test.cc",
     "quiche/quic/tools/connect_udp_tunnel_test.cc",
     "quiche/quic/tools/quic_memory_cache_backend_test.cc",
diff --git a/quiche/quic/core/internet_checksum.cc b/quiche/quic/core/internet_checksum.cc
index 746bd00..64bf1c7 100644
--- a/quiche/quic/core/internet_checksum.cc
+++ b/quiche/quic/core/internet_checksum.cc
@@ -7,6 +7,9 @@
 #include <stdint.h>
 #include <string.h>
 
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
+
 namespace quic {
 
 void InternetChecksum::Update(const char* data, size_t size) {
@@ -25,6 +28,14 @@
   Update(reinterpret_cast<const char*>(data), size);
 }
 
+void InternetChecksum::Update(absl::string_view data) {
+  Update(data.data(), data.size());
+}
+
+void InternetChecksum::Update(absl::Span<const uint8_t> data) {
+  Update(reinterpret_cast<const char*>(data.data()), data.size());
+}
+
 uint16_t InternetChecksum::Value() const {
   uint32_t total = accumulator_;
   while (total & 0xffff0000u) {
diff --git a/quiche/quic/core/internet_checksum.h b/quiche/quic/core/internet_checksum.h
index 1faa0f0..7a23135 100644
--- a/quiche/quic/core/internet_checksum.h
+++ b/quiche/quic/core/internet_checksum.h
@@ -8,6 +8,8 @@
 #include <cstddef>
 #include <cstdint>
 
+#include "absl/strings/string_view.h"
+#include "absl/types/span.h"
 #include "quiche/common/platform/api/quiche_export.h"
 
 namespace quic {
@@ -20,8 +22,9 @@
   // 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);
+  void Update(absl::string_view data);
+  void Update(absl::Span<const uint8_t> data);
 
   uint16_t Value() const;
 
diff --git a/quiche/quic/test_tools/test_ip_packets.cc b/quiche/quic/test_tools/test_ip_packets.cc
new file mode 100644
index 0000000..c1b4b1f
--- /dev/null
+++ b/quiche/quic/test_tools/test_ip_packets.cc
@@ -0,0 +1,208 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/test_tools/test_ip_packets.h"
+
+#include <cstdint>
+#include <limits>
+#include <string>
+
+#include "absl/strings/str_cat.h"
+#include "absl/strings/string_view.h"
+#include "quiche/quic/core/internet_checksum.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/common/platform/api/quiche_logging.h"
+#include "quiche/common/quiche_data_writer.h"
+#include "quiche/common/quiche_endian.h"
+#include "quiche/common/quiche_ip_address.h"
+#include "quiche/common/quiche_ip_address_family.h"
+
+#if defined(__linux__)
+#include <netinet/in.h>
+#include <netinet/ip.h>
+#include <netinet/ip6.h>
+#include <netinet/udp.h>
+#endif
+
+namespace quic::test {
+
+namespace {
+
+// RFC791, Section 3.1. Size without the optional Options field.
+constexpr uint16_t kIpv4HeaderSize = 20;
+
+// RFC8200, Section 3.
+constexpr uint16_t kIpv6HeaderSize = 40;
+
+// RFC768.
+constexpr uint16_t kUdpHeaderSize = 8;
+constexpr uint8_t kUdpProtocol = 0x11;
+
+// For Windows compatibility, avoid dependency on netinet, but when building on
+// Linux, check that the constants match.
+#if defined(__linux__)
+static_assert(kIpv4HeaderSize == sizeof(iphdr));
+static_assert(kIpv6HeaderSize == sizeof(ip6_hdr));
+static_assert(kUdpHeaderSize == sizeof(udphdr));
+static_assert(kUdpProtocol == IPPROTO_UDP);
+#endif
+
+std::string CreateIpv4Header(int payload_length,
+                             quiche::QuicheIpAddress source_address,
+                             quiche::QuicheIpAddress destination_address,
+                             uint8_t protocol) {
+  QUICHE_CHECK_GT(payload_length, 0);
+  QUICHE_CHECK_LE(payload_length,
+                  std::numeric_limits<uint16_t>::max() - kIpv4HeaderSize);
+  QUICHE_CHECK(source_address.address_family() ==
+               quiche::IpAddressFamily::IP_V4);
+  QUICHE_CHECK(destination_address.address_family() ==
+               quiche::IpAddressFamily::IP_V4);
+
+  std::string header(kIpv4HeaderSize, '\0');
+  quiche::QuicheDataWriter header_writer(header.size(), header.data());
+
+  header_writer.WriteUInt8(0x45);  // Version: 4, Header length: 5 words
+  header_writer.WriteUInt8(0x00);  // DSCP: 0, ECN: 0
+  header_writer.WriteUInt16(kIpv4HeaderSize + payload_length);  // Total length
+  header_writer.WriteUInt16(0x0000);  // Identification: 0 (replaced by socket)
+  header_writer.WriteUInt16(0x0000);  // Flags: 0, Fragment offset: 0
+  header_writer.WriteUInt8(64);       // TTL: 64 hops/seconds
+  header_writer.WriteUInt8(protocol);
+  header_writer.WriteUInt16(0x0000);  // Checksum (replaced by socket)
+  header_writer.WriteStringPiece(source_address.ToPackedString());
+  header_writer.WriteStringPiece(destination_address.ToPackedString());
+  QUICHE_CHECK_EQ(header_writer.remaining(), 0u);
+
+  return header;
+}
+
+std::string CreateIpv6Header(int payload_length,
+                             quiche::QuicheIpAddress source_address,
+                             quiche::QuicheIpAddress destination_address,
+                             uint8_t next_header) {
+  QUICHE_CHECK_GT(payload_length, 0);
+  QUICHE_CHECK_LE(payload_length, std::numeric_limits<uint16_t>::max());
+  QUICHE_CHECK(source_address.address_family() ==
+               quiche::IpAddressFamily::IP_V6);
+  QUICHE_CHECK(destination_address.address_family() ==
+               quiche::IpAddressFamily::IP_V6);
+
+  std::string header(kIpv6HeaderSize, '\0');
+  quiche::QuicheDataWriter header_writer(header.size(), header.data());
+
+  // Version: 6
+  // Traffic class: 0
+  // Flow label: 0 (possibly replaced by socket)
+  header_writer.WriteUInt32(0x60000000);
+
+  header_writer.WriteUInt16(payload_length);
+  header_writer.WriteUInt8(next_header);
+  header_writer.WriteUInt8(64);  // Hop limit: 64
+  header_writer.WriteStringPiece(source_address.ToPackedString());
+  header_writer.WriteStringPiece(destination_address.ToPackedString());
+  QUICHE_CHECK_EQ(header_writer.remaining(), 0u);
+
+  return header;
+}
+
+}  // namespace
+
+std::string CreateIpPacket(const quiche::QuicheIpAddress& source_address,
+                           const quiche::QuicheIpAddress& destination_address,
+                           absl::string_view payload,
+                           IpPacketPayloadType payload_type) {
+  QUICHE_CHECK(source_address.address_family() ==
+               destination_address.address_family());
+
+  uint8_t payload_protocol;
+  switch (payload_type) {
+    case IpPacketPayloadType::kUdp:
+      payload_protocol = kUdpProtocol;
+      break;
+    default:
+      QUICHE_NOTREACHED();
+      return "";
+  }
+
+  std::string header;
+  switch (source_address.address_family()) {
+    case quiche::IpAddressFamily::IP_V4:
+      header = CreateIpv4Header(payload.size(), source_address,
+                                destination_address, payload_protocol);
+      break;
+    case quiche::IpAddressFamily::IP_V6:
+      header = CreateIpv6Header(payload.size(), source_address,
+                                destination_address, payload_protocol);
+      break;
+    default:
+      QUICHE_NOTREACHED();
+      return "";
+  }
+
+  return absl::StrCat(header, payload);
+}
+
+std::string CreateUdpPacket(const QuicSocketAddress& source_address,
+                            const QuicSocketAddress& destination_address,
+                            absl::string_view payload) {
+  QUICHE_CHECK(source_address.host().address_family() ==
+               destination_address.host().address_family());
+  QUICHE_CHECK(!payload.empty());
+  QUICHE_CHECK_LE(payload.size(),
+                  static_cast<uint16_t>(std::numeric_limits<uint16_t>::max() -
+                                        kUdpHeaderSize));
+
+  std::string header(kUdpHeaderSize, '\0');
+  quiche::QuicheDataWriter header_writer(header.size(), header.data());
+
+  header_writer.WriteUInt16(source_address.port());
+  header_writer.WriteUInt16(destination_address.port());
+  header_writer.WriteUInt16(kUdpHeaderSize + payload.size());
+
+  InternetChecksum checksum;
+  switch (source_address.host().address_family()) {
+    case quiche::IpAddressFamily::IP_V4: {
+      // IP pseudo header information. See RFC768.
+      checksum.Update(source_address.host().ToPackedString());
+      checksum.Update(destination_address.host().ToPackedString());
+      uint8_t protocol[] = {0x00, kUdpProtocol};
+      checksum.Update(protocol, sizeof(protocol));
+      uint16_t udp_length =
+          quiche::QuicheEndian::HostToNet16(kUdpHeaderSize + payload.size());
+      checksum.Update(reinterpret_cast<uint8_t*>(&udp_length),
+                      sizeof(udp_length));
+      break;
+    }
+    case quiche::IpAddressFamily::IP_V6: {
+      // IP pseudo header information. See RFC8200, Section 8.1.
+      checksum.Update(source_address.host().ToPackedString());
+      checksum.Update(destination_address.host().ToPackedString());
+      uint32_t udp_length =
+          quiche::QuicheEndian::HostToNet32(kUdpHeaderSize + payload.size());
+      checksum.Update(reinterpret_cast<uint8_t*>(&udp_length),
+                      sizeof(udp_length));
+      uint8_t protocol[] = {0x00, 0x00, 0x00, kUdpProtocol};
+      checksum.Update(protocol, sizeof(protocol));
+      break;
+    }
+    default:
+      QUICHE_NOTREACHED();
+      return "";
+  }
+
+  checksum.Update(header.data(), header.size());
+  checksum.Update(payload.data(), payload.size());
+  uint16_t checksum_val = checksum.Value();
+
+  // Checksum is always written in the same byte order in which it was
+  // calculated.
+  header_writer.WriteBytes(&checksum_val, sizeof(checksum_val));
+
+  QUICHE_CHECK_EQ(header_writer.remaining(), 0u);
+
+  return absl::StrCat(header, payload);
+}
+
+}  // namespace quic::test
diff --git a/quiche/quic/test_tools/test_ip_packets.h b/quiche/quic/test_tools/test_ip_packets.h
new file mode 100644
index 0000000..cda55e5
--- /dev/null
+++ b/quiche/quic/test_tools/test_ip_packets.h
@@ -0,0 +1,35 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef QUICHE_QUIC_TEST_TOOLS_IP_PACKET_GENERATION_H_
+#define QUICHE_QUIC_TEST_TOOLS_IP_PACKET_GENERATION_H_
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/common/quiche_ip_address.h"
+
+namespace quic::test {
+
+enum class IpPacketPayloadType {
+  kUdp,
+};
+
+// Create an IP packet, appropriate for sending to a raw IP socket.
+std::string CreateIpPacket(
+    const quiche::QuicheIpAddress& source_address,
+    const quiche::QuicheIpAddress& destination_address,
+    absl::string_view payload,
+    IpPacketPayloadType payload_type = IpPacketPayloadType::kUdp);
+
+// Create a UDP packet, appropriate for sending to a raw UDP socket or including
+// as the payload of an IP packet.
+std::string CreateUdpPacket(const QuicSocketAddress& source_address,
+                            const QuicSocketAddress& destination_address,
+                            absl::string_view payload);
+
+}  // namespace quic::test
+
+#endif  // QUICHE_QUIC_TEST_TOOLS_IP_PACKET_GENERATION_H_
diff --git a/quiche/quic/test_tools/test_ip_packets_test.cc b/quiche/quic/test_tools/test_ip_packets_test.cc
new file mode 100644
index 0000000..e60fc13
--- /dev/null
+++ b/quiche/quic/test_tools/test_ip_packets_test.cc
@@ -0,0 +1,90 @@
+// Copyright 2023 The Chromium Authors
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/test_tools/test_ip_packets.h"
+
+#include <string>
+
+#include "absl/strings/string_view.h"
+#include "quiche/quic/platform/api/quic_socket_address.h"
+#include "quiche/common/platform/api/quiche_test.h"
+#include "quiche/common/quiche_ip_address.h"
+
+namespace quic::test {
+namespace {
+
+TEST(TestIpPacketsTest, CreateIpv4Packet) {
+  quiche::QuicheIpAddress source_ip;
+  ASSERT_TRUE(source_ip.FromString("192.0.2.45"));
+  ASSERT_TRUE(source_ip.IsIPv4());
+  QuicSocketAddress source_address{source_ip, /*port=*/54131};
+
+  quiche::QuicheIpAddress destination_ip;
+  ASSERT_TRUE(destination_ip.FromString("192.0.2.67"));
+  ASSERT_TRUE(destination_ip.IsIPv4());
+  QuicSocketAddress destination_address(destination_ip, /*port=*/57542);
+
+  std::string packet =
+      CreateIpPacket(source_ip, destination_ip,
+                     CreateUdpPacket(source_address, destination_address,
+                                     /*payload=*/"foo"),
+                     IpPacketPayloadType::kUdp);
+
+  constexpr static char kExpected[] =
+      "\x45"              // Version: 4, Header length: 5 words
+      "\x00"              // DSCP: 0, ECN: 0
+      "\x00\x1F"          // Total length: 31
+      "\x00\x00"          // Id: 0
+      "\x00\x00"          // Flags: 0, Fragment offset: 0
+      "\x40"              // TTL: 64 hops
+      "\x11"              // Protocol: 17 (UDP)
+      "\x00\x00"          // Header checksum: 0
+      "\xC0\x00\x02\x2D"  // Source IP
+      "\xC0\x00\x02\x43"  // Destination IP
+      "\xD3\x73"          // Source port
+      "\xE0\xC6"          // Destination port
+      "\x00\x0B"          // Length: 11
+      "\xF1\xBC"          // Checksum: 0xF1BC
+      "foo";              // Payload
+  EXPECT_EQ(absl::string_view(packet),
+            absl::string_view(kExpected, sizeof(kExpected) - 1));
+}
+
+TEST(TestIpPacketsTest, CreateIpv6Packet) {
+  quiche::QuicheIpAddress source_ip;
+  ASSERT_TRUE(source_ip.FromString("2001:db8::45"));
+  ASSERT_TRUE(source_ip.IsIPv6());
+  QuicSocketAddress source_address{source_ip, /*port=*/51941};
+
+  quiche::QuicheIpAddress destination_ip;
+  ASSERT_TRUE(destination_ip.FromString("2001:db8::67"));
+  ASSERT_TRUE(destination_ip.IsIPv6());
+  QuicSocketAddress destination_address(destination_ip, /*port=*/55341);
+
+  std::string packet =
+      CreateIpPacket(source_ip, destination_ip,
+                     CreateUdpPacket(source_address, destination_address,
+                                     /*payload=*/"foo"),
+                     IpPacketPayloadType::kUdp);
+
+  constexpr static char kExpected[] =
+      "\x60\x00\x00\x00"  // Version: 6, Traffic class: 0, Flow label: 0
+      "\x00\x0b"          // Payload length: 11
+      "\x11"              // Next header: 17 (UDP)
+      "\x40"              // Hop limit: 64
+      // Source IP
+      "\x20\x01\x0D\xB8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x45"
+      // Destination IP
+      "\x20\x01\x0D\xB8\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x67"
+      "\xCA\xE5"  // Source port
+      "\xD8\x2D"  // Destination port
+      "\x00\x0B"  // Length: 11
+      "\x2B\x37"  // Checksum: 0x2B37
+      "foo";      // Payload
+  EXPECT_EQ(absl::string_view(packet),
+            absl::string_view(kExpected, sizeof(kExpected) - 1));
+}
+
+}  // namespace
+}  // namespace quic::test