Refactor PerPacketOptions for QuicWriter. From the bug:

per_packet_options_ was originally designed as a vehicle for platforms to get specific instructions to their corresponding QuicWriters via a QuicConnection that didn't understand the semantics of those instructions. Therefore, it is passed to QuicConnection as a piece of memory of which the platform retains ownership.

Over time, other instructions specific to QuicConnection have appeared in this struct, as it provides a vehicle to send information to QuicWriter without changing APIs. But this violates the intent of the struct, makes Quiche functions reliant on a piece of memory owned by the platform, and is bug prone (e.g., if the platform provides the same piece of memory for all connections in a dispatcher, the outcome will be very bad).

PiperOrigin-RevId: 534577370
diff --git a/quiche/quic/core/batch_writer/quic_batch_writer_base.cc b/quiche/quic/core/batch_writer/quic_batch_writer_base.cc
index 4a94c71..fae805f 100644
--- a/quiche/quic/core/batch_writer/quic_batch_writer_base.cc
+++ b/quiche/quic/core/batch_writer/quic_batch_writer_base.cc
@@ -18,9 +18,10 @@
 
 WriteResult QuicBatchWriterBase::WritePacket(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
-    const QuicSocketAddress& peer_address, PerPacketOptions* options) {
-  const WriteResult result =
-      InternalWritePacket(buffer, buf_len, self_address, peer_address, options);
+    const QuicSocketAddress& peer_address, PerPacketOptions* options,
+    const QuicPacketWriterParams& params) {
+  const WriteResult result = InternalWritePacket(buffer, buf_len, self_address,
+                                                 peer_address, options, params);
 
   if (IsWriteBlockedStatus(result.status)) {
     write_blocked_ = true;
@@ -31,14 +32,15 @@
 
 WriteResult QuicBatchWriterBase::InternalWritePacket(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
-    const QuicSocketAddress& peer_address, PerPacketOptions* options) {
+    const QuicSocketAddress& peer_address, PerPacketOptions* options,
+    const QuicPacketWriterParams& params) {
   if (buf_len > kMaxOutgoingPacketSize) {
     return WriteResult(WRITE_STATUS_MSG_TOO_BIG, EMSGSIZE);
   }
 
   ReleaseTime release_time{0, QuicTime::Delta::Zero()};
   if (SupportsReleaseTime()) {
-    release_time = GetReleaseTime(options);
+    release_time = GetReleaseTime(params);
     if (release_time.release_time_offset >= QuicTime::Delta::Zero()) {
       QUIC_SERVER_HISTOGRAM_TIMES(
           "batch_writer_positive_release_time_offset",
@@ -55,7 +57,7 @@
   }
 
   const CanBatchResult can_batch_result =
-      CanBatch(buffer, buf_len, self_address, peer_address, options,
+      CanBatch(buffer, buf_len, self_address, peer_address, options, params,
                release_time.actual_release_time);
 
   bool buffered = false;
@@ -64,7 +66,7 @@
   if (can_batch_result.can_batch) {
     QuicBatchWriterBuffer::PushResult push_result =
         batch_buffer_->PushBufferedWrite(buffer, buf_len, self_address,
-                                         peer_address, options,
+                                         peer_address, options, params,
                                          release_time.actual_release_time);
     if (push_result.succeeded) {
       buffered = true;
@@ -111,7 +113,7 @@
   if (!buffered) {
     QuicBatchWriterBuffer::PushResult push_result =
         batch_buffer_->PushBufferedWrite(buffer, buf_len, self_address,
-                                         peer_address, options,
+                                         peer_address, options, params,
                                          release_time.actual_release_time);
     buffered = push_result.succeeded;
 
diff --git a/quiche/quic/core/batch_writer/quic_batch_writer_base.h b/quiche/quic/core/batch_writer/quic_batch_writer_base.h
index a5a1e38..a33b8e2 100644
--- a/quiche/quic/core/batch_writer/quic_batch_writer_base.h
+++ b/quiche/quic/core/batch_writer/quic_batch_writer_base.h
@@ -32,7 +32,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override;
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override;
 
   bool IsWriteBlocked() const final { return write_blocked_; }
 
@@ -78,7 +79,7 @@
     QuicTime::Delta release_time_offset = QuicTime::Delta::Zero();
   };
   virtual ReleaseTime GetReleaseTime(
-      const PerPacketOptions* /*options*/) const {
+      const QuicPacketWriterParams& /*params*/) const {
     QUICHE_DCHECK(false)
         << "Should not be called since release time is unsupported.";
     return ReleaseTime{0, QuicTime::Delta::Zero()};
@@ -101,6 +102,7 @@
                                   const QuicIpAddress& self_address,
                                   const QuicSocketAddress& peer_address,
                                   const PerPacketOptions* options,
+                                  const QuicPacketWriterParams& params,
                                   uint64_t release_time) const = 0;
 
   struct QUIC_EXPORT_PRIVATE FlushImplResult {
@@ -129,7 +131,8 @@
   WriteResult InternalWritePacket(const char* buffer, size_t buf_len,
                                   const QuicIpAddress& self_address,
                                   const QuicSocketAddress& peer_address,
-                                  PerPacketOptions* options);
+                                  PerPacketOptions* options,
+                                  const QuicPacketWriterParams& params);
 
   // Calls FlushImpl() and check its post condition.
   FlushImplResult CheckedFlush();
diff --git a/quiche/quic/core/batch_writer/quic_batch_writer_buffer.cc b/quiche/quic/core/batch_writer/quic_batch_writer_buffer.cc
index ac7ddd7..d283e2b 100644
--- a/quiche/quic/core/batch_writer/quic_batch_writer_buffer.cc
+++ b/quiche/quic/core/batch_writer/quic_batch_writer_buffer.cc
@@ -54,7 +54,7 @@
 QuicBatchWriterBuffer::PushResult QuicBatchWriterBuffer::PushBufferedWrite(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
     const QuicSocketAddress& peer_address, const PerPacketOptions* options,
-    uint64_t release_time) {
+    const QuicPacketWriterParams& params, uint64_t release_time) {
   QUICHE_DCHECK(Invariants());
   QUICHE_DCHECK_LE(buf_len, kMaxOutgoingPacketSize);
 
@@ -84,7 +84,7 @@
   }
   buffered_writes_.emplace_back(
       next_write_location, buf_len, self_address, peer_address,
-      options ? options->Clone() : std::unique_ptr<PerPacketOptions>(),
+      options ? options->Clone() : std::unique_ptr<PerPacketOptions>(), params,
       release_time);
 
   QUICHE_DCHECK(Invariants());
diff --git a/quiche/quic/core/batch_writer/quic_batch_writer_buffer.h b/quiche/quic/core/batch_writer/quic_batch_writer_buffer.h
index 2369401..62282a3 100644
--- a/quiche/quic/core/batch_writer/quic_batch_writer_buffer.h
+++ b/quiche/quic/core/batch_writer/quic_batch_writer_buffer.h
@@ -44,6 +44,7 @@
                                const QuicIpAddress& self_address,
                                const QuicSocketAddress& peer_address,
                                const PerPacketOptions* options,
+                               const QuicPacketWriterParams& params,
                                uint64_t release_time);
 
   void UndoLastPush();
diff --git a/quiche/quic/core/batch_writer/quic_batch_writer_buffer_test.cc b/quiche/quic/core/batch_writer/quic_batch_writer_buffer_test.cc
index e081a79..ffa88f0 100644
--- a/quiche/quic/core/batch_writer/quic_batch_writer_buffer_test.cc
+++ b/quiche/quic/core/batch_writer/quic_batch_writer_buffer_test.cc
@@ -52,7 +52,8 @@
   void CheckBufferedWriteContent(int buffered_write_index, char buffer_content,
                                  size_t buf_len, const QuicIpAddress& self_addr,
                                  const QuicSocketAddress& peer_addr,
-                                 const PerPacketOptions* options) {
+                                 const PerPacketOptions* /*options*/,
+                                 const QuicPacketWriterParams& params) {
     const BufferedWrite& buffered_write =
         batch_buffer_->buffered_writes()[buffered_write_index];
     EXPECT_EQ(buf_len, buffered_write.buf_len);
@@ -64,12 +65,8 @@
     }
     EXPECT_EQ(self_addr, buffered_write.self_address);
     EXPECT_EQ(peer_addr, buffered_write.peer_address);
-    if (options == nullptr) {
-      EXPECT_EQ(nullptr, buffered_write.options);
-    } else {
-      EXPECT_EQ(options->release_time_delay,
-                buffered_write.options->release_time_delay);
-    }
+    EXPECT_EQ(params.release_time_delay,
+              buffered_write.params.release_time_delay);
   }
 
  protected:
@@ -141,7 +138,8 @@
                    << ", batch_buffer=" << batch_buffer_->DebugString());
 
       auto push_result = batch_buffer_->PushBufferedWrite(
-          buffer, buf_len, self_addr_, peer_addr_, nullptr, release_time_);
+          buffer, buf_len, self_addr_, peer_addr_, nullptr,
+          QuicPacketWriterParams(), release_time_);
       if (!push_result.succeeded) {
         ++num_push_failures;
       }
@@ -160,49 +158,51 @@
 TEST_F(QuicBatchWriterBufferTest, MixedPushes) {
   // First, a in-place push.
   char* buffer = batch_buffer_->GetNextWriteLocation();
+  QuicPacketWriterParams params;
   auto push_result = batch_buffer_->PushBufferedWrite(
       FillPacketBuffer('A', buffer), kDefaultMaxPacketSize, self_addr_,
-      peer_addr_, nullptr, release_time_);
+      peer_addr_, nullptr, params, release_time_);
   EXPECT_TRUE(push_result.succeeded);
   EXPECT_FALSE(push_result.buffer_copied);
   CheckBufferedWriteContent(0, 'A', kDefaultMaxPacketSize, self_addr_,
-                            peer_addr_, nullptr);
+                            peer_addr_, nullptr, params);
 
   // Then a push with external buffer.
   push_result = batch_buffer_->PushBufferedWrite(
       FillPacketBuffer('B'), kDefaultMaxPacketSize, self_addr_, peer_addr_,
-      nullptr, release_time_);
+      nullptr, params, release_time_);
   EXPECT_TRUE(push_result.succeeded);
   EXPECT_TRUE(push_result.buffer_copied);
   CheckBufferedWriteContent(1, 'B', kDefaultMaxPacketSize, self_addr_,
-                            peer_addr_, nullptr);
+                            peer_addr_, nullptr, params);
 
   // Then another in-place push.
   buffer = batch_buffer_->GetNextWriteLocation();
   push_result = batch_buffer_->PushBufferedWrite(
       FillPacketBuffer('C', buffer), kDefaultMaxPacketSize, self_addr_,
-      peer_addr_, nullptr, release_time_);
+      peer_addr_, nullptr, params, release_time_);
   EXPECT_TRUE(push_result.succeeded);
   EXPECT_FALSE(push_result.buffer_copied);
   CheckBufferedWriteContent(2, 'C', kDefaultMaxPacketSize, self_addr_,
-                            peer_addr_, nullptr);
+                            peer_addr_, nullptr, params);
 
   // Then another push with external buffer.
   push_result = batch_buffer_->PushBufferedWrite(
       FillPacketBuffer('D'), kDefaultMaxPacketSize, self_addr_, peer_addr_,
-      nullptr, release_time_);
+      nullptr, params, release_time_);
   EXPECT_TRUE(push_result.succeeded);
   EXPECT_TRUE(push_result.buffer_copied);
   CheckBufferedWriteContent(3, 'D', kDefaultMaxPacketSize, self_addr_,
-                            peer_addr_, nullptr);
+                            peer_addr_, nullptr, params);
 }
 
 TEST_F(QuicBatchWriterBufferTest, PopAll) {
   const int kNumBufferedWrites = 10;
+  QuicPacketWriterParams params;
   for (int i = 0; i < kNumBufferedWrites; ++i) {
     EXPECT_TRUE(batch_buffer_
                     ->PushBufferedWrite(packet_buffer_, kDefaultMaxPacketSize,
-                                        self_addr_, peer_addr_, nullptr,
+                                        self_addr_, peer_addr_, nullptr, params,
                                         release_time_)
                     .succeeded);
   }
@@ -217,11 +217,12 @@
 
 TEST_F(QuicBatchWriterBufferTest, PopPartial) {
   const int kNumBufferedWrites = 10;
+  QuicPacketWriterParams params;
   for (int i = 0; i < kNumBufferedWrites; ++i) {
     EXPECT_TRUE(batch_buffer_
-                    ->PushBufferedWrite(FillPacketBuffer('A' + i),
-                                        kDefaultMaxPacketSize - i, self_addr_,
-                                        peer_addr_, nullptr, release_time_)
+                    ->PushBufferedWrite(
+                        FillPacketBuffer('A' + i), kDefaultMaxPacketSize - i,
+                        self_addr_, peer_addr_, nullptr, params, release_time_)
                     .succeeded);
   }
 
@@ -239,7 +240,7 @@
         kDefaultMaxPacketSize - kNumBufferedWrites + expect_size_after_pop;
     for (size_t j = 0; j < expect_size_after_pop; ++j) {
       CheckBufferedWriteContent(j, first_write_content + j, first_write_len - j,
-                                self_addr_, peer_addr_, nullptr);
+                                self_addr_, peer_addr_, nullptr, params);
     }
   }
 }
@@ -248,13 +249,14 @@
   // First, a in-place push.
   char* buffer = batch_buffer_->GetNextWriteLocation();
   const size_t first_packet_len = 2;
+  QuicPacketWriterParams params;
   auto push_result = batch_buffer_->PushBufferedWrite(
       FillPacketBuffer('A', buffer, first_packet_len), first_packet_len,
-      self_addr_, peer_addr_, nullptr, release_time_);
+      self_addr_, peer_addr_, nullptr, params, release_time_);
   EXPECT_TRUE(push_result.succeeded);
   EXPECT_FALSE(push_result.buffer_copied);
   CheckBufferedWriteContent(0, 'A', first_packet_len, self_addr_, peer_addr_,
-                            nullptr);
+                            nullptr, params);
 
   // Simulate the case where the writer wants to do another in-place push, but
   // can't do so because it can't be batched with the first buffer.
@@ -269,11 +271,11 @@
   // Now the second push.
   push_result = batch_buffer_->PushBufferedWrite(
       FillPacketBuffer('B', buffer, second_packet_len), second_packet_len,
-      self_addr_, peer_addr_, nullptr, release_time_);
+      self_addr_, peer_addr_, nullptr, params, release_time_);
   EXPECT_TRUE(push_result.succeeded);
   EXPECT_TRUE(push_result.buffer_copied);
   CheckBufferedWriteContent(0, 'B', second_packet_len, self_addr_, peer_addr_,
-                            nullptr);
+                            nullptr, params);
 }
 
 }  // namespace
diff --git a/quiche/quic/core/batch_writer/quic_batch_writer_test.h b/quiche/quic/core/batch_writer/quic_batch_writer_test.h
index ebbb917..d42867f 100644
--- a/quiche/quic/core/batch_writer/quic_batch_writer_test.h
+++ b/quiche/quic/core/batch_writer/quic_batch_writer_test.h
@@ -195,7 +195,7 @@
 
       result = GetWriter()->WritePacket(&packet_buffer_[0], this_packet_size,
                                         self_address_.host(), peer_address_,
-                                        nullptr);
+                                        nullptr, QuicPacketWriterParams());
 
       ASSERT_EQ(WRITE_STATUS_OK, result.status) << strerror(result.error_code);
       bytes_flushed += result.bytes_written;
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 b6d6200..512e51f 100644
--- a/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc
+++ b/quiche/quic/core/batch_writer/quic_gso_batch_writer.cc
@@ -49,8 +49,8 @@
 
 QuicGsoBatchWriter::CanBatchResult QuicGsoBatchWriter::CanBatch(
     const char* /*buffer*/, size_t buf_len, const QuicIpAddress& self_address,
-    const QuicSocketAddress& peer_address, const PerPacketOptions* options,
-    uint64_t release_time) const {
+    const QuicSocketAddress& peer_address, const PerPacketOptions* /*options*/,
+    const QuicPacketWriterParams& params, uint64_t release_time) const {
   // If there is nothing buffered already, this write will be included in this
   // batch.
   if (buffered_writes().empty()) {
@@ -70,9 +70,9 @@
   const BufferedWrite& first = buffered_writes().front();
   const BufferedWrite& last = buffered_writes().back();
   // Whether this packet can be sent without delay, regardless of release time.
-  const bool can_burst = !SupportsReleaseTime() || !options ||
-                         options->release_time_delay.IsZero() ||
-                         options->allow_burst;
+  const bool can_burst = !SupportsReleaseTime() ||
+                         params.release_time_delay.IsZero() ||
+                         params.allow_burst;
   size_t max_segments = MaxSegments(first.buf_len);
   bool can_batch =
       buffered_writes().size() < max_segments &&                    // [0]
@@ -96,18 +96,14 @@
 }
 
 QuicGsoBatchWriter::ReleaseTime QuicGsoBatchWriter::GetReleaseTime(
-    const PerPacketOptions* options) const {
+    const QuicPacketWriterParams& params) const {
   QUICHE_DCHECK(SupportsReleaseTime());
 
-  if (options == nullptr) {
-    return {0, QuicTime::Delta::Zero()};
-  }
-
   const uint64_t now = NowInNanosForReleaseTime();
   const uint64_t ideal_release_time =
-      now + options->release_time_delay.ToMicroseconds() * 1000;
+      now + params.release_time_delay.ToMicroseconds() * 1000;
 
-  if ((options->release_time_delay.IsZero() || options->allow_burst) &&
+  if ((params.release_time_delay.IsZero() || params.allow_burst) &&
       !buffered_writes().empty() &&
       // If release time of buffered packets is in the past, flush buffered
       // packets and buffer this packet at the ideal release time.
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 17657fc..96ccc76 100644
--- a/quiche/quic/core/batch_writer/quic_gso_batch_writer.h
+++ b/quiche/quic/core/batch_writer/quic_gso_batch_writer.h
@@ -25,6 +25,7 @@
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
                           const PerPacketOptions* options,
+                          const QuicPacketWriterParams& params,
                           uint64_t release_time) const override;
 
   FlushImplResult FlushImpl() override;
@@ -36,7 +37,8 @@
                      int fd, clockid_t clockid_for_release_time,
                      ReleaseTimeForceEnabler enabler);
 
-  ReleaseTime GetReleaseTime(const PerPacketOptions* options) const override;
+  ReleaseTime GetReleaseTime(
+      const QuicPacketWriterParams& params) const override;
 
   // Get the current time in nanos from |clockid_for_release_time_|.
   virtual uint64_t NowInNanosForReleaseTime() const;
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 efe4c71..c4c0406 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
@@ -75,7 +75,7 @@
                       other.peer_address,
                       other.options ? other.options->Clone()
                                     : std::unique_ptr<PerPacketOptions>(),
-                      other.release_time) {}
+                      QuicPacketWriterParams(), other.release_time) {}
 };
 
 // Pointed to by all instances of |BatchCriteriaTestData|. Content not used.
@@ -87,7 +87,7 @@
                         uint64_t release_time, bool can_batch, bool must_flush)
       : buffered_write(unused_packet_buffer, buf_len, self_address,
                        peer_address, std::unique_ptr<PerPacketOptions>(),
-                       release_time),
+                       QuicPacketWriterParams(), release_time),
         can_batch(can_batch),
         must_flush(must_flush) {}
 
@@ -203,13 +203,14 @@
  protected:
   WriteResult WritePacket(QuicGsoBatchWriter* writer, size_t packet_size) {
     return writer->WritePacket(&packet_buffer_[0], packet_size, self_address_,
-                               peer_address_, nullptr);
+                               peer_address_, nullptr,
+                               QuicPacketWriterParams());
   }
 
-  WriteResult WritePacketWithOptions(QuicGsoBatchWriter* writer,
-                                     PerPacketOptions* options) {
+  WriteResult WritePacketWithParams(QuicGsoBatchWriter* writer,
+                                    QuicPacketWriterParams& params) {
     return writer->WritePacket(&packet_buffer_[0], 1350, self_address_,
-                               peer_address_, options);
+                               peer_address_, nullptr, params);
   }
 
   QuicIpAddress self_address_ = QuicIpAddress::Any4();
@@ -239,13 +240,13 @@
     for (size_t j = 0; j < test_data_table.size(); ++j) {
       const BatchCriteriaTestData& test_data = test_data_table[j];
       SCOPED_TRACE(testing::Message() << "i=" << i << ", j=" << j);
-      TestPerPacketOptions options;
-      options.release_time_delay = QuicTime::Delta::FromMicroseconds(
+      QuicPacketWriterParams params;
+      params.release_time_delay = QuicTime::Delta::FromMicroseconds(
           test_data.buffered_write.release_time);
       TestQuicGsoBatchWriter::CanBatchResult result = writer->CanBatch(
           test_data.buffered_write.buffer, test_data.buffered_write.buf_len,
           test_data.buffered_write.self_address,
-          test_data.buffered_write.peer_address, &options,
+          test_data.buffered_write.peer_address, nullptr, params,
           test_data.buffered_write.release_time);
 
       ASSERT_EQ(test_data.can_batch, result.can_batch);
@@ -257,8 +258,8 @@
                             test_data.buffered_write.buffer,
                             test_data.buffered_write.buf_len,
                             test_data.buffered_write.self_address,
-                            test_data.buffered_write.peer_address, &options,
-                            test_data.buffered_write.release_time)
+                            test_data.buffered_write.peer_address, nullptr,
+                            params, test_data.buffered_write.release_time)
                         .succeeded);
       }
     }
@@ -379,32 +380,27 @@
   ASSERT_EQ(0u, writer.buffered_writes().size());
 }
 
-TEST_F(QuicGsoBatchWriterTest, ReleaseTimeNullOptions) {
-  auto writer = TestQuicGsoBatchWriter::NewInstanceWithReleaseTimeSupport();
-  EXPECT_EQ(0u, writer->GetReleaseTime(nullptr).actual_release_time);
-}
-
 TEST_F(QuicGsoBatchWriterTest, ReleaseTime) {
   const WriteResult write_buffered(WRITE_STATUS_OK, 0);
 
   auto writer = TestQuicGsoBatchWriter::NewInstanceWithReleaseTimeSupport();
 
-  TestPerPacketOptions options;
-  EXPECT_TRUE(options.release_time_delay.IsZero());
-  EXPECT_FALSE(options.allow_burst);
+  QuicPacketWriterParams params;
+  EXPECT_TRUE(params.release_time_delay.IsZero());
+  EXPECT_FALSE(params.allow_burst);
   EXPECT_EQ(MillisToNanos(1),
-            writer->GetReleaseTime(&options).actual_release_time);
+            writer->GetReleaseTime(params).actual_release_time);
 
   // The 1st packet has no delay.
-  WriteResult result = WritePacketWithOptions(writer.get(), &options);
+  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 has some delay, but allows burst.
-  options.release_time_delay = QuicTime::Delta::FromMilliseconds(3);
-  options.allow_burst = true;
-  result = WritePacketWithOptions(writer.get(), &options);
+  params.release_time_delay = QuicTime::Delta::FromMilliseconds(3);
+  params.allow_burst = true;
+  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::FromMilliseconds(-3));
@@ -417,16 +413,16 @@
         errno = 0;
         return 0;
       }));
-  options.release_time_delay = QuicTime::Delta::FromMilliseconds(5);
-  options.allow_burst = false;
-  result = WritePacketWithOptions(writer.get(), &options);
+  params.release_time_delay = QuicTime::Delta::FromMilliseconds(5);
+  params.allow_burst = false;
+  result = WritePacketWithParams(writer.get(), params);
   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 2700), result);
   EXPECT_EQ(MillisToNanos(6), writer->buffered_writes().back().release_time);
   EXPECT_EQ(result.send_time_offset, QuicTime::Delta::Zero());
 
   // The 4th packet has same delay, but allows burst.
-  options.allow_burst = true;
-  result = WritePacketWithOptions(writer.get(), &options);
+  params.allow_burst = true;
+  result = WritePacketWithParams(writer.get(), params);
   ASSERT_EQ(write_buffered, result);
   EXPECT_EQ(MillisToNanos(6), writer->buffered_writes().back().release_time);
   EXPECT_EQ(result.send_time_offset, QuicTime::Delta::Zero());
@@ -439,19 +435,19 @@
         errno = 0;
         return 0;
       }));
-  options.allow_burst = true;
+  params.allow_burst = true;
   EXPECT_EQ(MillisToNanos(6),
-            writer->GetReleaseTime(&options).actual_release_time);
+            writer->GetReleaseTime(params).actual_release_time);
   ASSERT_EQ(WriteResult(WRITE_STATUS_OK, 3000),
             writer->WritePacket(&packet_buffer_[0], 300, self_address_,
-                                peer_address_, &options));
+                                peer_address_, nullptr, params));
   EXPECT_TRUE(writer->buffered_writes().empty());
 
   // Pretend 1ms has elapsed and the 6th packet has 1ms less delay. In other
   // words, the release time should still be the same as packets 3-5.
   writer->ForceReleaseTimeMs(2);
-  options.release_time_delay = QuicTime::Delta::FromMilliseconds(4);
-  result = WritePacketWithOptions(writer.get(), &options);
+  params.release_time_delay = QuicTime::Delta::FromMilliseconds(4);
+  result = WritePacketWithParams(writer.get(), params);
   ASSERT_EQ(write_buffered, result);
   EXPECT_EQ(MillisToNanos(6), writer->buffered_writes().back().release_time);
   EXPECT_EQ(result.send_time_offset, QuicTime::Delta::Zero());
diff --git a/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.cc b/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.cc
index 8568e26..0c724b2 100644
--- a/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.cc
+++ b/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.cc
@@ -14,7 +14,8 @@
     const char* /*buffer*/, size_t /*buf_len*/,
     const QuicIpAddress& /*self_address*/,
     const QuicSocketAddress& /*peer_address*/,
-    const PerPacketOptions* /*options*/, uint64_t /*release_time*/) const {
+    const PerPacketOptions* /*options*/,
+    const QuicPacketWriterParams& /*params*/, uint64_t /*release_time*/) const {
   return CanBatchResult(/*can_batch=*/true, /*must_flush=*/false);
 }
 
diff --git a/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.h b/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.h
index 04a1b28..9dda5dd 100644
--- a/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.h
+++ b/quiche/quic/core/batch_writer/quic_sendmmsg_batch_writer.h
@@ -19,6 +19,7 @@
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
                           const PerPacketOptions* options,
+                          const QuicPacketWriterParams& params,
                           uint64_t release_time) const override;
 
   FlushImplResult FlushImpl() override;
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 44e3abe..d4e442f 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -31,6 +31,7 @@
 #include "quiche/quic/core/quic_error_codes.h"
 #include "quiche/quic/core/quic_framer.h"
 #include "quiche/quic/core/quic_packet_creator.h"
+#include "quiche/quic/core/quic_packet_writer.h"
 #include "quiche/quic/core/quic_packet_writer_wrapper.h"
 #include "quiche/quic/core/quic_packets.h"
 #include "quiche/quic/core/quic_session.h"
@@ -947,6 +948,7 @@
   std::vector<std::string> received_webtransport_unidirectional_streams_;
   bool use_preferred_address_ = false;
   QuicSocketAddress server_preferred_address_;
+  QuicPacketWriterParams packet_writer_params_;
 };
 
 // Run all end to end tests with all supported versions.
@@ -3206,15 +3208,17 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override {
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override {
     if (self_address_to_overwrite_.IsInitialized()) {
       // Send the same packet on the overwriting address before sending on the
       // actual self address.
-      QuicPacketWriterWrapper::WritePacket(
-          buffer, buf_len, self_address_to_overwrite_, peer_address, options);
+      QuicPacketWriterWrapper::WritePacket(buffer, buf_len,
+                                           self_address_to_overwrite_,
+                                           peer_address, options, params);
     }
     return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address,
-                                                peer_address, options);
+                                                peer_address, options, params);
   }
 
   void set_self_address_to_overwrite(const QuicIpAddress& self_address) {
@@ -3996,7 +4000,8 @@
   server_thread_->Pause();
   auto client_address = client_connection->self_address();
   server_writer_->WritePacket(packet->data(), packet->length(),
-                              server_address_.host(), client_address, nullptr);
+                              server_address_.host(), client_address, nullptr,
+                              packet_writer_params_);
   server_thread_->Resume();
 
   // The request should fail.
@@ -4038,7 +4043,8 @@
   server_thread_->Pause();
   auto client_address = client_connection->self_address();
   server_writer_->WritePacket(packet->data(), packet->length(),
-                              server_address_.host(), client_address, nullptr);
+                              server_address_.host(), client_address, nullptr,
+                              packet_writer_params_);
   server_thread_->Resume();
 
   // The request should fail. IETF stateless reset does not include connection
@@ -4087,7 +4093,7 @@
   client_writer_->WritePacket(
       packet->data(), packet->length(),
       client_->client()->network_helper()->GetLatestClientAddress().host(),
-      server_address_, nullptr);
+      server_address_, nullptr, packet_writer_params_);
 
   // The connection should be unaffected.
   SendSynchronousFooRequestAndCheckResponse();
@@ -4119,7 +4125,8 @@
   server_thread_->Pause();
   server_writer_->WritePacket(
       packet->data(), packet->length(), server_address_.host(),
-      client_->client()->network_helper()->GetLatestClientAddress(), nullptr);
+      client_->client()->network_helper()->GetLatestClientAddress(), nullptr,
+      packet_writer_params_);
   server_thread_->Resume();
 
   // The connection should be unaffected.
@@ -4149,10 +4156,11 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          quic::PerPacketOptions* options) override {
+                          quic::PerPacketOptions* options,
+                          const quic::QuicPacketWriterParams& params) override {
     if (!intercept_enabled_) {
       return PacketDroppingTestWriter::WritePacket(
-          buffer, buf_len, self_address, peer_address, options);
+          buffer, buf_len, self_address, peer_address, options, params);
     }
     PacketHeaderFormat format;
     QuicLongHeaderType long_packet_type;
@@ -4177,16 +4185,19 @@
       intercept_enabled_ = false;
       server_thread_->Resume();
       // Pass the client-sent packet through.
-      return WritePacket(buffer, buf_len, self_address, peer_address, options);
+      return WritePacket(buffer, buf_len, self_address, peer_address, options,
+                         params);
     }
     // Send a version negotiation packet.
     std::unique_ptr<QuicEncryptedPacket> packet(
         QuicFramer::BuildVersionNegotiationPacket(
             destination_connection_id, source_connection_id, /*ietf_quic=*/true,
             has_length_prefix, supported_versions_));
+    QuicPacketWriterParams default_params;
     server_writer_->WritePacket(
         packet->data(), packet->length(), peer_address.host(),
-        client_->client()->network_helper()->GetLatestClientAddress(), nullptr);
+        client_->client()->network_helper()->GetLatestClientAddress(), nullptr,
+        default_params);
     // Drop the client-sent packet but pretend it was sent.
     return WriteResult(WRITE_STATUS_OK, buf_len);
   }
@@ -4248,7 +4259,7 @@
   client_writer_->WritePacket(
       &packet[0], sizeof(packet),
       client_->client()->network_helper()->GetLatestClientAddress().host(),
-      server_address_, nullptr);
+      server_address_, nullptr, packet_writer_params_);
   EXPECT_TRUE(server_thread_->WaitUntil(
       [&] {
         return QuicDispatcherPeer::GetAndClearLastError(
@@ -4295,7 +4306,7 @@
   client_writer_->WritePacket(
       reinterpret_cast<const char*>(packet), sizeof(packet),
       client_->client()->network_helper()->GetLatestClientAddress().host(),
-      server_address_, nullptr);
+      server_address_, nullptr, packet_writer_params_);
 
   EXPECT_TRUE(server_thread_->WaitUntil(
       [&] {
@@ -4331,7 +4342,7 @@
   client_writer_->WritePacket(
       damaged_packet.data(), damaged_packet.length(),
       client_->client()->network_helper()->GetLatestClientAddress().host(),
-      server_address_, nullptr);
+      server_address_, nullptr, packet_writer_params_);
   // Give the server time to process the packet.
   absl::SleepFor(absl::Seconds(1));
   // This error is sent to the connection's OnError (which ignores it), so the
@@ -5261,10 +5272,11 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override {
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override {
     if (!hold_next_packet_) {
-      return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address,
-                                                  peer_address, options);
+      return QuicPacketWriterWrapper::WritePacket(
+          buffer, buf_len, self_address, peer_address, options, params);
     }
     QUIC_DLOG(INFO) << "Packet is held by the writer";
     packet_content_ = std::string(buffer, buf_len);
@@ -5286,7 +5298,7 @@
     ASSERT_EQ(WRITE_STATUS_OK,
               QuicPacketWriterWrapper::WritePacket(
                   packet_content_.data(), packet_content_.length(),
-                  self_address_, peer_address_, options_.release())
+                  self_address_, peer_address_, options_.release(), params_)
                   .status);
     packet_content_.clear();
   }
@@ -5297,6 +5309,7 @@
   QuicIpAddress self_address_;
   QuicSocketAddress peer_address_;
   std::unique_ptr<PerPacketOptions> options_;
+  QuicPacketWriterParams params_;
 };
 
 TEST_P(EndToEndTest, ClientValidateNewNetwork) {
@@ -5849,9 +5862,10 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          quic::PerPacketOptions* options) override {
+                          quic::PerPacketOptions* options,
+                          const quic::QuicPacketWriterParams& params) override {
     const WriteResult result = QuicPacketWriterWrapper::WritePacket(
-        buffer, buf_len, self_address, peer_address, options);
+        buffer, buf_len, self_address, peer_address, options, params);
     const uint8_t type_byte = buffer[0];
     if (!error_returned_ && (type_byte & FLAGS_LONG_HEADER) &&
         TypeByteIsServerHello(type_byte)) {
@@ -5924,7 +5938,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          quic::PerPacketOptions* options) override {
+                          quic::PerPacketOptions* options,
+                          const quic::QuicPacketWriterParams& params) override {
     const uint8_t type_byte = buffer[0];
 
     if (type_byte & FLAGS_LONG_HEADER) {
@@ -5939,7 +5954,7 @@
       return WriteResult(WRITE_STATUS_ERROR, *MessageTooBigErrorCode());
     }
     return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address,
-                                                peer_address, options);
+                                                peer_address, options, params);
   }
 
  private:
@@ -6076,14 +6091,15 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override {
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override {
     if (num_packets_to_copy_ > 0) {
       num_packets_to_copy_--;
       packets_.push_back(
           QuicEncryptedPacket(buffer, buf_len, /*owns_buffer=*/false).Clone());
     }
     return PacketDroppingTestWriter::WritePacket(buffer, buf_len, self_address,
-                                                 peer_address, options);
+                                                 peer_address, options, params);
   }
 
   std::vector<std::unique_ptr<QuicEncryptedPacket>>& packets() {
@@ -7097,9 +7113,7 @@
   EXPECT_EQ(ecn->ect0, 0);
   EXPECT_EQ(ecn->ect1, 0);
   EXPECT_EQ(ecn->ce, 0);
-  TestPerPacketOptions options;
-  client_connection->set_per_packet_options(&options);
-  options.ecn_codepoint = ECN_NOT_ECT;
+  client_connection->set_ecn_codepoint(ECN_NOT_ECT);
   client_->SendSynchronousRequest("/foo");
   EXPECT_EQ(ecn->ect0, 0);
   EXPECT_EQ(ecn->ect1, 0);
@@ -7119,9 +7133,7 @@
   EXPECT_EQ(ecn->ect0, 0);
   EXPECT_EQ(ecn->ect1, 0);
   EXPECT_EQ(ecn->ce, 0);
-  TestPerPacketOptions options;
-  client_connection->set_per_packet_options(&options);
-  options.ecn_codepoint = ECN_ECT0;
+  client_connection->set_ecn_codepoint(ECN_ECT0);
   client_->SendSynchronousRequest("/foo");
   if (!GetQuicRestartFlag(quic_receive_ecn) ||
       !GetQuicRestartFlag(quic_quiche_ecn_sockets) ||
@@ -7147,9 +7159,7 @@
   EXPECT_EQ(ecn->ect0, 0);
   EXPECT_EQ(ecn->ect1, 0);
   EXPECT_EQ(ecn->ce, 0);
-  TestPerPacketOptions options;
-  client_connection->set_per_packet_options(&options);
-  options.ecn_codepoint = ECN_ECT1;
+  client_connection->set_ecn_codepoint(ECN_ECT1);
   client_->SendSynchronousRequest("/foo");
   if (!GetQuicRestartFlag(quic_receive_ecn) ||
       !GetQuicRestartFlag(quic_quiche_ecn_sockets) ||
@@ -7175,9 +7185,7 @@
   EXPECT_EQ(ecn->ect0, 0);
   EXPECT_EQ(ecn->ect1, 0);
   EXPECT_EQ(ecn->ce, 0);
-  TestPerPacketOptions options;
-  client_connection->set_per_packet_options(&options);
-  options.ecn_codepoint = ECN_CE;
+  client_connection->set_ecn_codepoint(ECN_CE);
   client_->SendSynchronousRequest("/foo");
   if (!GetQuicRestartFlag(quic_receive_ecn) ||
       !GetQuicRestartFlag(quic_quiche_ecn_sockets) ||
@@ -7202,9 +7210,7 @@
   QuicEcnCounts* ecn = QuicSentPacketManagerPeer::GetPeerEcnCounts(
       QuicConnectionPeer::GetSentPacketManager(server_connection),
       APPLICATION_DATA);
-  TestPerPacketOptions options;
-  options.ecn_codepoint = ECN_ECT1;
-  server_connection->set_per_packet_options(&options);
+  server_connection->set_ecn_codepoint(ECN_ECT1);
   client_->SendSynchronousRequest("/foo");
   // A second request provides a packet for the client ACKs to go with.
   client_->SendSynchronousRequest("/foo");
diff --git a/quiche/quic/core/http/quic_spdy_session_test.cc b/quiche/quic/core/http/quic_spdy_session_test.cc
index e778864..6c98257 100644
--- a/quiche/quic/core/http/quic_spdy_session_test.cc
+++ b/quiche/quic/core/http/quic_spdy_session_test.cc
@@ -523,7 +523,7 @@
 
   void CompleteHandshake() {
     if (VersionHasIetfQuicFrames(transport_version())) {
-      EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+      EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
           .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
     }
     if (connection_->version().UsesTls() &&
@@ -791,7 +791,7 @@
 TEST_P(QuicSpdySessionTestServer,
        DebugDFatalIfMarkingClosedStreamWriteBlocked) {
   CompleteHandshake();
-  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
       .WillRepeatedly(Return(WriteResult(WRITE_STATUS_OK, 0)));
 
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
@@ -822,7 +822,7 @@
       static_cast<QuicSession*>(&session_), QuicUtils::GetMaxStreamCount());
   QuicStreamsBlockedFrame frame;
   frame.stream_count = QuicUtils::GetMaxStreamCount();
-  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
   EXPECT_CALL(debug_visitor, OnGoAwayFrameSent(_));
   session_.OnStreamsBlockedFrame(frame);
@@ -860,7 +860,7 @@
 
   // Expect that we only send one packet, the writes from different streams
   // should be bundled together.
-  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
   EXPECT_CALL(*send_algorithm, OnPacketSent(_, _, _, _, _));
   EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
@@ -926,7 +926,7 @@
 
   // Drive packet writer manually.
   EXPECT_CALL(*writer_, IsWriteBlocked()).WillRepeatedly(Return(true));
-  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _)).Times(0);
+  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _)).Times(0);
 
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
 
@@ -1078,7 +1078,7 @@
     return;
   }
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
-  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
 
   EXPECT_CALL(*connection_, SendControlFrame(_))
@@ -1118,7 +1118,7 @@
   StrictMock<MockHttp3DebugVisitor> debug_visitor;
   session_.set_debug_visitor(&debug_visitor);
 
-  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
   // Send max stream id (currently 32 bits).
   EXPECT_CALL(debug_visitor, OnGoAwayFrameSent(/* stream_id = */ 0xfffffffc));
@@ -1161,7 +1161,7 @@
       GetNthClientInitiatedBidirectionalStreamId(transport_version(), 0);
   EXPECT_TRUE(session_.GetOrCreateStream(kTestStreamId));
 
-  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
   // Send max stream id (currently 32 bits).
   EXPECT_CALL(debug_visitor, OnGoAwayFrameSent(/* stream_id = */ 0xfffffffc));
@@ -1268,7 +1268,7 @@
   // In HTTP/3, Qpack stream will send data on stream reset and cause packet to
   // be flushed.
   if (VersionUsesHttp3(transport_version())) {
-    EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+    EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
         .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
   }
   EXPECT_CALL(*connection_, SendControlFrame(_));
@@ -1503,7 +1503,7 @@
     EXPECT_CALL(*connection_, OnStreamReset(stream->id(), _));
     EXPECT_CALL(*connection_, SendControlFrame(_));
   } else {
-    EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+    EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
         .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
   }
   QuicRstStreamFrame rst_frame(kInvalidControlFrameId, stream->id(),
@@ -1782,7 +1782,7 @@
   CompleteHandshake();
   TestStream* stream = session_.CreateOutgoingBidirectionalStream();
 
-  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
   // Write headers with FIN set to close write side of stream.
   // Header block does not matter.
@@ -2838,7 +2838,7 @@
     session_.OnSetting(SETTINGS_MAX_FIELD_SECTION_SIZE, 5);
     EXPECT_EQ(5u, session_.max_outbound_header_list_size());
 
-    EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+    EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
         .WillRepeatedly(Return(WriteResult(WRITE_STATUS_OK, 0)));
     QpackEncoder* qpack_encoder = session_.qpack_encoder();
     EXPECT_EQ(0u, QpackEncoderPeer::maximum_blocked_streams(qpack_encoder));
@@ -3065,7 +3065,7 @@
                                 QuicUtils::StreamIdDelta(transport_version())));
 
   // Close connection.
-  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer_, WritePacket(_, _, _, _, _, _))
       .WillRepeatedly(Return(WriteResult(WRITE_STATUS_OK, 0)));
   EXPECT_CALL(*connection_, CloseConnection(QUIC_NO_ERROR, _, _))
       .WillOnce(
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index 3e575f2..6fd85b7 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -3251,7 +3251,7 @@
 
 QuicTime QuicConnection::CalculatePacketSentTime() {
   const QuicTime now = clock_->Now();
-  if (!supports_release_time_ || per_packet_options_ == nullptr) {
+  if (!supports_release_time_) {
     // Don't change the release delay.
     return now;
   }
@@ -3261,8 +3261,8 @@
   // Release before |now| is impossible.
   QuicTime next_release_time =
       std::max(now, next_release_time_result.release_time);
-  per_packet_options_->release_time_delay = next_release_time - now;
-  per_packet_options_->allow_burst = next_release_time_result.allow_burst;
+  packet_writer_params_.release_time_delay = next_release_time - now;
+  packet_writer_params_.allow_burst = next_release_time_result.allow_burst;
   return next_release_time;
 }
 
@@ -3929,7 +3929,7 @@
     return;
   }
   QUIC_DVLOG(1) << ENDPOINT << "ECN feedback is invalid, stop marking.";
-  ClearEcnCodepoint();
+  packet_writer_params_.ecn_codepoint = ECN_NOT_ECT;
 }
 
 std::unique_ptr<QuicSelfIssuedConnectionIdManager>
@@ -4073,7 +4073,8 @@
 
 QuicEcnCodepoint QuicConnection::GetEcnCodepointToSend(
     const QuicSocketAddress& destination_address) const {
-  const QuicEcnCodepoint original_codepoint = GetNextEcnCodepoint();
+  const QuicEcnCodepoint original_codepoint =
+      packet_writer_params_.ecn_codepoint;
   if (disable_ecn_codepoint_validation_) {
     return original_codepoint;
   }
@@ -4106,24 +4107,16 @@
   return original_codepoint;
 }
 
-void QuicConnection::ClearEcnCodepoint() { MaybeSetEcnCodepoint(ECN_NOT_ECT); }
-
-void QuicConnection::MaybeSetEcnCodepoint(QuicEcnCodepoint ecn_codepoint) {
-  if (per_packet_options_ != nullptr) {
-    per_packet_options_->ecn_codepoint = ecn_codepoint;
-  }
-}
-
 WriteResult QuicConnection::SendPacketToWriter(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
     const QuicSocketAddress& destination_address, QuicPacketWriter* writer,
     const QuicEcnCodepoint ecn_codepoint) {
-  QuicEcnCodepoint original_codepoint = GetNextEcnCodepoint();
+  QuicPacketWriterParams params = packet_writer_params_;
+  params.ecn_codepoint = ecn_codepoint;
   last_ecn_codepoint_sent_ = ecn_codepoint;
-  MaybeSetEcnCodepoint(ecn_codepoint);
-  WriteResult result = writer->WritePacket(
-      buffer, buf_len, self_address, destination_address, per_packet_options_);
-  MaybeSetEcnCodepoint(original_codepoint);
+  WriteResult result =
+      writer->WritePacket(buffer, buf_len, self_address, destination_address,
+                          per_packet_options_, params);
   return result;
 }
 
@@ -4231,7 +4224,7 @@
   if (!HasQueuedData() && !retransmission_alarm_->IsSet()) {
     SetRetransmissionAlarm();
   }
-  if (GetNextEcnCodepoint() == ECN_NOT_ECT ||
+  if (packet_writer_params_.ecn_codepoint == ECN_NOT_ECT ||
       default_path_.ecn_marked_packet_acked) {
     return;
   }
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 8274be2..a89e915 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -1319,6 +1319,20 @@
     return sent_server_preferred_address_;
   }
 
+  // Sets the ECN marking for all outgoing packets, assuming that the congestion
+  // control supports that codepoint. QuicConnection will revert to sending
+  // ECN_NOT_ECT if there is evidence the path is dropping ECN-marked packets,
+  // or if the peer provides invalid ECN feedback.
+  void set_ecn_codepoint(QuicEcnCodepoint ecn_codepoint) {
+    if (GetQuicReloadableFlag(quic_send_ect1)) {
+      packet_writer_params_.ecn_codepoint = ecn_codepoint;
+    }
+  }
+
+  QuicEcnCodepoint ecn_codepoint() const {
+    return packet_writer_params_.ecn_codepoint;
+  }
+
  protected:
   // Calls cancel() on all the alarms owned by this connection.
   void CancelAllAlarms();
@@ -1985,25 +1999,10 @@
   // Returns true if |address| is known server address.
   bool IsKnownServerAddress(const QuicSocketAddress& address) const;
 
-  // Retrieves the ECN codepoint stored in per_packet_options_, unless the flag
-  // is not set.
-  QuicEcnCodepoint GetNextEcnCodepoint() const {
-    return (per_packet_options_ != nullptr &&
-            GetQuicReloadableFlag(quic_send_ect1))
-               ? per_packet_options_->ecn_codepoint
-               : ECN_NOT_ECT;
-  }
-
   // Retrieves the ECN codepoint to be sent on the next packet.
   QuicEcnCodepoint GetEcnCodepointToSend(
       const QuicSocketAddress& destination_address) const;
 
-  // Sets the ECN codepoint to Not-ECT.
-  void ClearEcnCodepoint();
-
-  // Set the ECN codepoint, but only if set_per_packet_options has been called.
-  void MaybeSetEcnCodepoint(QuicEcnCodepoint ecn_codepoint);
-
   // Writes the packet to |writer| with the ECN mark specified in
   // |ecn_codepoint|. Will also set last_ecn_sent_ appropriately.
   WriteResult SendPacketToWriter(const char* buffer, size_t buf_len,
@@ -2033,6 +2032,7 @@
   QuicConnectionHelperInterface* helper_;  // Not owned.
   QuicAlarmFactory* alarm_factory_;        // Not owned.
   PerPacketOptions* per_packet_options_;   // Not owned.
+  QuicPacketWriterParams packet_writer_params_;
   QuicPacketWriter* writer_;  // Owned or not depending on |owns_writer_|.
   bool owns_writer_;
   // Encryption level for new packets. Should only be changed via
diff --git a/quiche/quic/core/quic_connection_test.cc b/quiche/quic/core/quic_connection_test.cc
index 5db68ba..4e326fd 100644
--- a/quiche/quic/core/quic_connection_test.cc
+++ b/quiche/quic/core/quic_connection_test.cc
@@ -17110,10 +17110,8 @@
 
 TEST_P(QuicConnectionTest, EcnCodepointsRejected) {
   SetQuicReloadableFlag(quic_send_ect1, true);
-  TestPerPacketOptions per_packet_options;
-  connection_.set_per_packet_options(&per_packet_options);
   for (QuicEcnCodepoint ecn : {ECN_NOT_ECT, ECN_ECT0, ECN_ECT1, ECN_CE}) {
-    per_packet_options.ecn_codepoint = ecn;
+    connection_.set_ecn_codepoint(ecn);
     if (ecn == ECN_ECT0) {
       EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillOnce(Return(false));
     } else if (ecn == ECN_ECT1) {
@@ -17121,17 +17119,15 @@
     }
     EXPECT_CALL(connection_, OnSerializedPacket(_));
     SendPing();
-    EXPECT_EQ(per_packet_options.ecn_codepoint, ecn);
+    EXPECT_EQ(connection_.ecn_codepoint(), ecn);
     EXPECT_EQ(writer_->last_ecn_sent(), ECN_NOT_ECT);
   }
 }
 
 TEST_P(QuicConnectionTest, EcnCodepointsAccepted) {
   SetQuicReloadableFlag(quic_send_ect1, true);
-  TestPerPacketOptions per_packet_options;
-  connection_.set_per_packet_options(&per_packet_options);
   for (QuicEcnCodepoint ecn : {ECN_NOT_ECT, ECN_ECT0, ECN_ECT1, ECN_CE}) {
-    per_packet_options.ecn_codepoint = ecn;
+    connection_.set_ecn_codepoint(ecn);
     if (ecn == ECN_ECT0) {
       EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillOnce(Return(true));
     } else if (ecn == ECN_ECT1) {
@@ -17143,34 +17139,30 @@
     if (ecn == ECN_CE) {
       expected_codepoint = ECN_NOT_ECT;
     }
-    EXPECT_EQ(per_packet_options.ecn_codepoint, ecn);
+    EXPECT_EQ(connection_.ecn_codepoint(), ecn);
     EXPECT_EQ(writer_->last_ecn_sent(), expected_codepoint);
   }
 }
 
 TEST_P(QuicConnectionTest, EcnCodepointsRejectedIfFlagIsFalse) {
   SetQuicReloadableFlag(quic_send_ect1, false);
-  TestPerPacketOptions per_packet_options;
-  connection_.set_per_packet_options(&per_packet_options);
   for (QuicEcnCodepoint ecn : {ECN_NOT_ECT, ECN_ECT0, ECN_ECT1, ECN_CE}) {
-    per_packet_options.ecn_codepoint = ecn;
+    connection_.set_ecn_codepoint(ecn);
     EXPECT_CALL(connection_, OnSerializedPacket(_));
     SendPing();
-    EXPECT_EQ(per_packet_options.ecn_codepoint, ECN_NOT_ECT);
+    EXPECT_EQ(connection_.ecn_codepoint(), ECN_NOT_ECT);
     EXPECT_EQ(writer_->last_ecn_sent(), ECN_NOT_ECT);
   }
 }
 
 TEST_P(QuicConnectionTest, EcnValidationDisabled) {
   SetQuicReloadableFlag(quic_send_ect1, true);
-  TestPerPacketOptions per_packet_options;
-  connection_.set_per_packet_options(&per_packet_options);
   QuicConnectionPeer::DisableEcnCodepointValidation(&connection_);
   for (QuicEcnCodepoint ecn : {ECN_NOT_ECT, ECN_ECT0, ECN_ECT1, ECN_CE}) {
-    per_packet_options.ecn_codepoint = ecn;
+    connection_.set_ecn_codepoint(ecn);
     EXPECT_CALL(connection_, OnSerializedPacket(_));
     SendPing();
-    EXPECT_EQ(per_packet_options.ecn_codepoint, ecn);
+    EXPECT_EQ(connection_.ecn_codepoint(), ecn);
     EXPECT_EQ(writer_->last_ecn_sent(), ecn);
   }
 }
@@ -17178,26 +17170,22 @@
 TEST_P(QuicConnectionTest, RtoDisablesEcnMarking) {
   SetQuicReloadableFlag(quic_send_ect1, true);
   EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  TestPerPacketOptions per_packet_options;
-  per_packet_options.ecn_codepoint = ECN_ECT1;
-  connection_.set_per_packet_options(&per_packet_options);
+  connection_.set_ecn_codepoint(ECN_ECT1);
   QuicPacketCreatorPeer::SetPacketNumber(
       QuicConnectionPeer::GetPacketCreator(&connection_), 1);
   SendPing();
   connection_.OnRetransmissionTimeout();
   EXPECT_EQ(writer_->last_ecn_sent(), ECN_NOT_ECT);
-  EXPECT_EQ(per_packet_options.ecn_codepoint, ECN_ECT1);
+  EXPECT_EQ(connection_.ecn_codepoint(), ECN_ECT1);
   // On 2nd RTO, QUIC abandons ECN.
   connection_.OnRetransmissionTimeout();
   EXPECT_EQ(writer_->last_ecn_sent(), ECN_NOT_ECT);
-  EXPECT_EQ(per_packet_options.ecn_codepoint, ECN_NOT_ECT);
+  EXPECT_EQ(connection_.ecn_codepoint(), ECN_NOT_ECT);
 }
 
 TEST_P(QuicConnectionTest, RtoDoesntDisableEcnMarkingIfEcnAcked) {
   EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  TestPerPacketOptions per_packet_options;
-  per_packet_options.ecn_codepoint = ECN_ECT1;
-  connection_.set_per_packet_options(&per_packet_options);
+  connection_.set_ecn_codepoint(ECN_ECT1);
   QuicPacketCreatorPeer::SetPacketNumber(
       QuicConnectionPeer::GetPacketCreator(&connection_), 1);
   if (!GetQuicReloadableFlag(quic_send_ect1)) {
@@ -17213,18 +17201,17 @@
   QuicEcnCodepoint expected_codepoint =
       GetQuicReloadableFlag(quic_send_ect1) ? ECN_ECT1 : ECN_NOT_ECT;
   EXPECT_EQ(writer_->last_ecn_sent(), expected_codepoint);
-  EXPECT_EQ(per_packet_options.ecn_codepoint, expected_codepoint);
+  EXPECT_EQ(connection_.ecn_codepoint(), expected_codepoint);
   connection_.OnRetransmissionTimeout();
   EXPECT_EQ(writer_->last_ecn_sent(), expected_codepoint);
-  EXPECT_EQ(per_packet_options.ecn_codepoint, expected_codepoint);
+  EXPECT_EQ(connection_.ecn_codepoint(), expected_codepoint);
 }
 
 TEST_P(QuicConnectionTest, InvalidFeedbackCancelsEcn) {
+  SetQuicReloadableFlag(quic_send_ect1, true);
   EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  TestPerPacketOptions per_packet_options;
-  per_packet_options.ecn_codepoint = ECN_ECT1;
-  connection_.set_per_packet_options(&per_packet_options);
-  EXPECT_EQ(per_packet_options.ecn_codepoint, ECN_ECT1);
+  connection_.set_ecn_codepoint(ECN_ECT1);
+  EXPECT_EQ(connection_.ecn_codepoint(), ECN_ECT1);
   if (!GetQuicReloadableFlag(quic_send_ect1)) {
     EXPECT_QUIC_BUG(connection_.OnInvalidEcnFeedback(),
                     "Unexpected call to OnInvalidEcnFeedback\\(\\)\\.");
@@ -17232,15 +17219,13 @@
   } else {
     connection_.OnInvalidEcnFeedback();
   }
-  EXPECT_EQ(per_packet_options.ecn_codepoint, ECN_NOT_ECT);
+  EXPECT_EQ(connection_.ecn_codepoint(), ECN_NOT_ECT);
 }
 
 TEST_P(QuicConnectionTest, StateMatchesSentEcn) {
   SetQuicReloadableFlag(quic_send_ect1, true);
   EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  TestPerPacketOptions per_packet_options;
-  per_packet_options.ecn_codepoint = ECN_ECT1;
-  connection_.set_per_packet_options(&per_packet_options);
+  connection_.set_ecn_codepoint(ECN_ECT1);
   SendPing();
   QuicSentPacketManager* sent_packet_manager =
       QuicConnectionPeer::GetSentPacketManager(&connection_);
@@ -17256,9 +17241,7 @@
   }
   SetQuicReloadableFlag(quic_send_ect1, true);
   EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  TestPerPacketOptions per_packet_options;
-  per_packet_options.ecn_codepoint = ECN_ECT1;
-  connection_.set_per_packet_options(&per_packet_options);
+  connection_.set_ecn_codepoint(ECN_ECT1);
   // All these steps are necessary to send an INITIAL ping and save it to be
   // coalesced, instead of just calling SendPing() and sending it immediately.
   char buffer[1000];
@@ -17272,7 +17255,7 @@
   creator_->set_encryption_level(ENCRYPTION_FORWARD_SECURE);
   EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillRepeatedly(Return(true));
   // If not for the line below, these packets would coalesce.
-  per_packet_options.ecn_codepoint = ECN_ECT0;
+  connection_.set_ecn_codepoint(ECN_ECT0);
   EXPECT_EQ(writer_->packets_write_attempts(), 0);
   SendPing();
   EXPECT_EQ(writer_->packets_write_attempts(), 2);
@@ -17282,14 +17265,12 @@
 TEST_P(QuicConnectionTest, BufferedPacketRetainsOldEcn) {
   SetQuicReloadableFlag(quic_send_ect1, true);
   EXPECT_CALL(*send_algorithm_, SupportsECT1()).WillRepeatedly(Return(true));
-  TestPerPacketOptions per_packet_options;
-  per_packet_options.ecn_codepoint = ECN_ECT1;
-  connection_.set_per_packet_options(&per_packet_options);
+  connection_.set_ecn_codepoint(ECN_ECT1);
   writer_->SetWriteBlocked();
   EXPECT_CALL(visitor_, OnWriteBlocked()).Times(2);
   SendPing();
   EXPECT_CALL(*send_algorithm_, SupportsECT0()).WillRepeatedly(Return(true));
-  per_packet_options.ecn_codepoint = ECN_ECT0;
+  connection_.set_ecn_codepoint(ECN_ECT0);
   writer_->SetWritable();
   connection_.OnCanWrite();
   EXPECT_EQ(writer_->last_ecn_sent(), ECN_ECT1);
diff --git a/quiche/quic/core/quic_crypto_stream_test.cc b/quiche/quic/core/quic_crypto_stream_test.cc
index 76f840b..70928ae 100644
--- a/quiche/quic/core/quic_crypto_stream_test.cc
+++ b/quiche/quic/core/quic_crypto_stream_test.cc
@@ -133,7 +133,7 @@
                                            Perspective::IS_CLIENT)),
         session_(connection_, /*create_mock_crypto_stream=*/false) {
     EXPECT_CALL(*static_cast<MockPacketWriter*>(connection_->writer()),
-                WritePacket(_, _, _, _, _))
+                WritePacket(_, _, _, _, _, _))
         .WillRepeatedly(Return(WriteResult(WRITE_STATUS_OK, 0)));
     stream_ = new MockQuicCryptoStream(&session_);
     session_.SetCryptoStream(stream_);
diff --git a/quiche/quic/core/quic_default_packet_writer.cc b/quiche/quic/core/quic_default_packet_writer.cc
index 7eac827..6d45217 100644
--- a/quiche/quic/core/quic_default_packet_writer.cc
+++ b/quiche/quic/core/quic_default_packet_writer.cc
@@ -15,14 +15,13 @@
 
 WriteResult QuicDefaultPacketWriter::WritePacket(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
-    const QuicSocketAddress& peer_address, PerPacketOptions* options) {
+    const QuicSocketAddress& peer_address, PerPacketOptions* /*options*/,
+    const QuicPacketWriterParams& params) {
   QUICHE_DCHECK(!write_blocked_);
   QuicUdpPacketInfo packet_info;
   packet_info.SetPeerAddress(peer_address);
   packet_info.SetSelfIp(self_address);
-  if (options != nullptr) {
-    packet_info.SetEcnCodepoint(options->ecn_codepoint);
-  }
+  packet_info.SetEcnCodepoint(params.ecn_codepoint);
   WriteResult result =
       QuicUdpSocketApi().WritePacket(fd_, buffer, buf_len, packet_info);
   if (IsWriteBlockedStatus(result.status)) {
diff --git a/quiche/quic/core/quic_default_packet_writer.h b/quiche/quic/core/quic_default_packet_writer.h
index 718f8eb..c513362 100644
--- a/quiche/quic/core/quic_default_packet_writer.h
+++ b/quiche/quic/core/quic_default_packet_writer.h
@@ -28,7 +28,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override;
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override;
   bool IsWriteBlocked() const override;
   void SetWritable() override;
   absl::optional<int> MessageTooBigErrorCode() const override;
diff --git a/quiche/quic/core/quic_dispatcher_test.cc b/quiche/quic/core/quic_dispatcher_test.cc
index ff2516a..37d6185 100644
--- a/quiche/quic/core/quic_dispatcher_test.cc
+++ b/quiche/quic/core/quic_dispatcher_test.cc
@@ -1544,7 +1544,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& /*self_client_address*/,
                           const QuicSocketAddress& /*peer_client_address*/,
-                          PerPacketOptions* /*options*/) override {
+                          PerPacketOptions* /*options*/,
+                          const QuicPacketWriterParams& /*params*/) override {
     packets_.push_back(
         QuicEncryptedPacket(buffer, buf_len, /*owns_buffer=*/false).Clone());
     return WriteResult(WRITE_STATUS_OK, buf_len);
@@ -1877,7 +1878,8 @@
   WriteResult WritePacket(const char* /*buffer*/, size_t /*buf_len*/,
                           const QuicIpAddress& /*self_client_address*/,
                           const QuicSocketAddress& /*peer_client_address*/,
-                          PerPacketOptions* /*options*/) override {
+                          PerPacketOptions* /*options*/,
+                          const QuicPacketWriterParams& /*params*/) override {
     // It would be quite possible to actually implement this method here with
     // the fake blocked status, but it would be significantly more work in
     // Chromium, and since it's not called anyway, don't bother.
diff --git a/quiche/quic/core/quic_linux_socket_utils.h b/quiche/quic/core/quic_linux_socket_utils.h
index de80dfd..8d53e47 100644
--- a/quiche/quic/core/quic_linux_socket_utils.h
+++ b/quiche/quic/core/quic_linux_socket_utils.h
@@ -109,18 +109,19 @@
                 const QuicSocketAddress& peer_address)
       : BufferedWrite(buffer, buf_len, self_address, peer_address,
                       std::unique_ptr<PerPacketOptions>(),
-                      /*release_time=*/0) {}
+                      QuicPacketWriterParams(), /*release_time=*/0) {}
 
   BufferedWrite(const char* buffer, size_t buf_len,
                 const QuicIpAddress& self_address,
                 const QuicSocketAddress& peer_address,
                 std::unique_ptr<PerPacketOptions> options,
-                uint64_t release_time)
+                const QuicPacketWriterParams& params, uint64_t release_time)
       : buffer(buffer),
         buf_len(buf_len),
         self_address(self_address),
         peer_address(peer_address),
         options(std::move(options)),
+        params(params),
         release_time(release_time) {}
 
   const char* buffer;  // Not owned.
@@ -128,6 +129,7 @@
   QuicIpAddress self_address;
   QuicSocketAddress peer_address;
   std::unique_ptr<PerPacketOptions> options;
+  QuicPacketWriterParams params;
 
   // The release time according to the owning packet writer's clock, which is
   // often not a QuicClock. Calculated from packet writer's Now() and the
diff --git a/quiche/quic/core/quic_packet_writer.h b/quiche/quic/core/quic_packet_writer.h
index 3e6cb21..c3b3fab 100644
--- a/quiche/quic/core/quic_packet_writer.h
+++ b/quiche/quic/core/quic_packet_writer.h
@@ -18,7 +18,11 @@
 
 struct WriteResult;
 
-struct QUIC_EXPORT_PRIVATE PerPacketOptions {
+// This class allows a platform to pass instructions to an associated child of
+// QuicWriter without intervening QUIC code understanding anything about its
+// contents.
+class QUIC_EXPORT_PRIVATE PerPacketOptions {
+ public:
   virtual ~PerPacketOptions() {}
 
   // Returns a heap-allocated copy of |this|.
@@ -29,7 +33,10 @@
   // This method is declared pure virtual in order to ensure the subclasses
   // would not forget to override it.
   virtual std::unique_ptr<PerPacketOptions> Clone() const = 0;
+};
 
+// The owner of QuicPacketWriter can pass control information via this struct.
+struct QUIC_EXPORT_PRIVATE QuicPacketWriterParams {
   // Specifies ideal release time delay for this packet.
   QuicTime::Delta release_time_delay = QuicTime::Delta::Zero();
   // Whether it is allowed to send this packet without |release_time_delay|.
@@ -107,7 +114,8 @@
   virtual WriteResult WritePacket(const char* buffer, size_t buf_len,
                                   const QuicIpAddress& self_address,
                                   const QuicSocketAddress& peer_address,
-                                  PerPacketOptions* options) = 0;
+                                  PerPacketOptions* options,
+                                  const QuicPacketWriterParams& params) = 0;
 
   // Returns true if the network socket is not writable.
   virtual bool IsWriteBlocked() const = 0;
diff --git a/quiche/quic/core/quic_packet_writer_wrapper.cc b/quiche/quic/core/quic_packet_writer_wrapper.cc
index c040c91..c3b4b28 100644
--- a/quiche/quic/core/quic_packet_writer_wrapper.cc
+++ b/quiche/quic/core/quic_packet_writer_wrapper.cc
@@ -14,9 +14,10 @@
 
 WriteResult QuicPacketWriterWrapper::WritePacket(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
-    const QuicSocketAddress& peer_address, PerPacketOptions* options) {
+    const QuicSocketAddress& peer_address, PerPacketOptions* options,
+    const QuicPacketWriterParams& params) {
   return writer_->WritePacket(buffer, buf_len, self_address, peer_address,
-                              options);
+                              options, params);
 }
 
 bool QuicPacketWriterWrapper::IsWriteBlocked() const {
diff --git a/quiche/quic/core/quic_packet_writer_wrapper.h b/quiche/quic/core/quic_packet_writer_wrapper.h
index 3afeaf1..7d43fdc 100644
--- a/quiche/quic/core/quic_packet_writer_wrapper.h
+++ b/quiche/quic/core/quic_packet_writer_wrapper.h
@@ -27,7 +27,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override;
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override;
   bool IsWriteBlocked() const override;
   void SetWritable() override;
   absl::optional<int> MessageTooBigErrorCode() const override;
diff --git a/quiche/quic/core/quic_path_validator.cc b/quiche/quic/core/quic_path_validator.cc
index 48c21d2..3185664 100644
--- a/quiche/quic/core/quic_path_validator.cc
+++ b/quiche/quic/core/quic_path_validator.cc
@@ -169,7 +169,7 @@
                 << path_context_->peer_address();
   path_context_->WriterToUse()->WritePacket(
       buffer, buf_len, path_context_->self_address().host(),
-      path_context_->peer_address(), nullptr);
+      path_context_->peer_address(), nullptr, QuicPacketWriterParams());
 }
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_session_test.cc b/quiche/quic/core/quic_session_test.cc
index bdf74b3..9bcbf71 100644
--- a/quiche/quic/core/quic_session_test.cc
+++ b/quiche/quic/core/quic_session_test.cc
@@ -1170,7 +1170,7 @@
 
   // Expect that we only send one packet, the writes from different streams
   // should be bundled together.
-  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
   EXPECT_CALL(*send_algorithm, OnPacketSent(_, _, _, _, _));
   EXPECT_CALL(*send_algorithm, OnApplicationLimited(_));
@@ -1238,7 +1238,7 @@
   MockPacketWriter* writer = static_cast<MockPacketWriter*>(
       QuicConnectionPeer::GetWriter(session_.connection()));
   EXPECT_CALL(*writer, IsWriteBlocked()).WillRepeatedly(Return(true));
-  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _)).Times(0);
+  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _, _)).Times(0);
 
   TestStream* stream2 = session_.CreateOutgoingBidirectionalStream();
 
@@ -1415,7 +1415,7 @@
   connection_->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE);
   MockPacketWriter* writer = static_cast<MockPacketWriter*>(
       QuicConnectionPeer::GetWriter(session_.connection()));
-  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
 
   EXPECT_CALL(*connection_, SendControlFrame(_))
@@ -1471,7 +1471,7 @@
 
   MockPacketWriter* writer = static_cast<MockPacketWriter*>(
       QuicConnectionPeer::GetWriter(session_.connection()));
-  EXPECT_CALL(*writer, WritePacket(_, _, _, new_peer_address, _))
+  EXPECT_CALL(*writer, WritePacket(_, _, _, new_peer_address, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
 
   EXPECT_CALL(*connection_, SendConnectivityProbingPacket(_, _))
@@ -3123,7 +3123,7 @@
   CompleteHandshake();
   MockPacketWriter* writer = static_cast<MockPacketWriter*>(
       QuicConnectionPeer::GetWriter(session_.connection()));
-  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _))
+  EXPECT_CALL(*writer, WritePacket(_, _, _, _, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
   // Set a small connection level flow control limit.
   const uint64_t kWindow = 36;
diff --git a/quiche/quic/core/quic_time_wait_list_manager.cc b/quiche/quic/core/quic_time_wait_list_manager.cc
index d479973..484d35e 100644
--- a/quiche/quic/core/quic_time_wait_list_manager.cc
+++ b/quiche/quic/core/quic_time_wait_list_manager.cc
@@ -377,7 +377,7 @@
   WriteResult result = writer_->WritePacket(
       queued_packet->packet()->data(), queued_packet->packet()->length(),
       queued_packet->self_address().host(), queued_packet->peer_address(),
-      nullptr);
+      nullptr, QuicPacketWriterParams());
 
   // If using a batch writer and the packet is buffered, flush it.
   if (writer_->IsBatchMode() && result.status == WRITE_STATUS_OK &&
diff --git a/quiche/quic/core/quic_time_wait_list_manager_test.cc b/quiche/quic/core/quic_time_wait_list_manager_test.cc
index eef0b99..9b2f36f 100644
--- a/quiche/quic/core/quic_time_wait_list_manager_test.cc
+++ b/quiche/quic/core/quic_time_wait_list_manager_test.cc
@@ -236,7 +236,7 @@
           connection_id_, EmptyQuicConnectionId(), /*ietf_quic=*/false,
           /*use_length_prefix=*/false, AllSupportedVersions()));
   EXPECT_CALL(writer_, WritePacket(_, packet->length(), self_address_.host(),
-                                   peer_address_, _))
+                                   peer_address_, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
 
   time_wait_list_manager_.SendVersionNegotiationPacket(
@@ -253,7 +253,7 @@
           connection_id_, EmptyQuicConnectionId(), /*ietf_quic=*/true,
           /*use_length_prefix=*/false, AllSupportedVersions()));
   EXPECT_CALL(writer_, WritePacket(_, packet->length(), self_address_.host(),
-                                   peer_address_, _))
+                                   peer_address_, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
 
   time_wait_list_manager_.SendVersionNegotiationPacket(
@@ -269,7 +269,7 @@
           connection_id_, EmptyQuicConnectionId(), /*ietf_quic=*/true,
           /*use_length_prefix=*/true, AllSupportedVersions()));
   EXPECT_CALL(writer_, WritePacket(_, packet->length(), self_address_.host(),
-                                   peer_address_, _))
+                                   peer_address_, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
 
   time_wait_list_manager_.SendVersionNegotiationPacket(
@@ -286,7 +286,7 @@
           connection_id_, TestConnectionId(0x33), /*ietf_quic=*/true,
           /*use_length_prefix=*/true, AllSupportedVersions()));
   EXPECT_CALL(writer_, WritePacket(_, packet->length(), self_address_.host(),
-                                   peer_address_, _))
+                                   peer_address_, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
 
   time_wait_list_manager_.SendVersionNegotiationPacket(
@@ -307,7 +307,7 @@
                   QuicTimeWaitListManager::SEND_CONNECTION_CLOSE_PACKETS,
                   &termination_packets);
   EXPECT_CALL(writer_, WritePacket(_, kConnectionCloseLength,
-                                   self_address_.host(), peer_address_, _))
+                                   self_address_.host(), peer_address_, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
 
   ProcessPacket(connection_id_);
@@ -327,7 +327,7 @@
                   QuicTimeWaitListManager::SEND_CONNECTION_CLOSE_PACKETS,
                   &termination_packets);
   EXPECT_CALL(writer_, WritePacket(_, kConnectionCloseLength,
-                                   self_address_.host(), peer_address_, _))
+                                   self_address_.host(), peer_address_, _, _))
       .Times(2)
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
 
@@ -339,7 +339,7 @@
   AddConnectionId(connection_id_,
                   QuicTimeWaitListManager::SEND_STATELESS_RESET);
   EXPECT_CALL(writer_,
-              WritePacket(_, _, self_address_.host(), peer_address_, _))
+              WritePacket(_, _, self_address_.host(), peer_address_, _, _))
       .With(Args<0, 1>(PublicResetPacketEq(connection_id_)))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
 
@@ -353,7 +353,7 @@
   EXPECT_EQ(1u, time_wait_list_manager_.num_connections());
   for (int packet_number = 1; packet_number < 101; ++packet_number) {
     if ((packet_number & (packet_number - 1)) == 0) {
-      EXPECT_CALL(writer_, WritePacket(_, _, _, _, _))
+      EXPECT_CALL(writer_, WritePacket(_, _, _, _, _, _))
           .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
     }
     ProcessPacket(connection_id_);
@@ -373,7 +373,7 @@
   AddStatelessConnectionId(connection_id_);
 
   EXPECT_CALL(writer_,
-              WritePacket(_, _, self_address_.host(), peer_address_, _))
+              WritePacket(_, _, self_address_.host(), peer_address_, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
 
   ProcessPacket(connection_id_);
@@ -468,14 +468,14 @@
       connection_id, EmptyQuicConnectionId(), /*packet_number=*/234));
   // Let first write through.
   EXPECT_CALL(writer_,
-              WritePacket(_, _, self_address_.host(), peer_address_, _))
+              WritePacket(_, _, self_address_.host(), peer_address_, _, _))
       .With(Args<0, 1>(PublicResetPacketEq(connection_id)))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, packet->length())));
   ProcessPacket(connection_id);
 
   // write block for the next packet.
   EXPECT_CALL(writer_,
-              WritePacket(_, _, self_address_.host(), peer_address_, _))
+              WritePacket(_, _, self_address_.host(), peer_address_, _, _))
       .With(Args<0, 1>(PublicResetPacketEq(connection_id)))
       .WillOnce(DoAll(Assign(&writer_is_blocked_, true),
                       Return(WriteResult(WRITE_STATUS_BLOCKED, EAGAIN))));
@@ -492,7 +492,7 @@
                   QuicTimeWaitListManager::SEND_STATELESS_RESET);
   std::unique_ptr<QuicEncryptedPacket> other_packet(ConstructEncryptedPacket(
       other_connection_id, EmptyQuicConnectionId(), /*packet_number=*/23423));
-  EXPECT_CALL(writer_, WritePacket(_, _, _, _, _)).Times(0);
+  EXPECT_CALL(writer_, WritePacket(_, _, _, _, _, _)).Times(0);
   EXPECT_CALL(visitor_, OnWriteBlocked(&time_wait_list_manager_));
   ProcessPacket(other_connection_id);
   EXPECT_EQ(2u, time_wait_list_manager_.num_connections());
@@ -500,11 +500,11 @@
   // Now expect all the write blocked public reset packets to be sent again.
   writer_is_blocked_ = false;
   EXPECT_CALL(writer_,
-              WritePacket(_, _, self_address_.host(), peer_address_, _))
+              WritePacket(_, _, self_address_.host(), peer_address_, _, _))
       .With(Args<0, 1>(PublicResetPacketEq(connection_id)))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, packet->length())));
   EXPECT_CALL(writer_,
-              WritePacket(_, _, self_address_.host(), peer_address_, _))
+              WritePacket(_, _, self_address_.host(), peer_address_, _, _))
       .With(Args<0, 1>(PublicResetPacketEq(other_connection_id)))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, packet->length())));
   time_wait_list_manager_.OnBlockedWriterCanWrite();
@@ -527,7 +527,7 @@
   EXPECT_EQ(1u, time_wait_list_manager_.num_connections());
 
   EXPECT_CALL(writer_, WritePacket(_, kConnectionCloseLength,
-                                   self_address_.host(), peer_address_, _))
+                                   self_address_.host(), peer_address_, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
 
   ProcessPacket(connection_id_);
@@ -653,7 +653,7 @@
 
   // Termination packet is not encrypted, instead, send stateless reset.
   EXPECT_CALL(writer_,
-              WritePacket(_, _, self_address_.host(), peer_address_, _))
+              WritePacket(_, _, self_address_.host(), peer_address_, _, _))
       .With(Args<0, 1>(PublicResetPacketEq(connection_id_)))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 0)));
   // Processes IETF short header packet.
@@ -677,7 +677,7 @@
       TimeWaitConnectionInfo(/*ietf_quic=*/true, &termination_packets,
                              {connection_id_}));
   EXPECT_CALL(writer_, WritePacket(_, kConnectionCloseLength,
-                                   self_address_.host(), peer_address_, _))
+                                   self_address_.host(), peer_address_, _, _))
       .WillOnce(Return(WriteResult(WRITE_STATUS_OK, 1)));
 
   // Processes IETF short header packet.
@@ -707,7 +707,7 @@
                              active_connection_ids, QuicTime::Delta::Zero()));
 
   EXPECT_CALL(writer_, WritePacket(_, kConnectionCloseLength,
-                                   self_address_.host(), peer_address_, _))
+                                   self_address_.host(), peer_address_, _, _))
       .Times(2)
       .WillRepeatedly(Return(WriteResult(WRITE_STATUS_OK, 1)));
   // Processes IETF short header packet.
@@ -743,7 +743,7 @@
       .Times(testing::AnyNumber());
   // Write block for the next packets.
   EXPECT_CALL(writer_,
-              WritePacket(_, _, self_address_.host(), peer_address_, _))
+              WritePacket(_, _, self_address_.host(), peer_address_, _, _))
       .With(Args<0, 1>(PublicResetPacketEq(TestConnectionId(1))))
       .WillOnce(DoAll(Assign(&writer_is_blocked_, true),
                       Return(WriteResult(WRITE_STATUS_BLOCKED, EAGAIN))));
diff --git a/quiche/quic/masque/masque_encapsulated_client.cc b/quiche/quic/masque/masque_encapsulated_client.cc
index d384363..34adde4 100644
--- a/quiche/quic/masque/masque_encapsulated_client.cc
+++ b/quiche/quic/masque/masque_encapsulated_client.cc
@@ -78,7 +78,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& /*self_address*/,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* /*options*/) override {
+                          PerPacketOptions* /*options*/,
+                          const QuicPacketWriterParams& /*params*/) override {
     QUICHE_DCHECK(peer_address.IsInitialized());
     QUIC_DVLOG(1) << "MasquePacketWriter trying to write " << buf_len
                   << " bytes to " << peer_address;
diff --git a/quiche/quic/qbone/qbone_session_test.cc b/quiche/quic/qbone/qbone_session_test.cc
index 1a9d023..ab92c1c 100644
--- a/quiche/quic/qbone/qbone_session_test.cc
+++ b/quiche/quic/qbone/qbone_session_test.cc
@@ -355,11 +355,12 @@
     // Hook everything up!
     MockPacketWriter* client_writer = static_cast<MockPacketWriter*>(
         QuicConnectionPeer::GetWriter(client_peer_->connection()));
-    ON_CALL(*client_writer, WritePacket(_, _, _, _, _))
+    ON_CALL(*client_writer, WritePacket(_, _, _, _, _, _))
         .WillByDefault(Invoke([this](const char* buffer, size_t buf_len,
                                      const QuicIpAddress& self_address,
                                      const QuicSocketAddress& peer_address,
-                                     PerPacketOptions* options) {
+                                     PerPacketOptions* option,
+                                     const QuicPacketWriterParams& params) {
           char* copy = new char[1024 * 1024];
           memcpy(copy, buffer, buf_len);
           runner_.Schedule([this, copy, buf_len] {
@@ -373,11 +374,12 @@
         }));
     MockPacketWriter* server_writer = static_cast<MockPacketWriter*>(
         QuicConnectionPeer::GetWriter(server_peer_->connection()));
-    ON_CALL(*server_writer, WritePacket(_, _, _, _, _))
+    ON_CALL(*server_writer, WritePacket(_, _, _, _, _, _))
         .WillByDefault(Invoke([this](const char* buffer, size_t buf_len,
                                      const QuicIpAddress& self_address,
                                      const QuicSocketAddress& peer_address,
-                                     PerPacketOptions* options) {
+                                     PerPacketOptions* options,
+                                     const QuicPacketWriterParams& params) {
           char* copy = new char[1024 * 1024];
           memcpy(copy, buffer, buf_len);
           runner_.Schedule([this, copy, buf_len] {
diff --git a/quiche/quic/qbone/qbone_stream_test.cc b/quiche/quic/qbone/qbone_stream_test.cc
index b7cc198..16052bc 100644
--- a/quiche/quic/qbone/qbone_stream_test.cc
+++ b/quiche/quic/qbone/qbone_stream_test.cc
@@ -101,7 +101,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override {
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override {
     return WriteResult(WRITE_STATUS_ERROR, 0);
   }
 
diff --git a/quiche/quic/test_tools/bad_packet_writer.cc b/quiche/quic/test_tools/bad_packet_writer.cc
index 1e146c4..7961fe8 100644
--- a/quiche/quic/test_tools/bad_packet_writer.cc
+++ b/quiche/quic/test_tools/bad_packet_writer.cc
@@ -17,13 +17,14 @@
 WriteResult BadPacketWriter::WritePacket(const char* buffer, size_t buf_len,
                                          const QuicIpAddress& self_address,
                                          const QuicSocketAddress& peer_address,
-                                         PerPacketOptions* options) {
+                                         PerPacketOptions* options,
+                                         const QuicPacketWriterParams& params) {
   if (error_code_ == 0 || packet_causing_write_error_ > 0) {
     if (packet_causing_write_error_ > 0) {
       --packet_causing_write_error_;
     }
     return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address,
-                                                peer_address, options);
+                                                peer_address, options, params);
   }
   // It's time to cause write error.
   int error_code = error_code_;
diff --git a/quiche/quic/test_tools/bad_packet_writer.h b/quiche/quic/test_tools/bad_packet_writer.h
index bcf12f5..5f3da24 100644
--- a/quiche/quic/test_tools/bad_packet_writer.h
+++ b/quiche/quic/test_tools/bad_packet_writer.h
@@ -21,7 +21,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override;
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override;
 
  private:
   size_t packet_causing_write_error_;
diff --git a/quiche/quic/test_tools/first_flight.cc b/quiche/quic/test_tools/first_flight.cc
index dcdeac3..a0b93fb 100644
--- a/quiche/quic/test_tools/first_flight.cc
+++ b/quiche/quic/test_tools/first_flight.cc
@@ -75,11 +75,14 @@
   void OnDelegatedPacket(const char* buffer, size_t buf_len,
                          const QuicIpAddress& /*self_client_address*/,
                          const QuicSocketAddress& /*peer_client_address*/,
-                         PerPacketOptions* /*options*/) override {
+                         PerPacketOptions* /*options*/,
+                         const QuicPacketWriterParams& params) override {
     packets_.emplace_back(
         QuicReceivedPacket(buffer, buf_len,
                            connection_helper_.GetClock()->ApproximateNow(),
-                           /*owns_buffer=*/false)
+                           /*owns_buffer=*/false, /*ttl=*/0, /*ttl_valid=*/true,
+                           /*packet_headers=*/nullptr, /*headers_length=*/0,
+                           /*owns_header_buffer=*/false, params.ecn_codepoint)
             .Clone());
   }
 
diff --git a/quiche/quic/test_tools/first_flight.h b/quiche/quic/test_tools/first_flight.h
index 3896031..948511a 100644
--- a/quiche/quic/test_tools/first_flight.h
+++ b/quiche/quic/test_tools/first_flight.h
@@ -31,7 +31,8 @@
     virtual void OnDelegatedPacket(const char* buffer, size_t buf_len,
                                    const QuicIpAddress& self_client_address,
                                    const QuicSocketAddress& peer_client_address,
-                                   PerPacketOptions* options) = 0;
+                                   PerPacketOptions* options,
+                                   const QuicPacketWriterParams& params) = 0;
   };
 
   // |delegate| MUST be valid for the duration of the DelegatedPacketWriter's
@@ -62,9 +63,10 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_client_address,
                           const QuicSocketAddress& peer_client_address,
-                          PerPacketOptions* options) override {
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override {
     delegate_->OnDelegatedPacket(buffer, buf_len, self_client_address,
-                                 peer_client_address, options);
+                                 peer_client_address, options, params);
     return WriteResult(WRITE_STATUS_OK, buf_len);
   }
 
diff --git a/quiche/quic/test_tools/limited_mtu_test_writer.cc b/quiche/quic/test_tools/limited_mtu_test_writer.cc
index fa46be1..8f9bc1c 100644
--- a/quiche/quic/test_tools/limited_mtu_test_writer.cc
+++ b/quiche/quic/test_tools/limited_mtu_test_writer.cc
@@ -13,14 +13,15 @@
 
 WriteResult LimitedMtuTestWriter::WritePacket(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
-    const QuicSocketAddress& peer_address, PerPacketOptions* options) {
+    const QuicSocketAddress& peer_address, PerPacketOptions* options,
+    const QuicPacketWriterParams& params) {
   if (buf_len > mtu_) {
     // Drop the packet.
     return WriteResult(WRITE_STATUS_OK, buf_len);
   }
 
   return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address,
-                                              peer_address, options);
+                                              peer_address, options, params);
 }
 
 }  // namespace test
diff --git a/quiche/quic/test_tools/limited_mtu_test_writer.h b/quiche/quic/test_tools/limited_mtu_test_writer.h
index 96cc828..e046872 100644
--- a/quiche/quic/test_tools/limited_mtu_test_writer.h
+++ b/quiche/quic/test_tools/limited_mtu_test_writer.h
@@ -24,7 +24,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override;
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override;
 
  private:
   QuicByteCount mtu_;
diff --git a/quiche/quic/test_tools/packet_dropping_test_writer.cc b/quiche/quic/test_tools/packet_dropping_test_writer.cc
index 69aae14..ac9625f 100644
--- a/quiche/quic/test_tools/packet_dropping_test_writer.cc
+++ b/quiche/quic/test_tools/packet_dropping_test_writer.cc
@@ -88,7 +88,8 @@
 
 WriteResult PacketDroppingTestWriter::WritePacket(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
-    const QuicSocketAddress& peer_address, PerPacketOptions* options) {
+    const QuicSocketAddress& peer_address, PerPacketOptions* options,
+    const QuicPacketWriterParams& params) {
   ++num_calls_to_write_;
   ReleaseOldPackets();
 
@@ -157,7 +158,7 @@
     }
     delayed_packets_.push_back(
         DelayedWrite(buffer, buf_len, self_address, peer_address,
-                     std::move(delayed_options), send_time));
+                     std::move(delayed_options), params, send_time));
     cur_buffer_size_ += buf_len;
 
     // Set the alarm if it's not yet set.
@@ -169,7 +170,7 @@
   }
 
   return QuicPacketWriterWrapper::WritePacket(buffer, buf_len, self_address,
-                                              peer_address, options);
+                                              peer_address, options, params);
 }
 
 bool PacketDroppingTestWriter::IsWriteBlocked() const {
@@ -207,7 +208,7 @@
   // Grab the next one off the queue and send it.
   QuicPacketWriterWrapper::WritePacket(
       iter->buffer.data(), iter->buffer.length(), iter->self_address,
-      iter->peer_address, iter->options.get());
+      iter->peer_address, iter->options.get(), iter->params);
   QUICHE_DCHECK_GE(cur_buffer_size_, iter->buffer.length());
   cur_buffer_size_ -= iter->buffer.length();
   delayed_packets_.erase(iter);
@@ -239,11 +240,13 @@
 PacketDroppingTestWriter::DelayedWrite::DelayedWrite(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
     const QuicSocketAddress& peer_address,
-    std::unique_ptr<PerPacketOptions> options, QuicTime send_time)
+    std::unique_ptr<PerPacketOptions> options,
+    const QuicPacketWriterParams& params, QuicTime send_time)
     : buffer(buffer, buf_len),
       self_address(self_address),
       peer_address(peer_address),
       options(std::move(options)),
+      params(params),
       send_time(send_time) {}
 
 PacketDroppingTestWriter::DelayedWrite::~DelayedWrite() = default;
diff --git a/quiche/quic/test_tools/packet_dropping_test_writer.h b/quiche/quic/test_tools/packet_dropping_test_writer.h
index a7e91d3..dd83cb3 100644
--- a/quiche/quic/test_tools/packet_dropping_test_writer.h
+++ b/quiche/quic/test_tools/packet_dropping_test_writer.h
@@ -49,7 +49,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override;
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override;
 
   bool IsWriteBlocked() const override;
 
@@ -142,7 +143,8 @@
     DelayedWrite(const char* buffer, size_t buf_len,
                  const QuicIpAddress& self_address,
                  const QuicSocketAddress& peer_address,
-                 std::unique_ptr<PerPacketOptions> options, QuicTime send_time);
+                 std::unique_ptr<PerPacketOptions> options,
+                 const QuicPacketWriterParams& params, QuicTime send_time);
     DelayedWrite(const DelayedWrite&) = delete;
     DelayedWrite(DelayedWrite&&) = default;
     DelayedWrite& operator=(const DelayedWrite&) = delete;
@@ -153,6 +155,7 @@
     QuicIpAddress self_address;
     QuicSocketAddress peer_address;
     std::unique_ptr<PerPacketOptions> options;
+    QuicPacketWriterParams params;
     QuicTime send_time;
   };
 
diff --git a/quiche/quic/test_tools/packet_reordering_writer.cc b/quiche/quic/test_tools/packet_reordering_writer.cc
index 8eb8573..12ff9b4 100644
--- a/quiche/quic/test_tools/packet_reordering_writer.cc
+++ b/quiche/quic/test_tools/packet_reordering_writer.cc
@@ -13,18 +13,19 @@
 
 WriteResult PacketReorderingWriter::WritePacket(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
-    const QuicSocketAddress& peer_address, PerPacketOptions* options) {
+    const QuicSocketAddress& peer_address, PerPacketOptions* options,
+    const QuicPacketWriterParams& params) {
   if (!delay_next_) {
     QUIC_VLOG(2) << "Writing a non-delayed packet";
     WriteResult wr = QuicPacketWriterWrapper::WritePacket(
-        buffer, buf_len, self_address, peer_address, options);
+        buffer, buf_len, self_address, peer_address, options, params);
     --num_packets_to_wait_;
     if (num_packets_to_wait_ == 0) {
       QUIC_VLOG(2) << "Writing a delayed packet";
       // It's time to write the delayed packet.
       QuicPacketWriterWrapper::WritePacket(
           delayed_data_.data(), delayed_data_.length(), delayed_self_address_,
-          delayed_peer_address_, delayed_options_.get());
+          delayed_peer_address_, delayed_options_.get(), delayed_params_);
     }
     return wr;
   }
@@ -37,6 +38,7 @@
   if (options != nullptr) {
     delayed_options_ = options->Clone();
   }
+  delayed_params_ = params;
   delay_next_ = false;
   return WriteResult(WRITE_STATUS_OK, buf_len);
 }
diff --git a/quiche/quic/test_tools/packet_reordering_writer.h b/quiche/quic/test_tools/packet_reordering_writer.h
index 53204c5..fb77c0d 100644
--- a/quiche/quic/test_tools/packet_reordering_writer.h
+++ b/quiche/quic/test_tools/packet_reordering_writer.h
@@ -5,6 +5,7 @@
 #ifndef QUICHE_QUIC_TEST_TOOLS_PACKET_REORDERING_WRITER_H_
 #define QUICHE_QUIC_TEST_TOOLS_PACKET_REORDERING_WRITER_H_
 
+#include "quiche/quic/core/quic_packet_writer.h"
 #include "quiche/quic/core/quic_packet_writer_wrapper.h"
 
 namespace quic {
@@ -25,7 +26,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override;
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override;
 
   void SetDelay(size_t num_packets_to_wait);
 
@@ -36,6 +38,7 @@
   QuicIpAddress delayed_self_address_;
   QuicSocketAddress delayed_peer_address_;
   std::unique_ptr<PerPacketOptions> delayed_options_;
+  QuicPacketWriterParams delayed_params_;
 };
 
 }  // namespace test
diff --git a/quiche/quic/test_tools/quic_test_utils.cc b/quiche/quic/test_tools/quic_test_utils.cc
index e622ed8..e04f0ca 100644
--- a/quiche/quic/test_tools/quic_test_utils.cc
+++ b/quiche/quic/test_tools/quic_test_utils.cc
@@ -1301,10 +1301,10 @@
   }
 }
 
-WriteResult TestPacketWriter::WritePacket(const char* buffer, size_t buf_len,
-                                          const QuicIpAddress& self_address,
-                                          const QuicSocketAddress& peer_address,
-                                          PerPacketOptions* options) {
+WriteResult TestPacketWriter::WritePacket(
+    const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
+    const QuicSocketAddress& peer_address, PerPacketOptions* /*options*/,
+    const QuicPacketWriterParams& params) {
   last_write_source_address_ = self_address;
   last_write_peer_address_ = peer_address;
   // If the buffer is allocated from the pool, return it back to the pool.
@@ -1377,7 +1377,7 @@
     bytes_buffered_ += last_packet_size_;
     return WriteResult(WRITE_STATUS_OK, 0);
   }
-  last_ecn_sent_ = (options == nullptr) ? ECN_NOT_ECT : options->ecn_codepoint;
+  last_ecn_sent_ = params.ecn_codepoint;
   return WriteResult(WRITE_STATUS_OK, last_packet_size_);
 }
 
diff --git a/quiche/quic/test_tools/quic_test_utils.h b/quiche/quic/test_tools/quic_test_utils.h
index 7b5667a..e368259 100644
--- a/quiche/quic/test_tools/quic_test_utils.h
+++ b/quiche/quic/test_tools/quic_test_utils.h
@@ -1185,7 +1185,8 @@
 
   MOCK_METHOD(WriteResult, WritePacket,
               (const char*, size_t buf_len, const QuicIpAddress& self_address,
-               const QuicSocketAddress& peer_address, PerPacketOptions*),
+               const QuicSocketAddress& peer_address, PerPacketOptions*,
+               const QuicPacketWriterParams&),
               (override));
   MOCK_METHOD(bool, IsWriteBlocked, (), (const, override));
   MOCK_METHOD(void, SetWritable, (), (override));
@@ -1832,7 +1833,8 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override;
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override;
 
   bool ShouldWriteFail() { return write_should_fail_; }
 
@@ -2177,13 +2179,6 @@
   return result;
 }
 
-struct TestPerPacketOptions : PerPacketOptions {
- public:
-  std::unique_ptr<quic::PerPacketOptions> Clone() const override {
-    return std::make_unique<TestPerPacketOptions>(*this);
-  }
-};
-
 }  // namespace test
 }  // namespace quic
 
diff --git a/quiche/quic/test_tools/simulator/quic_endpoint_base.cc b/quiche/quic/test_tools/simulator/quic_endpoint_base.cc
index d466c96..3ff517a 100644
--- a/quiche/quic/test_tools/simulator/quic_endpoint_base.cc
+++ b/quiche/quic/test_tools/simulator/quic_endpoint_base.cc
@@ -122,7 +122,8 @@
 
 WriteResult QuicEndpointBase::Writer::WritePacket(
     const char* buffer, size_t buf_len, const QuicIpAddress& /*self_address*/,
-    const QuicSocketAddress& /*peer_address*/, PerPacketOptions* options) {
+    const QuicSocketAddress& /*peer_address*/, PerPacketOptions* options,
+    const QuicPacketWriterParams& /*params*/) {
   QUICHE_DCHECK(!IsWriteBlocked());
   QUICHE_DCHECK(options == nullptr);
   QUICHE_DCHECK(buf_len <= kMaxOutgoingPacketSize);
diff --git a/quiche/quic/test_tools/simulator/quic_endpoint_base.h b/quiche/quic/test_tools/simulator/quic_endpoint_base.h
index 540b285..c734b81 100644
--- a/quiche/quic/test_tools/simulator/quic_endpoint_base.h
+++ b/quiche/quic/test_tools/simulator/quic_endpoint_base.h
@@ -79,7 +79,8 @@
     WriteResult WritePacket(const char* buffer, size_t buf_len,
                             const QuicIpAddress& self_address,
                             const QuicSocketAddress& peer_address,
-                            PerPacketOptions* options) override;
+                            PerPacketOptions* options,
+                            const QuicPacketWriterParams& params) override;
     bool IsWriteBlocked() const override;
     void SetWritable() override;
     absl::optional<int> MessageTooBigErrorCode() const override;
diff --git a/quiche/quic/tools/quic_client_default_network_helper.cc b/quiche/quic/tools/quic_client_default_network_helper.cc
index 424b8b9..9c1e4e5 100644
--- a/quiche/quic/tools/quic_client_default_network_helper.cc
+++ b/quiche/quic/tools/quic_client_default_network_helper.cc
@@ -30,9 +30,10 @@
   WriteResult WritePacket(const char* buffer, size_t buf_len,
                           const QuicIpAddress& self_address,
                           const QuicSocketAddress& peer_address,
-                          PerPacketOptions* options) override {
+                          PerPacketOptions* options,
+                          const QuicPacketWriterParams& params) override {
     WriteResult result = QuicDefaultPacketWriter::WritePacket(
-        buffer, buf_len, self_address, peer_address, options);
+        buffer, buf_len, self_address, peer_address, options, params);
     if (IsWriteBlockedStatus(result.status)) {
       bool success = event_loop_->RearmSocket(fd(), kSocketEventWritable);
       QUICHE_DCHECK(success);