ECN support for several QuicPacketWriter implementations. Protected by quic_send_ect1. PiperOrigin-RevId: 537933737
diff --git a/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc b/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc index 512e51f..77218d4 100644 --- a/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc +++ b/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc
@@ -65,7 +65,8 @@ // [2] It won't cause this batch to exceed kMaxGsoPacketSize. // [3] Already buffered writes all have the same length. // [4] Length of already buffered writes must >= length of the new write. - // [5] The new packet can be released without delay, or it has the same + // [5] The ECN markings match. + // [6] The new packet can be released without delay, or it has the same // release time as buffered writes. const BufferedWrite& first = buffered_writes().front(); const BufferedWrite& last = buffered_writes().back(); @@ -81,7 +82,8 @@ batch_buffer().SizeInUse() + buf_len <= kMaxGsoPacketSize && // [2] first.buf_len == last.buf_len && // [3] first.buf_len >= buf_len && // [4] - (can_burst || first.release_time == release_time); // [5] + first.params.ecn_codepoint == params.ecn_codepoint && // [5] + (can_burst || first.release_time == release_time); // [6] // A flush is required if any of the following is true: // [a] The new write can't be batched. @@ -138,7 +140,8 @@ // static void QuicGsoBatchWriter::BuildCmsg(QuicMsgHdr* hdr, const QuicIpAddress& self_address, - uint16_t gso_size, uint64_t release_time) { + uint16_t gso_size, uint64_t release_time, + QuicEcnCodepoint ecn_codepoint) { hdr->SetIpInNextCmsg(self_address); if (gso_size > 0) { *hdr->GetNextCmsgData<uint16_t>(SOL_UDP, UDP_SEGMENT) = gso_size; @@ -146,6 +149,16 @@ if (release_time != 0) { *hdr->GetNextCmsgData<uint64_t>(SOL_SOCKET, SO_TXTIME) = release_time; } + if (ecn_codepoint != ECN_NOT_ECT && GetQuicReloadableFlag(quic_send_ect1)) { + QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 8, 8); + if (self_address.IsIPv4()) { + *hdr->GetNextCmsgData<int>(IPPROTO_IP, IP_TOS) = + static_cast<int>(ecn_codepoint); + } else { + *hdr->GetNextCmsgData<int>(IPPROTO_IPV6, IPV6_TCLASS) = + static_cast<int>(ecn_codepoint); + } + } } QuicGsoBatchWriter::FlushImplResult QuicGsoBatchWriter::FlushImpl() {
diff --git a/quiche/quic/core/batch_writer/quic_gso_batch_writer.h b/quiche/quic/core/batch_writer/quic_gso_batch_writer.h index 96ccc76..492e446 100644 --- a/quiche/quic/core/batch_writer/quic_gso_batch_writer.h +++ b/quiche/quic/core/batch_writer/quic_gso_batch_writer.h
@@ -21,6 +21,10 @@ bool SupportsReleaseTime() const final { return supports_release_time_; } + bool SupportsEcn() const override { + return GetQuicReloadableFlag(quic_send_ect1); + } + CanBatchResult CanBatch(const char* buffer, size_t buf_len, const QuicIpAddress& self_address, const QuicSocketAddress& peer_address, @@ -52,10 +56,11 @@ return gso_size <= 2 ? 16 : 45; } - static const int kCmsgSpace = - kCmsgSpaceForIp + kCmsgSpaceForSegmentSize + kCmsgSpaceForTxTime; + static const int kCmsgSpace = kCmsgSpaceForIp + kCmsgSpaceForSegmentSize + + kCmsgSpaceForTxTime + kCmsgSpaceForTOS; static void BuildCmsg(QuicMsgHdr* hdr, const QuicIpAddress& self_address, - uint16_t gso_size, uint64_t release_time); + uint16_t gso_size, uint64_t release_time, + QuicEcnCodepoint ecn_codepoint); template <size_t CmsgSpace, typename CmsgBuilderT> FlushImplResult InternalFlushImpl(CmsgBuilderT cmsg_builder) { @@ -73,7 +78,8 @@ sizeof(cbuf)); uint16_t gso_size = buffered_writes().size() > 1 ? first.buf_len : 0; - cmsg_builder(&hdr, first.self_address, gso_size, first.release_time); + cmsg_builder(&hdr, first.self_address, gso_size, first.release_time, + first.params.ecn_codepoint); write_result = QuicLinuxSocketUtils::WritePacket(fd(), hdr); QUIC_DVLOG(1) << "Write GSO packet result: " << write_result
diff --git a/quiche/quic/core/batch_writer/quic_gso_batch_writer_test.cc b/quiche/quic/core/batch_writer/quic_gso_batch_writer_test.cc index c4c0406..b21c5e7 100644 --- a/quiche/quic/core/batch_writer/quic_gso_batch_writer_test.cc +++ b/quiche/quic/core/batch_writer/quic_gso_batch_writer_test.cc
@@ -4,6 +4,8 @@ #include "quiche/quic/core/batch_writer/quic_gso_batch_writer.h" +#include <sys/socket.h> + #include <cstdint> #include <limits> #include <memory> @@ -61,12 +63,6 @@ uint64_t forced_release_time_ms_ = 1; }; -struct QUIC_EXPORT_PRIVATE TestPerPacketOptions : public PerPacketOptions { - std::unique_ptr<quic::PerPacketOptions> Clone() const override { - return std::make_unique<TestPerPacketOptions>(*this); - } -}; - // TestBufferedWrite is a copy-constructible BufferedWrite. struct QUIC_EXPORT_PRIVATE TestBufferedWrite : public BufferedWrite { using BufferedWrite::BufferedWrite; @@ -453,6 +449,97 @@ EXPECT_EQ(result.send_time_offset, QuicTime::Delta::Zero()); } +TEST_F(QuicGsoBatchWriterTest, EcnCodepoint) { + const WriteResult write_buffered(WRITE_STATUS_OK, 0); + + auto writer = TestQuicGsoBatchWriter::NewInstanceWithReleaseTimeSupport(); + + QuicPacketWriterParams params; + EXPECT_TRUE(params.release_time_delay.IsZero()); + EXPECT_FALSE(params.allow_burst); + params.ecn_codepoint = ECN_ECT0; + + // The 1st packet has no delay. + WriteResult result = WritePacketWithParams(writer.get(), params); + ASSERT_EQ(write_buffered, result); + EXPECT_EQ(MillisToNanos(1), writer->buffered_writes().back().release_time); + EXPECT_EQ(result.send_time_offset, QuicTime::Delta::Zero()); + + // The 2nd packet should be buffered. + params.allow_burst = true; + result = WritePacketWithParams(writer.get(), params); + ASSERT_EQ(write_buffered, result); + + // The 3rd packet changes the ECN codepoint. + // The first 2 packets are flushed due to different codepoint. + params.ecn_codepoint = ECN_ECT1; + EXPECT_CALL(mock_syscalls_, Sendmsg(_, _, _)) + .WillOnce(Invoke([](int /*sockfd*/, const msghdr* msg, int /*flags*/) { + const int kEct0 = 0x01; + EXPECT_EQ(2700u, PacketLength(msg)); + msghdr mutable_msg; + memcpy(&mutable_msg, msg, sizeof(*msg)); + for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&mutable_msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&mutable_msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_TOS) { + EXPECT_EQ(*reinterpret_cast<int*> CMSG_DATA(cmsg), kEct0); + break; + } + } + errno = 0; + return 0; + })); + result = WritePacketWithParams(writer.get(), params); + ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 2700), result); +} + +TEST_F(QuicGsoBatchWriterTest, EcnCodepointIPv6) { + const WriteResult write_buffered(WRITE_STATUS_OK, 0); + + self_address_ = QuicIpAddress::Any6(); + peer_address_ = QuicSocketAddress(QuicIpAddress::Any6(), 443); + auto writer = TestQuicGsoBatchWriter::NewInstanceWithReleaseTimeSupport(); + + QuicPacketWriterParams params; + EXPECT_TRUE(params.release_time_delay.IsZero()); + EXPECT_FALSE(params.allow_burst); + params.ecn_codepoint = ECN_ECT0; + + // The 1st packet has no delay. + WriteResult result = WritePacketWithParams(writer.get(), params); + ASSERT_EQ(write_buffered, result); + EXPECT_EQ(MillisToNanos(1), writer->buffered_writes().back().release_time); + EXPECT_EQ(result.send_time_offset, QuicTime::Delta::Zero()); + + // The 2nd packet should be buffered. + params.allow_burst = true; + result = WritePacketWithParams(writer.get(), params); + ASSERT_EQ(write_buffered, result); + + // The 3rd packet changes the ECN codepoint. + // The first 2 packets are flushed due to different codepoint. + params.ecn_codepoint = ECN_ECT1; + EXPECT_CALL(mock_syscalls_, Sendmsg(_, _, _)) + .WillOnce(Invoke([](int /*sockfd*/, const msghdr* msg, int /*flags*/) { + const int kEct0 = 0x01; + EXPECT_EQ(2700u, PacketLength(msg)); + msghdr mutable_msg; + memcpy(&mutable_msg, msg, sizeof(*msg)); + for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&mutable_msg); cmsg != NULL; + cmsg = CMSG_NXTHDR(&mutable_msg, cmsg)) { + if (cmsg->cmsg_level == IPPROTO_IPV6 && + cmsg->cmsg_type == IPV6_TCLASS) { + EXPECT_EQ(*reinterpret_cast<int*> CMSG_DATA(cmsg), kEct0); + break; + } + } + errno = 0; + return 0; + })); + result = WritePacketWithParams(writer.get(), params); + ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 2700), result); +} + } // namespace } // namespace test } // namespace quic
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc index 8737453..f78f11a 100644 --- a/quiche/quic/core/quic_connection.cc +++ b/quiche/quic/core/quic_connection.cc
@@ -3915,7 +3915,7 @@ // Only packets on the default path are in-flight. if (!default_path_.ecn_marked_packet_acked) { QUIC_DVLOG(1) << ENDPOINT << "First ECT packet acked on active path."; - QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 2, 3); + QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 2, 8); default_path_.ecn_marked_packet_acked = true; } } @@ -7354,7 +7354,7 @@ if (!GetQuicReloadableFlag(quic_send_ect1)) { return false; } - QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 3, 3); + QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 3, 8); if (disable_ecn_codepoint_validation_ || ecn_codepoint == ECN_NOT_ECT) { packet_writer_params_.ecn_codepoint = ecn_codepoint; return true;
diff --git a/quiche/quic/core/quic_linux_socket_utils.h b/quiche/quic/core/quic_linux_socket_utils.h index 41be945..36c2dd7 100644 --- a/quiche/quic/core/quic_linux_socket_utils.h +++ b/quiche/quic/core/quic_linux_socket_utils.h
@@ -57,6 +57,8 @@ const int kCmsgSpaceForTTL = CMSG_SPACE(sizeof(int)); +const int kCmsgSpaceForTOS = CMSG_SPACE(sizeof(int)); + // QuicMsgHdr is used to build msghdr objects that can be used send packets via // ::sendmsg. //
diff --git a/quiche/quic/core/quic_sent_packet_manager.cc b/quiche/quic/core/quic_sent_packet_manager.cc index 2f030ec..0357120 100644 --- a/quiche/quic/core/quic_sent_packet_manager.cc +++ b/quiche/quic/core/quic_sent_packet_manager.cc
@@ -1421,7 +1421,7 @@ // Validate ECN feedback. absl::optional<QuicEcnCounts> valid_ecn_counts; if (GetQuicReloadableFlag(quic_send_ect1)) { - QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 1, 3); + QUIC_RELOADABLE_FLAG_COUNT_N(quic_send_ect1, 1, 8); if (IsEcnFeedbackValid(acked_packet_number_space, ecn_counts, newly_acked_ect0, newly_acked_ect1)) { valid_ecn_counts = ecn_counts;