Allow QUIC connections to send and receive IPv6 flow labels.

Protected by FLAGS_quic_reloadable_flag_quic_support_flow_label.

PiperOrigin-RevId: 685910584
diff --git a/quiche/quic/core/http/end_to_end_test.cc b/quiche/quic/core/http/end_to_end_test.cc
index 6ecfbd2..c3b99ae 100644
--- a/quiche/quic/core/http/end_to_end_test.cc
+++ b/quiche/quic/core/http/end_to_end_test.cc
@@ -7747,6 +7747,38 @@
   server_thread_->Resume();
 }
 
+TEST_P(EndToEndTest, FlowLabelSend) {
+  SetQuicReloadableFlag(quic_support_flow_label, true);
+  ASSERT_TRUE(Initialize());
+
+  const uint32_t server_flow_label = 2;
+  quiche::QuicheNotification set;
+  server_thread_->Schedule([this, &set]() {
+    QuicConnection* server_connection = GetServerConnection();
+    if (server_connection != nullptr) {
+      server_connection->set_outgoing_flow_label(server_flow_label);
+    } else {
+      ADD_FAILURE() << "Missing server connection";
+    }
+    set.Notify();
+  });
+  set.WaitForNotification();
+
+  const uint32_t client_flow_label = 1;
+  QuicConnection* client_connection = GetClientConnection();
+  client_connection->set_outgoing_flow_label(client_flow_label);
+
+  client_->SendSynchronousRequest("/foo");
+
+  EXPECT_EQ(client_flow_label, client_connection->outgoing_flow_label());
+  EXPECT_EQ(server_flow_label, client_connection->last_received_flow_label());
+
+  server_thread_->Pause();
+  QuicConnection* server_connection = GetServerConnection();
+  EXPECT_EQ(server_flow_label, server_connection->outgoing_flow_label());
+  EXPECT_EQ(client_flow_label, server_connection->last_received_flow_label());
+}
+
 TEST_P(EndToEndTest, ServerReportsNotEct) {
   // Client connects using not-ECT.
   SetQuicRestartFlag(quic_support_ect1, true);
diff --git a/quiche/quic/core/quic_coalesced_packet.cc b/quiche/quic/core/quic_coalesced_packet.cc
index 94180f8..b9cea82 100644
--- a/quiche/quic/core/quic_coalesced_packet.cc
+++ b/quiche/quic/core/quic_coalesced_packet.cc
@@ -14,7 +14,10 @@
 namespace quic {
 
 QuicCoalescedPacket::QuicCoalescedPacket()
-    : length_(0), max_packet_length_(0), ecn_codepoint_(ECN_NOT_ECT) {}
+    : length_(0),
+      max_packet_length_(0),
+      ecn_codepoint_(ECN_NOT_ECT),
+      flow_label_(0) {}
 
 QuicCoalescedPacket::~QuicCoalescedPacket() { Clear(); }
 
@@ -22,8 +25,8 @@
     const SerializedPacket& packet, const QuicSocketAddress& self_address,
     const QuicSocketAddress& peer_address,
     quiche::QuicheBufferAllocator* allocator,
-    QuicPacketLength current_max_packet_length,
-    QuicEcnCodepoint ecn_codepoint) {
+    QuicPacketLength current_max_packet_length, QuicEcnCodepoint ecn_codepoint,
+    uint32_t flow_label) {
   if (packet.encrypted_length == 0) {
     QUIC_BUG(quic_bug_10611_1) << "Trying to coalesce an empty packet";
     return true;
@@ -60,6 +63,10 @@
       // Do not coalesce packets with different ECN codepoints.
       return false;
     }
+    if (flow_label != flow_label_) {
+      // Do not coalesce packets with different flow labels
+      return false;
+    }
   }
 
   if (length_ + packet.encrypted_length > max_packet_length_) {
@@ -75,6 +82,7 @@
     QUIC_CODE_COUNT(QUIC_SUCCESSFULLY_COALESCED_MULTIPLE_PACKETS);
   }
   ecn_codepoint_ = ecn_codepoint;
+  flow_label_ = flow_label;
   length_ += packet.encrypted_length;
   transmission_types_[packet.encryption_level] = packet.transmission_type;
   if (packet.encryption_level == ENCRYPTION_INITIAL) {
diff --git a/quiche/quic/core/quic_coalesced_packet.h b/quiche/quic/core/quic_coalesced_packet.h
index 5f3a020..f209950 100644
--- a/quiche/quic/core/quic_coalesced_packet.h
+++ b/quiche/quic/core/quic_coalesced_packet.h
@@ -27,7 +27,7 @@
                            const QuicSocketAddress& peer_address,
                            quiche::QuicheBufferAllocator* allocator,
                            QuicPacketLength current_max_packet_length,
-                           QuicEcnCodepoint ecn_codepoint);
+                           QuicEcnCodepoint ecn_codepoint, uint32_t flow_label);
 
   // Clears this coalesced packet.
   void Clear();
@@ -69,6 +69,8 @@
 
   QuicEcnCodepoint ecn_codepoint() const { return ecn_codepoint_; }
 
+  uint32_t flow_label() const { return flow_label_; }
+
  private:
   friend class test::QuicCoalescedPacketPeer;
 
@@ -95,6 +97,9 @@
 
   // A coalesced packet shares an ECN codepoint.
   QuicEcnCodepoint ecn_codepoint_;
+
+  // A coalesced packet shares an single flow label.
+  uint32_t flow_label_;
 };
 
 }  // namespace quic
diff --git a/quiche/quic/core/quic_coalesced_packet_test.cc b/quiche/quic/core/quic_coalesced_packet_test.cc
index 61d30f7..82e1f49 100644
--- a/quiche/quic/core/quic_coalesced_packet_test.cc
+++ b/quiche/quic/core/quic_coalesced_packet_test.cc
@@ -33,7 +33,7 @@
   packet1.retransmittable_frames.push_back(
       QuicFrame(QuicStreamFrame(1, true, 0, 100)));
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet1, self_address, peer_address,
-                                            &allocator, 1500, ECN_NOT_ECT));
+                                            &allocator, 1500, ECN_NOT_ECT, 0));
   EXPECT_EQ(PTO_RETRANSMISSION,
             coalesced.TransmissionTypeOfPacket(ENCRYPTION_INITIAL));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
@@ -48,7 +48,7 @@
   SerializedPacket packet2(QuicPacketNumber(2), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 500, false, false);
   EXPECT_FALSE(coalesced.MaybeCoalescePacket(
-      packet2, self_address, peer_address, &allocator, 1500, ECN_NOT_ECT));
+      packet2, self_address, peer_address, &allocator, 1500, ECN_NOT_ECT, 0));
   EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 
   SerializedPacket packet3(QuicPacketNumber(3), PACKET_4BYTE_PACKET_NUMBER,
@@ -57,7 +57,7 @@
   packet3.encryption_level = ENCRYPTION_ZERO_RTT;
   packet3.transmission_type = LOSS_RETRANSMISSION;
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet3, self_address, peer_address,
-                                            &allocator, 1500, ECN_NOT_ECT));
+                                            &allocator, 1500, ECN_NOT_ECT, 0));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1000u, coalesced.length());
   EXPECT_EQ(2u, coalesced.NumberOfPackets());
@@ -75,14 +75,14 @@
   // Cannot coalesce packet of changed self/peer address.
   EXPECT_FALSE(coalesced.MaybeCoalescePacket(
       packet4, QuicSocketAddress(QuicIpAddress::Loopback4(), 3), peer_address,
-      &allocator, 1500, ECN_NOT_ECT));
+      &allocator, 1500, ECN_NOT_ECT, 0));
 
   // Packet does not fit.
   SerializedPacket packet5(QuicPacketNumber(5), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 501, false, false);
   packet5.encryption_level = ENCRYPTION_FORWARD_SECURE;
   EXPECT_FALSE(coalesced.MaybeCoalescePacket(
-      packet5, self_address, peer_address, &allocator, 1500, ECN_NOT_ECT));
+      packet5, self_address, peer_address, &allocator, 1500, ECN_NOT_ECT, 0));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1000u, coalesced.length());
   EXPECT_EQ(2u, coalesced.NumberOfPackets());
@@ -94,7 +94,7 @@
   packet6.encryption_level = ENCRYPTION_FORWARD_SECURE;
   EXPECT_QUIC_BUG(
       coalesced.MaybeCoalescePacket(packet6, self_address, peer_address,
-                                    &allocator, 1000, ECN_NOT_ECT),
+                                    &allocator, 1000, ECN_NOT_ECT, 0),
       "Max packet length changes in the middle of the write path");
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1000u, coalesced.length());
@@ -119,9 +119,9 @@
   packet2.encryption_level = ENCRYPTION_FORWARD_SECURE;
 
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet1, self_address, peer_address,
-                                            &allocator, 1500, ECN_NOT_ECT));
+                                            &allocator, 1500, ECN_NOT_ECT, 0));
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet2, self_address, peer_address,
-                                            &allocator, 1500, ECN_NOT_ECT));
+                                            &allocator, 1500, ECN_NOT_ECT, 0));
   EXPECT_EQ(1000u, coalesced.length());
   EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
 
@@ -161,7 +161,7 @@
   packet1.retransmittable_frames.push_back(
       QuicFrame(QuicStreamFrame(1, true, 0, 100)));
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet1, self_address, peer_address,
-                                            &allocator, 1500, ECN_NOT_ECT));
+                                            &allocator, 1500, ECN_NOT_ECT, 0));
   EXPECT_EQ(PTO_RETRANSMISSION,
             coalesced.TransmissionTypeOfPacket(ENCRYPTION_INITIAL));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
@@ -179,7 +179,7 @@
 
   // Coalesce initial packet again.
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet1, self_address, peer_address,
-                                            &allocator, 1500, ECN_NOT_ECT));
+                                            &allocator, 1500, ECN_NOT_ECT, 0));
 
   SerializedPacket packet2(QuicPacketNumber(3), PACKET_4BYTE_PACKET_NUMBER,
                            buffer, 500, false, false);
@@ -187,7 +187,7 @@
   packet2.encryption_level = ENCRYPTION_ZERO_RTT;
   packet2.transmission_type = LOSS_RETRANSMISSION;
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet2, self_address, peer_address,
-                                            &allocator, 1500, ECN_NOT_ECT));
+                                            &allocator, 1500, ECN_NOT_ECT, 0));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1000u, coalesced.length());
   EXPECT_EQ(LOSS_RETRANSMISSION,
@@ -211,7 +211,7 @@
                            buffer, 501, false, false);
   packet3.encryption_level = ENCRYPTION_FORWARD_SECURE;
   EXPECT_TRUE(coalesced.MaybeCoalescePacket(packet3, self_address, peer_address,
-                                            &allocator, 1500, ECN_NOT_ECT));
+                                            &allocator, 1500, ECN_NOT_ECT, 0));
   EXPECT_EQ(1500u, coalesced.max_packet_length());
   EXPECT_EQ(1001u, coalesced.length());
   EXPECT_EQ(coalesced.ecn_codepoint(), ECN_NOT_ECT);
@@ -240,7 +240,7 @@
   packet1.retransmittable_frames.push_back(
       QuicFrame(QuicStreamFrame(1, true, 0, 100)));
   ASSERT_TRUE(coalesced.MaybeCoalescePacket(packet1, self_address, peer_address,
-                                            &allocator, 1500, ECN_ECT1));
+                                            &allocator, 1500, ECN_ECT1, 0));
   EXPECT_EQ(coalesced.ecn_codepoint(), ECN_ECT1);
 
   SerializedPacket packet2(QuicPacketNumber(2), PACKET_4BYTE_PACKET_NUMBER,
@@ -249,7 +249,7 @@
   packet2.encryption_level = ENCRYPTION_ZERO_RTT;
   packet2.transmission_type = LOSS_RETRANSMISSION;
   EXPECT_FALSE(coalesced.MaybeCoalescePacket(
-      packet2, self_address, peer_address, &allocator, 1500, ECN_NOT_ECT));
+      packet2, self_address, peer_address, &allocator, 1500, ECN_NOT_ECT, 0));
   EXPECT_EQ(coalesced.ecn_codepoint(), ECN_ECT1);
 }
 
diff --git a/quiche/quic/core/quic_connection.cc b/quiche/quic/core/quic_connection.cc
index f069d85..9be867e 100644
--- a/quiche/quic/core/quic_connection.cc
+++ b/quiche/quic/core/quic_connection.cc
@@ -2623,9 +2623,9 @@
   if (debug_visitor_ != nullptr) {
     debug_visitor_->OnPacketReceived(self_address, peer_address, packet);
   }
-  last_received_packet_info_ =
-      ReceivedPacketInfo(self_address, peer_address, packet.receipt_time(),
-                         packet.length(), packet.ecn_codepoint());
+  last_received_packet_info_ = ReceivedPacketInfo(
+      self_address, peer_address, packet.receipt_time(), packet.length(),
+      packet.ecn_codepoint(), packet.ipv6_flow_label());
   current_packet_data_ = packet.data();
 
   if (!default_path_.self_address.IsInitialized()) {
@@ -3053,7 +3053,7 @@
     const BufferedPacket& packet = buffered_packets_.front();
     WriteResult result = SendPacketToWriter(
         packet.data.get(), packet.length, packet.self_address.host(),
-        packet.peer_address, writer_, packet.ecn_codepoint);
+        packet.peer_address, writer_, packet.ecn_codepoint, packet.flow_label);
     QUIC_DVLOG(1) << ENDPOINT << "Sending buffered packet, result: " << result;
     if (IsMsgTooBig(writer_, result) && packet.length > long_term_mtu_) {
       // When MSG_TOO_BIG is returned, the system typically knows what the
@@ -3396,7 +3396,7 @@
               *packet, send_from_address, send_to_address,
               helper_->GetStreamSendBufferAllocator(),
               packet_creator_.max_packet_length(),
-              GetEcnCodepointToSend(send_to_address))) {
+              GetEcnCodepointToSend(send_to_address), outgoing_flow_label())) {
         // Failed to coalesce packet, flush current coalesced packet.
         if (!FlushCoalescedPacket()) {
           QUIC_BUG_IF(quic_connection_connected_after_flush_coalesced_failure,
@@ -3410,7 +3410,8 @@
                 *packet, send_from_address, send_to_address,
                 helper_->GetStreamSendBufferAllocator(),
                 packet_creator_.max_packet_length(),
-                GetEcnCodepointToSend(send_to_address))) {
+                GetEcnCodepointToSend(send_to_address),
+                outgoing_flow_label())) {
           // Failed to coalesce packet even it is the only packet, raise a write
           // error.
           QUIC_DLOG(ERROR) << ENDPOINT << "Failed to coalesce packet";
@@ -3432,7 +3433,8 @@
                     << " to buffered packets";
       last_ecn_codepoint_sent_ = GetEcnCodepointToSend(send_to_address);
       buffered_packets_.emplace_back(*packet, send_from_address,
-                                     send_to_address, last_ecn_codepoint_sent_);
+                                     send_to_address, last_ecn_codepoint_sent_,
+                                     last_flow_label_sent_);
       break;
     case SEND_TO_WRITER:
       // Stop using coalescer from now on.
@@ -3447,7 +3449,8 @@
       packet->release_encrypted_buffer = nullptr;
       result = SendPacketToWriter(
           packet->encrypted_buffer, encrypted_length, send_from_address.host(),
-          send_to_address, writer_, GetEcnCodepointToSend(send_to_address));
+          send_to_address, writer_, GetEcnCodepointToSend(send_to_address),
+          outgoing_flow_label());
       // This is a work around for an issue with linux UDP GSO batch writers.
       // When sending a GSO packet with 2 segments, if the first segment is
       // larger than the path MTU, instead of EMSGSIZE, the linux kernel returns
@@ -3482,7 +3485,8 @@
       QUIC_DVLOG(1) << ENDPOINT << "Adding packet: " << packet->packet_number
                     << " to buffered packets";
       buffered_packets_.emplace_back(*packet, send_from_address,
-                                     send_to_address, last_ecn_codepoint_sent_);
+                                     send_to_address, last_ecn_codepoint_sent_,
+                                     last_flow_label_sent_);
     }
   }
 
@@ -3898,7 +3902,7 @@
       serialized_packet.encryption_level == ENCRYPTION_FORWARD_SECURE) {
     first_serialized_one_rtt_packet_ = std::make_unique<BufferedPacket>(
         serialized_packet, self_address(), peer_address(),
-        GetEcnCodepointToSend(peer_address()));
+        GetEcnCodepointToSend(peer_address()), outgoing_flow_label());
   }
   SendOrQueuePacket(std::move(serialized_packet));
 }
@@ -4132,10 +4136,12 @@
 WriteResult QuicConnection::SendPacketToWriter(
     const char* buffer, size_t buf_len, const QuicIpAddress& self_address,
     const QuicSocketAddress& destination_address, QuicPacketWriter* writer,
-    const QuicEcnCodepoint ecn_codepoint) {
+    const QuicEcnCodepoint ecn_codepoint, uint32_t flow_label) {
   QuicPacketWriterParams params = packet_writer_params_;
   params.ecn_codepoint = ecn_codepoint;
   last_ecn_codepoint_sent_ = ecn_codepoint;
+  last_flow_label_sent_ = flow_label;
+  params.flow_label = flow_label;
   WriteResult result =
       writer->WritePacket(buffer, buf_len, self_address, destination_address,
                           per_packet_options_, params);
@@ -4898,18 +4904,21 @@
 
 QuicConnection::BufferedPacket::BufferedPacket(
     const SerializedPacket& packet, const QuicSocketAddress& self_address,
-    const QuicSocketAddress& peer_address, const QuicEcnCodepoint ecn_codepoint)
+    const QuicSocketAddress& peer_address, const QuicEcnCodepoint ecn_codepoint,
+    uint32_t flow_label)
     : BufferedPacket(packet.encrypted_buffer, packet.encrypted_length,
-                     self_address, peer_address, ecn_codepoint) {}
+                     self_address, peer_address, ecn_codepoint, flow_label) {}
 
 QuicConnection::BufferedPacket::BufferedPacket(
     const char* encrypted_buffer, QuicPacketLength encrypted_length,
     const QuicSocketAddress& self_address,
-    const QuicSocketAddress& peer_address, const QuicEcnCodepoint ecn_codepoint)
+    const QuicSocketAddress& peer_address, const QuicEcnCodepoint ecn_codepoint,
+    uint32_t flow_label)
     : length(encrypted_length),
       self_address(self_address),
       peer_address(peer_address),
-      ecn_codepoint(ecn_codepoint) {
+      ecn_codepoint(ecn_codepoint),
+      flow_label(flow_label) {
   data = std::make_unique<char[]>(encrypted_length);
   memcpy(data.get(), encrypted_buffer, encrypted_length);
 }
@@ -4927,15 +4936,17 @@
 
 QuicConnection::ReceivedPacketInfo::ReceivedPacketInfo(QuicTime receipt_time)
     : receipt_time(receipt_time) {}
+
 QuicConnection::ReceivedPacketInfo::ReceivedPacketInfo(
     const QuicSocketAddress& destination_address,
     const QuicSocketAddress& source_address, QuicTime receipt_time,
-    QuicByteCount length, QuicEcnCodepoint ecn_codepoint)
+    QuicByteCount length, QuicEcnCodepoint ecn_codepoint, uint32_t flow_label)
     : destination_address(destination_address),
       source_address(source_address),
       receipt_time(receipt_time),
       length(length),
-      ecn_codepoint(ecn_codepoint) {}
+      ecn_codepoint(ecn_codepoint),
+      flow_label(flow_label) {}
 
 std::ostream& operator<<(std::ostream& os,
                          const QuicConnection::ReceivedPacketInfo& info) {
@@ -5089,7 +5100,8 @@
                        packet->encrypted_buffer, packet->encrypted_length));
   WriteResult result = SendPacketToWriter(
       packet->encrypted_buffer, packet->encrypted_length, self_address.host(),
-      peer_address, writer, GetEcnCodepointToSend(peer_address));
+      peer_address, writer, GetEcnCodepointToSend(peer_address),
+      outgoing_flow_label());
 
   const uint32_t writer_batch_id = result.batch_id;
 
@@ -5986,12 +5998,12 @@
     buffered_packets_.emplace_back(
         buffer, static_cast<QuicPacketLength>(length),
         coalesced_packet_.self_address(), coalesced_packet_.peer_address(),
-        coalesced_packet_.ecn_codepoint());
+        coalesced_packet_.ecn_codepoint(), coalesced_packet_.flow_label());
   } else {
     WriteResult result = SendPacketToWriter(
         buffer, length, coalesced_packet_.self_address().host(),
         coalesced_packet_.peer_address(), writer_,
-        coalesced_packet_.ecn_codepoint());
+        coalesced_packet_.ecn_codepoint(), coalesced_packet_.flow_label());
     if (IsWriteError(result.status)) {
       OnWriteError(result.error_code);
       return false;
@@ -6004,7 +6016,7 @@
         buffered_packets_.emplace_back(
             buffer, static_cast<QuicPacketLength>(length),
             coalesced_packet_.self_address(), coalesced_packet_.peer_address(),
-            coalesced_packet_.ecn_codepoint());
+            coalesced_packet_.ecn_codepoint(), coalesced_packet_.flow_label());
       }
     }
   }
@@ -6363,7 +6375,8 @@
         buffered_packets_.emplace_back(
             first_serialized_one_rtt_packet_->data.get(),
             first_serialized_one_rtt_packet_->length, self_address(),
-            peer_address(), first_serialized_one_rtt_packet_->ecn_codepoint);
+            peer_address(), first_serialized_one_rtt_packet_->ecn_codepoint,
+            first_serialized_one_rtt_packet_->flow_label);
         packet_buffered = true;
       }
       break;
@@ -7391,6 +7404,11 @@
       << context.peer_address() << " after successful validation";
 }
 
+void QuicConnection::set_outgoing_flow_label(uint32_t flow_label) {
+  QUICHE_DCHECK(!packet_creator_.HasPendingFrames());
+  outgoing_flow_label_ = flow_label;
+}
+
 bool QuicConnection::set_ecn_codepoint(QuicEcnCodepoint ecn_codepoint) {
   if (!GetQuicRestartFlag(quic_support_ect1)) {
     return false;
diff --git a/quiche/quic/core/quic_connection.h b/quiche/quic/core/quic_connection.h
index 93b9689..61d783d 100644
--- a/quiche/quic/core/quic_connection.h
+++ b/quiche/quic/core/quic_connection.h
@@ -1436,6 +1436,18 @@
     return quic_limit_new_streams_per_loop_2_;
   }
 
+  void set_outgoing_flow_label(uint32_t flow_label);
+
+  // Returns the flow label used for outgoing IPv6 packets, or 0 if no
+  // flow label will be sent.
+  uint32_t outgoing_flow_label() const { return outgoing_flow_label_; }
+
+  // Returns the flow label received on the most recent packet, or 0 if no
+  // flow label was received.
+  uint32_t last_received_flow_label() const {
+    return last_received_packet_info_.flow_label;
+  }
+
   void OnDiscardZeroRttDecryptionKeysAlarm() override;
   void OnIdleDetectorAlarm() override;
   void OnNetworkBlackholeDetectorAlarm() override;
@@ -1631,12 +1643,12 @@
     BufferedPacket(const SerializedPacket& packet,
                    const QuicSocketAddress& self_address,
                    const QuicSocketAddress& peer_address,
-                   QuicEcnCodepoint ecn_codepoint);
+                   QuicEcnCodepoint ecn_codepoint, uint32_t flow_label);
     BufferedPacket(const char* encrypted_buffer,
                    QuicPacketLength encrypted_length,
                    const QuicSocketAddress& self_address,
                    const QuicSocketAddress& peer_address,
-                   QuicEcnCodepoint ecn_codepoint);
+                   QuicEcnCodepoint ecn_codepoint, uint32_t flow_label);
     // Please note, this buffered packet contains random bytes (and is not
     // *actually* a QUIC packet).
     BufferedPacket(QuicRandom& random, QuicPacketLength encrypted_length,
@@ -1653,6 +1665,7 @@
     const QuicSocketAddress self_address;
     const QuicSocketAddress peer_address;
     QuicEcnCodepoint ecn_codepoint = ECN_NOT_ECT;
+    uint32_t flow_label = 0;
   };
 
   // ReceivedPacketInfo comprises the received packet information.
@@ -1662,7 +1675,7 @@
     ReceivedPacketInfo(const QuicSocketAddress& destination_address,
                        const QuicSocketAddress& source_address,
                        QuicTime receipt_time, QuicByteCount length,
-                       QuicEcnCodepoint ecn_codepoint);
+                       QuicEcnCodepoint ecn_codepoint, uint32_t flow_label);
 
     QuicSocketAddress destination_address;
     QuicSocketAddress source_address;
@@ -1677,6 +1690,7 @@
     QuicPacketHeader header;
     absl::InlinedVector<QuicFrameType, 1> frames;
     QuicEcnCodepoint ecn_codepoint = ECN_NOT_ECT;
+    uint32_t flow_label = 0;
     // Stores the actual address this packet is received on when it is received
     // on the preferred address. In this case, |destination_address| will
     // be overridden to the current default self address.
@@ -2122,7 +2136,8 @@
                                  const QuicIpAddress& self_address,
                                  const QuicSocketAddress& destination_address,
                                  QuicPacketWriter* writer,
-                                 const QuicEcnCodepoint ecn_codepoint);
+                                 const QuicEcnCodepoint ecn_codepoint,
+                                 uint32_t flow_label);
 
   bool PeerAddressChanged() const;
 
@@ -2552,6 +2567,11 @@
   // The ECN codepoint of the last packet to be sent to the writer, which
   // might be different from the next codepoint in per_packet_options_.
   QuicEcnCodepoint last_ecn_codepoint_sent_ = ECN_NOT_ECT;
+  // The flow label of the last packet to be sent to the writer, which
+  // might be different from the next flow label in per_packet_options_.
+  uint32_t last_flow_label_sent_ = 0;
+  // The flow label to be sent for outgoing packets.
+  uint32_t outgoing_flow_label_ = 0;
 
   // If true, the peer has indicated that it supports the RESET_STREAM_AT frame.
   bool reliable_stream_reset_ = false;
diff --git a/quiche/quic/core/quic_default_packet_writer.cc b/quiche/quic/core/quic_default_packet_writer.cc
index 5b4bf27..8427726 100644
--- a/quiche/quic/core/quic_default_packet_writer.cc
+++ b/quiche/quic/core/quic_default_packet_writer.cc
@@ -24,6 +24,9 @@
   packet_info.SetPeerAddress(peer_address);
   packet_info.SetSelfIp(self_address);
   packet_info.SetEcnCodepoint(params.ecn_codepoint);
+  if (GetQuicReloadableFlag(quic_support_flow_label)) {
+    packet_info.SetFlowLabel(params.flow_label);
+  }
   WriteResult result =
       QuicUdpSocketApi().WritePacket(fd_, buffer, buf_len, packet_info);
   if (IsWriteBlockedStatus(result.status)) {
diff --git a/quiche/quic/core/quic_packet_creator_test.cc b/quiche/quic/core/quic_packet_creator_test.cc
index 0575b88..849fcca 100644
--- a/quiche/quic/core/quic_packet_creator_test.cc
+++ b/quiche/quic/core/quic_packet_creator_test.cc
@@ -2324,7 +2324,7 @@
     frames_.clear();
     ASSERT_TRUE(coalesced.MaybeCoalescePacket(
         serialized, self_address, peer_address, &allocator,
-        creator_.max_packet_length(), ECN_NOT_ECT));
+        creator_.max_packet_length(), ECN_NOT_ECT, 0));
   }
   char buffer[kMaxOutgoingPacketSize];
   size_t coalesced_length = creator_.SerializeCoalescedPacket(
diff --git a/quiche/quic/core/quic_packet_reader.cc b/quiche/quic/core/quic_packet_reader.cc
index 86062fd..4cf19a6 100644
--- a/quiche/quic/core/quic_packet_reader.cc
+++ b/quiche/quic/core/quic_packet_reader.cc
@@ -55,6 +55,9 @@
        QuicUdpPacketInfoBit::V6_SELF_IP, QuicUdpPacketInfoBit::RECV_TIMESTAMP,
        QuicUdpPacketInfoBit::TTL, QuicUdpPacketInfoBit::GOOGLE_PACKET_HEADER,
        QuicUdpPacketInfoBit::ECN});
+  if (GetQuicReloadableFlag(quic_support_flow_label)) {
+    info_bits.Set(QuicUdpPacketInfoBit::V6_FLOW_LABEL);
+  }
   size_t packets_read =
       socket_api_.ReadMultiplePackets(fd, info_bits, &read_results_);
   for (size_t i = 0; i < packets_read; ++i) {
@@ -94,11 +97,16 @@
     } else {
       QUIC_CODE_COUNT(quic_packet_reader_no_google_packet_header);
     }
+    uint32_t flow_label = 0;
+    if (result.packet_info.HasValue(QuicUdpPacketInfoBit::V6_FLOW_LABEL)) {
+      flow_label = result.packet_info.flow_label();
+    }
 
     QuicReceivedPacket packet(
         result.packet_buffer.buffer, result.packet_buffer.buffer_len, now,
         /*owns_buffer=*/false, ttl, has_ttl, headers, headers_length,
-        /*owns_header_buffer=*/false, result.packet_info.ecn_codepoint());
+        /*owns_header_buffer=*/false, result.packet_info.ecn_codepoint(),
+        flow_label);
     QuicSocketAddress self_address(self_ip, port);
     processor->ProcessPacket(self_address, peer_address, packet);
   }
diff --git a/quiche/quic/core/quic_packets.cc b/quiche/quic/core/quic_packets.cc
index 26c59f0..40ec9a2 100644
--- a/quiche/quic/core/quic_packets.cc
+++ b/quiche/quic/core/quic_packets.cc
@@ -355,13 +355,24 @@
     const char* buffer, size_t length, QuicTime receipt_time, bool owns_buffer,
     int ttl, bool ttl_valid, char* packet_headers, size_t headers_length,
     bool owns_header_buffer, QuicEcnCodepoint ecn_codepoint)
+    : quic::QuicReceivedPacket(buffer, length, receipt_time, owns_buffer, ttl,
+                               ttl_valid, packet_headers, headers_length,
+                               owns_header_buffer, ecn_codepoint,
+                               /*ipv6_flow_label=*/0) {}
+
+QuicReceivedPacket::QuicReceivedPacket(
+    const char* buffer, size_t length, QuicTime receipt_time, bool owns_buffer,
+    int ttl, bool ttl_valid, char* packet_headers, size_t headers_length,
+    bool owns_header_buffer, QuicEcnCodepoint ecn_codepoint,
+    uint32_t ipv6_flow_label)
     : QuicEncryptedPacket(buffer, length, owns_buffer),
       receipt_time_(receipt_time),
       ttl_(ttl_valid ? ttl : -1),
       packet_headers_(packet_headers),
       headers_length_(headers_length),
       owns_header_buffer_(owns_header_buffer),
-      ecn_codepoint_(ecn_codepoint) {}
+      ecn_codepoint_(ecn_codepoint),
+      ipv6_flow_label_(ipv6_flow_label) {}
 
 QuicReceivedPacket::~QuicReceivedPacket() {
   if (owns_header_buffer_) {
diff --git a/quiche/quic/core/quic_packets.h b/quiche/quic/core/quic_packets.h
index 2a26a7b..605ed4c 100644
--- a/quiche/quic/core/quic_packets.h
+++ b/quiche/quic/core/quic_packets.h
@@ -301,6 +301,11 @@
                      bool owns_buffer, int ttl, bool ttl_valid,
                      char* packet_headers, size_t headers_length,
                      bool owns_header_buffer, QuicEcnCodepoint ecn_codepoint);
+  QuicReceivedPacket(const char* buffer, size_t length, QuicTime receipt_time,
+                     bool owns_buffer, int ttl, bool ttl_valid,
+                     char* packet_headers, size_t headers_length,
+                     bool owns_header_buffer, QuicEcnCodepoint ecn_codepoint,
+                     uint32_t ipv6_flow_label);
   ~QuicReceivedPacket();
   QuicReceivedPacket(const QuicReceivedPacket&) = delete;
   QuicReceivedPacket& operator=(const QuicReceivedPacket&) = delete;
@@ -320,6 +325,11 @@
   // Length of packet headers.
   int headers_length() const { return headers_length_; }
 
+  QuicEcnCodepoint ecn_codepoint() const { return ecn_codepoint_; }
+
+  // Returns the IPv6 flow label in host byte order if present, or 0 otherwise.
+  uint32_t ipv6_flow_label() const { return ipv6_flow_label_; }
+
   // By default, gtest prints the raw bytes of an object. The bool data
   // member (in the base class QuicData) causes this object to have padding
   // bytes, which causes the default gtest object printer to read
@@ -327,8 +337,6 @@
   QUICHE_EXPORT friend std::ostream& operator<<(std::ostream& os,
                                                 const QuicReceivedPacket& s);
 
-  QuicEcnCodepoint ecn_codepoint() const { return ecn_codepoint_; }
-
  private:
   friend class test::QuicReceivedPacketPeer;
 
@@ -341,6 +349,8 @@
   // Whether owns the buffer for packet headers.
   bool owns_header_buffer_;
   QuicEcnCodepoint ecn_codepoint_;
+  // IPv6 flow label.
+  uint32_t ipv6_flow_label_;
 };
 
 // SerializedPacket contains information of a serialized(encrypted) packet.
diff --git a/quiche/quic/core/quic_packets_test.cc b/quiche/quic/core/quic_packets_test.cc
index 4b64be2..3c77628 100644
--- a/quiche/quic/core/quic_packets_test.cc
+++ b/quiche/quic/core/quic_packets_test.cc
@@ -130,6 +130,22 @@
   EXPECT_EQ(packet.ecn_codepoint(), copy->ecn_codepoint());
 }
 
+TEST_F(QuicPacketsTest, NoFlowLabelByDefault) {
+  char header[4] = "bar";
+  QuicReceivedPacket packet("foo", 3, QuicTime::Zero(), false, 0, true, header,
+                            sizeof(header) - 1, false,
+                            QuicEcnCodepoint::ECN_ECT1);
+  EXPECT_EQ(0, packet.ipv6_flow_label());
+}
+
+TEST_F(QuicPacketsTest, ExplicitFlowLabel) {
+  char header[4] = "bar";
+  QuicReceivedPacket packet("foo", 3, QuicTime::Zero(), false, 0, true, header,
+                            sizeof(header) - 1, false,
+                            QuicEcnCodepoint::ECN_ECT1, 42);
+  EXPECT_EQ(42, packet.ipv6_flow_label());
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic