Implement support for reading and writing IPv6 flow labels in QuicUdpSocket. Protected by FLAGS_quic_reloadable_flag_quic_support_flow_label. PiperOrigin-RevId: 684868287
diff --git a/build/source_list.bzl b/build/source_list.bzl index b663445..5aeac68 100644 --- a/build/source_list.bzl +++ b/build/source_list.bzl
@@ -1311,6 +1311,7 @@ "quic/core/quic_time_test.cc", "quic/core/quic_time_wait_list_manager_test.cc", "quic/core/quic_trace_visitor_test.cc", + "quic/core/quic_udp_socket_test.cc", "quic/core/quic_unacked_packet_map_test.cc", "quic/core/quic_utils_test.cc", "quic/core/quic_version_manager_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni index 35732c7..ce85b0e 100644 --- a/build/source_list.gni +++ b/build/source_list.gni
@@ -1312,6 +1312,7 @@ "src/quiche/quic/core/quic_time_test.cc", "src/quiche/quic/core/quic_time_wait_list_manager_test.cc", "src/quiche/quic/core/quic_trace_visitor_test.cc", + "src/quiche/quic/core/quic_udp_socket_test.cc", "src/quiche/quic/core/quic_unacked_packet_map_test.cc", "src/quiche/quic/core/quic_utils_test.cc", "src/quiche/quic/core/quic_version_manager_test.cc",
diff --git a/build/source_list.json b/build/source_list.json index 1f6ca08..c10a0a9 100644 --- a/build/source_list.json +++ b/build/source_list.json
@@ -1311,6 +1311,7 @@ "quiche/quic/core/quic_time_test.cc", "quiche/quic/core/quic_time_wait_list_manager_test.cc", "quiche/quic/core/quic_trace_visitor_test.cc", + "quiche/quic/core/quic_udp_socket_test.cc", "quiche/quic/core/quic_unacked_packet_map_test.cc", "quiche/quic/core/quic_utils_test.cc", "quiche/quic/core/quic_version_manager_test.cc",
diff --git a/quiche/common/quiche_feature_flags_list.h b/quiche/common/quiche_feature_flags_list.h index fd1fd04..2235d21 100755 --- a/quiche/common/quiche_feature_flags_list.h +++ b/quiche/common/quiche_feature_flags_list.h
@@ -49,6 +49,7 @@ QUICHE_FLAG(bool, quiche_reloadable_flag_quic_require_handshake_confirmation, true, true, "If true, require handshake confirmation for QUIC connections, functionally disabling 0-rtt handshakes.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_send_placeholder_ticket_when_encrypt_ticket_fails, false, true, "If true, when TicketCrypter fails to encrypt a session ticket, quic::TlsServerHandshaker will send a placeholder ticket, instead of an empty one, to the client.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_stop_reading_also_stops_header_decompression, true, true, "If true, QUIC stream will not continue decompressing buffer headers after StopReading() called.") +QUICHE_FLAG(bool, quiche_reloadable_flag_quic_support_flow_label, false, false, "If true, QUIC will support reading and writing IPv6 flow labels.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_test_peer_addr_change_after_normalize, false, false, "If true, QuicConnection::ProcessValidatedPacket will use normalized address to test peer address changes.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_testonly_default_false, false, false, "A testonly reloadable flag that will always default to false.") QUICHE_FLAG(bool, quiche_reloadable_flag_quic_testonly_default_true, true, true, "A testonly reloadable flag that will always default to true.")
diff --git a/quiche/quic/core/quic_udp_socket.h b/quiche/quic/core/quic_udp_socket.h index 404563f..001bc85 100644 --- a/quiche/quic/core/quic_udp_socket.h +++ b/quiche/quic/core/quic_udp_socket.h
@@ -36,6 +36,7 @@ ECN, // Read GOOGLE_PACKET_HEADER, // Read IS_GRO, // Read + V6_FLOW_LABEL, // Read & Write // Must be the last value. NUM_BITS @@ -154,13 +155,26 @@ bitmask_.Set(QuicUdpPacketInfoBit::GOOGLE_PACKET_HEADER); } - QuicEcnCodepoint ecn_codepoint() const { return ecn_codepoint_; } + QuicEcnCodepoint ecn_codepoint() const { + QUICHE_DCHECK(HasValue(QuicUdpPacketInfoBit::ECN)); + return ecn_codepoint_; + } void SetEcnCodepoint(const QuicEcnCodepoint ecn_codepoint) { ecn_codepoint_ = ecn_codepoint; bitmask_.Set(QuicUdpPacketInfoBit::ECN); } + uint32_t flow_label() const { + QUICHE_DCHECK(HasValue(QuicUdpPacketInfoBit::V6_FLOW_LABEL)); + return ipv6_flow_label_; + } + + void SetFlowLabel(uint32_t ipv6_flow_label) { + ipv6_flow_label_ = ipv6_flow_label; + bitmask_.Set(QuicUdpPacketInfoBit::V6_FLOW_LABEL); + } + private: QuicUdpPacketInfoBitMask bitmask_; QuicPacketCount dropped_packets_; @@ -172,6 +186,7 @@ BufferSpan google_packet_headers_; size_t gso_size_ = 0; QuicEcnCodepoint ecn_codepoint_ = ECN_NOT_ECT; + uint32_t ipv6_flow_label_ = 0; }; // QuicUdpSocketApi provides a minimal set of apis for sending and receiving
diff --git a/quiche/quic/core/quic_udp_socket_posix.inc b/quiche/quic/core/quic_udp_socket_posix.inc index 9293bf2..0b8d7b6 100644 --- a/quiche/quic/core/quic_udp_socket_posix.inc +++ b/quiche/quic/core/quic_udp_socket_posix.inc
@@ -10,6 +10,15 @@ #include <sys/socket.h> #include <sys/types.h> +#if defined(__linux__) +#include <linux/in6.h> +#ifndef IPV6_FLOWLABEL +#define IPV6_FLOWINFO 11 +#define IPV6_FLOWINFO_SEND 33 +#define IPV6_FLOWINFO_FLOWLABEL 0x000fffff +#endif +#endif + #include "absl/base/optimization.h" #include "quiche/quic/core/io/socket.h" #include "quiche/quic/core/quic_udp_socket.h" @@ -145,6 +154,17 @@ } } +#if defined(__linux__) + if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_FLOWINFO) { + if (packet_info_interested.IsSet(QuicUdpPacketInfoBit::V6_FLOW_LABEL)) { + uint32_t flow_label = 0; + memcpy(&flow_label, CMSG_DATA(cmsg), sizeof(flow_label)); + flow_label = IPV6_FLOWINFO_FLOWLABEL & ntohl(flow_label); + packet_info->SetFlowLabel(flow_label); + } + } +#endif + if (packet_info_interested.IsSet( QuicUdpPacketInfoBit::GOOGLE_PACKET_HEADER)) { BufferSpan google_packet_headers; @@ -223,6 +243,23 @@ << "IPv6 socket"; return false; } +#if defined(__linux__) + if (GetQuicReloadableFlag(quic_support_flow_label)) { + if (setsockopt(fd, SOL_IPV6, IPV6_FLOWINFO, &set, sizeof(set)) != 0) { + QUIC_LOG_FIRST_N(ERROR, 100) + << "Failed to request to receive flow label on " + << "IPv6 socket"; + return false; + } + if (setsockopt(fd, SOL_IPV6, IPV6_FLOWINFO_SEND, &set, sizeof(set)) != + 0) { + QUIC_LOG_FIRST_N(ERROR, 100) + << "Failed to request to send flow label on " + << "IPv6 socket"; + return false; + } + } +#endif if (!EnableReceiveSelfIpAddressForV6(fd)) { QUIC_LOG_FIRST_N(ERROR, 100) << "Failed to enable receiving of self v6 ip"; @@ -488,6 +525,20 @@ cmsghdr* cmsg = nullptr; + // Set IPv6 flow label. +#if defined(__linux__) + if (packet_info.HasValue(QuicUdpPacketInfoBit::V6_FLOW_LABEL) && + packet_info.flow_label() != 0) { + uint32_t flowlabel = packet_info.flow_label(); + if (!NextCmsg(&hdr, control_buffer, sizeof(control_buffer), SOL_IPV6, + IPV6_FLOWINFO, sizeof(flowlabel), &cmsg)) { + QUIC_LOG_FIRST_N(ERROR, 100) << "Not enough buffer to set flow label."; + return WriteResult(WRITE_STATUS_ERROR, EINVAL); + } + *reinterpret_cast<uint32_t*>(CMSG_DATA(cmsg)) = + htonl(flowlabel & IPV6_FLOWINFO_FLOWLABEL); + } +#endif // Set self IP. if (packet_info.HasValue(QuicUdpPacketInfoBit::V4_SELF_IP) && packet_info.self_v4_ip().IsInitialized()) {
diff --git a/quiche/quic/core/quic_udp_socket_test.cc b/quiche/quic/core/quic_udp_socket_test.cc new file mode 100644 index 0000000..3d46f87 --- /dev/null +++ b/quiche/quic/core/quic_udp_socket_test.cc
@@ -0,0 +1,136 @@ +// Copyright 2024 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 "quiche/quic/core/quic_udp_socket.h" + +#include <netinet/in.h> +#include <stdint.h> + +#include <cstddef> +#include <sstream> +#include <string> +#include <vector> + +#include "quiche/quic/platform/api/quic_test.h" + +namespace quic { +namespace test { +namespace { + +const int kReceiveBufferSize = 16000; +const int kSendBufferSize = 16000; + +class QuicUdpSocketTest : public QuicTest { + protected: + ABSL_CACHELINE_ALIGNED char packet_buffer_[20]; + ABSL_CACHELINE_ALIGNED char control_buffer_[512]; +}; + +TEST_F(QuicUdpSocketTest, Basic) { + SetQuicReloadableFlag(quic_support_flow_label, true); + const QuicSocketAddress any_address(quiche::QuicheIpAddress::Any6(), 0); + QuicUdpSocketApi socket_api; + + SocketFd server_socket = + socket_api.Create(AF_INET6, kSendBufferSize, kReceiveBufferSize); + ASSERT_NE(kQuicInvalidSocketFd, server_socket); + ASSERT_TRUE(socket_api.Bind(server_socket, any_address)); + QuicSocketAddress server_address; + ASSERT_EQ(0, server_address.FromSocket(server_socket)); + + SocketFd client_socket = + socket_api.Create(AF_INET6, kSendBufferSize, kReceiveBufferSize); + ASSERT_NE(kQuicInvalidSocketFd, client_socket); + ASSERT_TRUE(socket_api.Bind(client_socket, any_address)); + QuicSocketAddress client_address; + ASSERT_EQ(0, client_address.FromSocket(client_socket)); + + QuicUdpPacketInfo packet_info; + packet_info.SetPeerAddress(server_address); + + WriteResult write_result; + const absl::string_view client_data = "acd"; + write_result = socket_api.WritePacket(client_socket, client_data.data(), + client_data.length(), packet_info); + ASSERT_EQ(WRITE_STATUS_OK, write_result.status); + + QuicUdpPacketInfoBitMask packet_info_interested; + QuicUdpSocketApi::ReadPacketResult read_result; + read_result.packet_buffer = {&packet_buffer_[0], sizeof(packet_buffer_)}; + read_result.control_buffer = {&control_buffer_[0], sizeof(control_buffer_)}; + + socket_api.ReadPacket(server_socket, packet_info_interested, &read_result); + ASSERT_TRUE(read_result.ok); + ASSERT_EQ(client_data, + absl::string_view(read_result.packet_buffer.buffer, + read_result.packet_buffer.buffer_len)); + + const absl::string_view server_data = "acd"; + packet_info.SetPeerAddress(client_address); + write_result = socket_api.WritePacket(server_socket, server_data.data(), + server_data.length(), packet_info); + ASSERT_EQ(WRITE_STATUS_OK, write_result.status); + + read_result.Reset(sizeof(packet_buffer_)); + socket_api.ReadPacket(client_socket, packet_info_interested, &read_result); + ASSERT_TRUE(read_result.ok); + ASSERT_EQ(server_data, + absl::string_view(read_result.packet_buffer.buffer, + read_result.packet_buffer.buffer_len)); +} + +TEST_F(QuicUdpSocketTest, FlowLabel) { + SetQuicReloadableFlag(quic_support_flow_label, true); + const QuicSocketAddress any_address(quiche::QuicheIpAddress::Any6(), 0); + QuicUdpSocketApi socket_api; + + SocketFd server_socket = + socket_api.Create(AF_INET6, kSendBufferSize, kReceiveBufferSize); + ASSERT_NE(kQuicInvalidSocketFd, server_socket); + ASSERT_TRUE(socket_api.Bind(server_socket, any_address)); + QuicSocketAddress server_address; + ASSERT_EQ(0, server_address.FromSocket(server_socket)); + + SocketFd client_socket = + socket_api.Create(AF_INET6, kSendBufferSize, kReceiveBufferSize); + ASSERT_NE(kQuicInvalidSocketFd, client_socket); + ASSERT_TRUE(socket_api.Bind(client_socket, any_address)); + QuicSocketAddress client_address; + ASSERT_EQ(0, client_address.FromSocket(client_socket)); + + const absl::string_view data = "a"; + const uint32_t client_flow_label = 1; + QuicUdpPacketInfo packet_info; + packet_info.SetFlowLabel(client_flow_label); + packet_info.SetPeerAddress(server_address); + + WriteResult write_result; + write_result = socket_api.WritePacket(client_socket, data.data(), + data.length(), packet_info); + ASSERT_EQ(WRITE_STATUS_OK, write_result.status); + + QuicUdpPacketInfoBitMask packet_info_interested( + {quic::QuicUdpPacketInfoBit::V6_FLOW_LABEL}); + QuicUdpSocketApi::ReadPacketResult read_result; + read_result.packet_buffer = {&packet_buffer_[0], sizeof(packet_buffer_)}; + read_result.control_buffer = {&control_buffer_[0], sizeof(control_buffer_)}; + + socket_api.ReadPacket(server_socket, packet_info_interested, &read_result); + EXPECT_EQ(client_flow_label, read_result.packet_info.flow_label()); + + const uint32_t server_flow_label = 3; + packet_info.SetPeerAddress(client_address); + packet_info.SetFlowLabel(server_flow_label); + write_result = socket_api.WritePacket(server_socket, data.data(), + data.length(), packet_info); + ASSERT_EQ(WRITE_STATUS_OK, write_result.status); + + read_result.Reset(sizeof(packet_buffer_)); + socket_api.ReadPacket(client_socket, packet_info_interested, &read_result); + EXPECT_EQ(server_flow_label, read_result.packet_info.flow_label()); +} + +} // namespace +} // namespace test +} // namespace quic