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