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;