Replace legacy Google-specific parameter with individual parameters Now that we've removed the majority of Google-specific parameters, we can now move the remaining 3 to their own transport parameter. This'll remove the dependency from QUIC+TLS on QUIC_CRYPTO handshake message code, and simplify debugging. Tweak quic+tls transport parameters, protected by gfe2_restart_flag_quic_google_transport_param_send_new and gfe2_restart_flag_quic_google_transport_param_omit_old PiperOrigin-RevId: 311438446 Change-Id: I95a61b9acf6523d845600fe70c2f752d5e30f28a
diff --git a/quic/core/crypto/transport_parameters.cc b/quic/core/crypto/transport_parameters.cc index 4e761b3..3e286ad 100644 --- a/quic/core/crypto/transport_parameters.cc +++ b/quic/core/crypto/transport_parameters.cc
@@ -49,6 +49,9 @@ kMaxDatagramFrameSize = 0x20, + kInitialRoundTripTime = 0x3127, + kGoogleConnectionOptions = 0x3128, + kGoogleUserAgentId = 0x3129, kGoogleQuicParam = 18257, // Used for non-standard Google-specific params. kGoogleQuicVersion = 18258, // Used to transmit version and supported_versions. @@ -103,6 +106,12 @@ return "active_connection_id_limit"; case TransportParameters::kMaxDatagramFrameSize: return "max_datagram_frame_size"; + case TransportParameters::kInitialRoundTripTime: + return "initial_round_trip_time"; + case TransportParameters::kGoogleConnectionOptions: + return "google_connection_options"; + case TransportParameters::kGoogleUserAgentId: + return "user_agent_id"; case TransportParameters::kGoogleQuicParam: return "google"; case TransportParameters::kGoogleQuicVersion: @@ -130,6 +139,9 @@ case TransportParameters::kPreferredAddress: case TransportParameters::kActiveConnectionIdLimit: case TransportParameters::kMaxDatagramFrameSize: + case TransportParameters::kInitialRoundTripTime: + case TransportParameters::kGoogleConnectionOptions: + case TransportParameters::kGoogleUserAgentId: case TransportParameters::kGoogleQuicParam: case TransportParameters::kGoogleQuicVersion: return true; @@ -418,14 +430,31 @@ } rv += active_connection_id_limit.ToString(/*for_use_in_list=*/true); rv += max_datagram_frame_size.ToString(/*for_use_in_list=*/true); + rv += initial_round_trip_time_us.ToString(/*for_use_in_list=*/true); + if (google_connection_options.has_value()) { + rv += " " + TransportParameterIdToString(kGoogleConnectionOptions) + " "; + bool first = true; + for (const QuicTag& connection_option : google_connection_options.value()) { + if (first) { + first = false; + } else { + rv += ","; + } + rv += QuicTagToString(connection_option); + } + } + if (user_agent_id.has_value()) { + rv += " " + TransportParameterIdToString(kGoogleUserAgentId) + " \"" + + user_agent_id.value() + "\""; + } if (google_quic_params) { rv += " " + TransportParameterIdToString(kGoogleQuicParam); } - rv += "]"; for (const auto& kv : custom_parameters) { rv += " 0x" + quiche::QuicheTextUtils::Hex(static_cast<uint32_t>(kv.first)); rv += "=" + quiche::QuicheTextUtils::HexEncode(kv.second); } + rv += "]"; return rv; } @@ -455,7 +484,8 @@ kDefaultActiveConnectionIdLimitTransportParam, kMinActiveConnectionIdLimitTransportParam, kVarInt62MaxValue), - max_datagram_frame_size(kMaxDatagramFrameSize) + max_datagram_frame_size(kMaxDatagramFrameSize), + initial_round_trip_time_us(kInitialRoundTripTime) // Important note: any new transport parameters must be added // to TransportParameters::AreValid, SerializeTransportParameters and // ParseTransportParameters, TransportParameters's custom copy constructor, the @@ -483,6 +513,9 @@ disable_migration(other.disable_migration), active_connection_id_limit(other.active_connection_id_limit), max_datagram_frame_size(other.max_datagram_frame_size), + initial_round_trip_time_us(other.initial_round_trip_time_us), + google_connection_options(other.google_connection_options), + user_agent_id(other.user_agent_id), custom_parameters(other.custom_parameters) { if (other.preferred_address) { preferred_address = std::make_unique<TransportParameters::PreferredAddress>( @@ -520,6 +553,10 @@ rhs.active_connection_id_limit.value() && max_datagram_frame_size.value() == rhs.max_datagram_frame_size.value() && + initial_round_trip_time_us.value() == + rhs.initial_round_trip_time_us.value() && + google_connection_options == rhs.google_connection_options && + user_agent_id == rhs.user_agent_id && custom_parameters == rhs.custom_parameters)) { return false; } @@ -590,6 +627,15 @@ return false; } } + if (perspective == Perspective::IS_SERVER && + initial_round_trip_time_us.value() > 0) { + *error_details = "Server cannot send initial round trip time"; + return false; + } + if (perspective == Perspective::IS_SERVER && user_agent_id.has_value()) { + *error_details = "Server cannot send user agent ID"; + return false; + } const bool ok = idle_timeout_milliseconds.IsValid() && max_packet_size.IsValid() && initial_max_data.IsValid() && @@ -598,7 +644,8 @@ initial_max_stream_data_uni.IsValid() && initial_max_streams_bidi.IsValid() && initial_max_streams_uni.IsValid() && ack_delay_exponent.IsValid() && max_ack_delay.IsValid() && - active_connection_id_limit.IsValid() && max_datagram_frame_size.IsValid(); + active_connection_id_limit.IsValid() && + max_datagram_frame_size.IsValid() && initial_round_trip_time_us.IsValid(); if (!ok) { *error_details = "Invalid transport parameters " + this->ToString(); } @@ -691,7 +738,8 @@ !in.ack_delay_exponent.Write(&writer, version) || !in.max_ack_delay.Write(&writer, version) || !in.active_connection_id_limit.Write(&writer, version) || - !in.max_datagram_frame_size.Write(&writer, version)) { + !in.max_datagram_frame_size.Write(&writer, version) || + !in.initial_round_trip_time_us.Write(&writer, version)) { QUIC_BUG << "Failed to write integers for " << in; return false; } @@ -743,6 +791,42 @@ } } + // Google-specific connection options. + if (in.google_connection_options.has_value()) { + static_assert(sizeof(in.google_connection_options.value().front()) == 4, + "bad size"); + uint64_t connection_options_length = + in.google_connection_options.value().size() * 4; + if (!WriteTransportParameterId( + &writer, TransportParameters::kGoogleConnectionOptions, version) || + !WriteTransportParameterLength(&writer, connection_options_length, + version)) { + QUIC_BUG << "Failed to write google_connection_options of length " + << connection_options_length << " for " << in; + return false; + } + for (const QuicTag& connection_option : + in.google_connection_options.value()) { + if (!writer.WriteTag(connection_option)) { + QUIC_BUG << "Failed to write google_connection_option " + << QuicTagToString(connection_option) << " for " << in; + return false; + } + } + } + + // Google-specific user agent identifier. + if (in.user_agent_id.has_value()) { + if (!WriteTransportParameterId( + &writer, TransportParameters::kGoogleUserAgentId, version) || + !WriteTransportParameterStringPiece(&writer, in.user_agent_id.value(), + version)) { + QUIC_BUG << "Failed to write Google user agent ID \"" + << in.user_agent_id.value() << "\" for " << in; + return false; + } + } + // Google-specific non-standard parameter. if (in.google_quic_params) { const QuicData& serialized_google_quic_params = @@ -862,6 +946,7 @@ QUIC_DLOG(INFO) << "Serialized " << in << " as " << writer.length() << " bytes"; + return true; } @@ -1033,6 +1118,32 @@ parse_success = out->max_datagram_frame_size.Read(&value_reader, error_details); break; + case TransportParameters::kInitialRoundTripTime: + parse_success = + out->initial_round_trip_time_us.Read(&value_reader, error_details); + break; + case TransportParameters::kGoogleConnectionOptions: { + if (out->google_connection_options.has_value()) { + *error_details = "Received a second Google connection options"; + return false; + } + out->google_connection_options = QuicTagVector{}; + while (!value_reader.IsDoneReading()) { + QuicTag connection_option; + if (!value_reader.ReadTag(&connection_option)) { + *error_details = "Failed to read a Google connection option"; + return false; + } + out->google_connection_options.value().push_back(connection_option); + } + } break; + case TransportParameters::kGoogleUserAgentId: + if (out->user_agent_id.has_value()) { + *error_details = "Received a second user agent ID"; + return false; + } + out->user_agent_id = std::string(value_reader.ReadRemainingPayload()); + break; case TransportParameters::kGoogleQuicParam: { if (out->google_quic_params) { *error_details = "Received a second Google parameter";
diff --git a/quic/core/crypto/transport_parameters.h b/quic/core/crypto/transport_parameters.h index 9f9e616..9dec484 100644 --- a/quic/core/crypto/transport_parameters.h +++ b/quic/core/crypto/transport_parameters.h
@@ -184,6 +184,16 @@ // the sender accepts. See draft-ietf-quic-datagram. IntegerParameter max_datagram_frame_size; + // Google-specific transport parameter that carries an estimate of the + // initial round-trip time in microseconds. + IntegerParameter initial_round_trip_time_us; + + // Google-specific connection options. + quiche::QuicheOptional<QuicTagVector> google_connection_options; + + // Google-specific user agent identifier. + quiche::QuicheOptional<std::string> user_agent_id; + // Transport parameters used by Google QUIC but not IETF QUIC. This is // serialized into a TransportParameter struct with a TransportParameterId of // kGoogleQuicParamId.
diff --git a/quic/core/crypto/transport_parameters_test.cc b/quic/core/crypto/transport_parameters_test.cc index c3d5d57..1fc1f5c 100644 --- a/quic/core/crypto/transport_parameters_test.cc +++ b/quic/core/crypto/transport_parameters_test.cc
@@ -7,7 +7,9 @@ #include <cstring> #include <utility> +#include "net/third_party/quiche/src/quic/core/crypto/crypto_protocol.h" #include "net/third_party/quiche/src/quic/core/quic_connection_id.h" +#include "net/third_party/quiche/src/quic/core/quic_tag.h" #include "net/third_party/quiche/src/quic/core/quic_types.h" #include "net/third_party/quiche/src/quic/core/quic_versions.h" #include "net/third_party/quiche/src/quic/platform/api/quic_expect_bug.h" @@ -38,6 +40,7 @@ const uint64_t kFakeMaxAckDelay = 51; const bool kFakeDisableMigration = true; const uint64_t kFakeActiveConnectionIdLimit = 52; +const uint64_t kFakeInitialRoundTripTime = 53; const uint8_t kFakePreferredStatelessResetTokenData[16] = { 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F}; @@ -100,6 +103,15 @@ preferred_address); } +QuicTagVector CreateFakeGoogleConnectionOptions() { + return {kALPN, MakeQuicTag('E', 'F', 'G', 0x00), + MakeQuicTag('H', 'I', 'J', 0xff)}; +} + +std::string CreateFakeUserAgentId() { + return "FakeUAID"; +} + void RemoveGreaseParameters(TransportParameters* params) { std::vector<TransportParameters::TransportParameterId> grease_params; for (const auto& kv : params->custom_parameters) { @@ -237,6 +249,9 @@ orig_params.preferred_address = CreateFakePreferredAddress(); orig_params.active_connection_id_limit.set_value( kFakeActiveConnectionIdLimit); + orig_params.initial_round_trip_time_us.set_value(kFakeInitialRoundTripTime); + orig_params.google_connection_options = CreateFakeGoogleConnectionOptions(); + orig_params.user_agent_id = CreateFakeUserAgentId(); orig_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value; orig_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value; @@ -264,6 +279,9 @@ orig_params.disable_migration = kFakeDisableMigration; orig_params.active_connection_id_limit.set_value( kFakeActiveConnectionIdLimit); + orig_params.initial_round_trip_time_us.set_value(kFakeInitialRoundTripTime); + orig_params.google_connection_options = CreateFakeGoogleConnectionOptions(); + orig_params.user_agent_id = CreateFakeUserAgentId(); orig_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value; orig_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value; @@ -306,6 +324,7 @@ orig_params.preferred_address = CreateFakePreferredAddress(); orig_params.active_connection_id_limit.set_value( kFakeActiveConnectionIdLimit); + orig_params.google_connection_options = CreateFakeGoogleConnectionOptions(); std::vector<uint8_t> serialized; ASSERT_TRUE(SerializeTransportParameters(version_, orig_params, &serialized)); @@ -433,7 +452,7 @@ TEST_P(TransportParametersTest, ParseClientParams) { // clang-format off const uint8_t kClientParamsOld[] = { - 0x00, 0x49, // length of the parameters array that follows + 0x00, 0x6a, // length of the parameters array that follows // idle_timeout 0x00, 0x01, // parameter id 0x00, 0x02, // length @@ -481,6 +500,20 @@ 0x00, 0x0e, // parameter id 0x00, 0x01, // length 0x34, // value + // initial_round_trip_time_us + 0x31, 0x27, // parameter id + 0x00, 0x01, // length + 0x35, // value + // google_connection_options + 0x31, 0x28, // parameter id + 0x00, 0x0c, // length + 'A', 'L', 'P', 'N', // value + 'E', 'F', 'G', 0x00, + 'H', 'I', 'J', 0xff, + // user_agent_id + 0x31, 0x29, // parameter id + 0x00, 0x08, // length + 'F', 'a', 'k', 'e', 'U', 'A', 'I', 'D', // value // Google version extension 0x47, 0x52, // parameter id 0x00, 0x04, // length @@ -534,6 +567,20 @@ 0x0e, // parameter id 0x01, // length 0x34, // value + // initial_round_trip_time_us + 0x71, 0x27, // parameter id + 0x01, // length + 0x35, // value + // google_connection_options + 0x71, 0x28, // parameter id + 0x0c, // length + 'A', 'L', 'P', 'N', // value + 'E', 'F', 'G', 0x00, + 'H', 'I', 'J', 0xff, + // user_agent_id + 0x71, 0x29, // parameter id + 0x08, // length + 'F', 'a', 'k', 'e', 'U', 'A', 'I', 'D', // value // Google version extension 0x80, 0x00, 0x47, 0x52, // parameter id 0x04, // length @@ -578,6 +625,13 @@ EXPECT_EQ(kFakeDisableMigration, new_params.disable_migration); EXPECT_EQ(kFakeActiveConnectionIdLimit, new_params.active_connection_id_limit.value()); + EXPECT_EQ(kFakeInitialRoundTripTime, + new_params.initial_round_trip_time_us.value()); + ASSERT_TRUE(new_params.google_connection_options.has_value()); + EXPECT_EQ(CreateFakeGoogleConnectionOptions(), + new_params.google_connection_options.value()); + ASSERT_TRUE(new_params.user_agent_id.has_value()); + EXPECT_EQ(CreateFakeUserAgentId(), new_params.user_agent_id.value()); } TEST_P(TransportParametersTest, @@ -745,7 +799,7 @@ TEST_P(TransportParametersTest, ParseServerParams) { // clang-format off const uint8_t kServerParamsOld[] = { - 0x00, 0xa7, // length of parameters array that follows + 0x00, 0xb7, // length of parameters array that follows // original_connection_id 0x00, 0x00, // parameter id 0x00, 0x08, // length @@ -814,6 +868,12 @@ 0x00, 0x0e, // parameter id 0x00, 0x01, // length 0x34, // value + // google_connection_options + 0x31, 0x28, // parameter id + 0x00, 0x0c, // length + 'A', 'L', 'P', 'N', // value + 'E', 'F', 'G', 0x00, + 'H', 'I', 'J', 0xff, // Google version extension 0x47, 0x52, // parameter id 0x00, 0x0d, // length @@ -891,6 +951,12 @@ 0x0e, // parameter id 0x01, // length 0x34, // value + // google_connection_options + 0x71, 0x28, // parameter id + 0x0c, // length + 'A', 'L', 'P', 'N', // value + 'E', 'F', 'G', 0x00, + 'H', 'I', 'J', 0xff, // Google version extension 0x80, 0x00, 0x47, 0x52, // parameter id 0x0d, // length @@ -951,6 +1017,10 @@ new_params.preferred_address->stateless_reset_token); EXPECT_EQ(kFakeActiveConnectionIdLimit, new_params.active_connection_id_limit.value()); + ASSERT_TRUE(new_params.google_connection_options.has_value()); + EXPECT_EQ(CreateFakeGoogleConnectionOptions(), + new_params.google_connection_options.value()); + EXPECT_FALSE(new_params.user_agent_id.has_value()); } TEST_P(TransportParametersTest, ParseServerParametersRepeated) {
diff --git a/quic/core/quic_config.cc b/quic/core/quic_config.cc index 865aef8..7c781f4 100644 --- a/quic/core/quic_config.cc +++ b/quic/core/quic_config.cc
@@ -686,7 +686,7 @@ return bytes_for_connection_id_.GetReceivedValue(); } -void QuicConfig::SetInitialRoundTripTimeUsToSend(uint32_t rtt) { +void QuicConfig::SetInitialRoundTripTimeUsToSend(uint64_t rtt) { initial_round_trip_time_us_.SetSendValue(rtt); } @@ -694,7 +694,7 @@ return initial_round_trip_time_us_.HasReceivedValue(); } -uint32_t QuicConfig::ReceivedInitialRoundTripTimeUs() const { +uint64_t QuicConfig::ReceivedInitialRoundTripTimeUs() const { return initial_round_trip_time_us_.GetReceivedValue(); } @@ -702,7 +702,7 @@ return initial_round_trip_time_us_.HasSendValue(); } -uint32_t QuicConfig::GetInitialRoundTripTimeUsToSend() const { +uint64_t QuicConfig::GetInitialRoundTripTimeUsToSend() const { return initial_round_trip_time_us_.GetSendValue(); } @@ -1138,12 +1138,29 @@ active_connection_id_limit_.GetSendValue()); } - if (!params->google_quic_params) { - params->google_quic_params = std::make_unique<CryptoHandshakeMessage>(); + if (GetQuicRestartFlag(quic_google_transport_param_send_new)) { + QUIC_RESTART_FLAG_COUNT_N(quic_google_transport_param_send_new, 1, 3); + if (initial_round_trip_time_us_.HasSendValue()) { + params->initial_round_trip_time_us.set_value( + initial_round_trip_time_us_.GetSendValue()); + } + if (connection_options_.HasSendValues() && + !connection_options_.GetSendValues().empty()) { + params->google_connection_options = connection_options_.GetSendValues(); + } } - initial_round_trip_time_us_.ToHandshakeMessage( - params->google_quic_params.get()); - connection_options_.ToHandshakeMessage(params->google_quic_params.get()); + + if (!GetQuicRestartFlag(quic_google_transport_param_omit_old)) { + if (!params->google_quic_params) { + params->google_quic_params = std::make_unique<CryptoHandshakeMessage>(); + } + initial_round_trip_time_us_.ToHandshakeMessage( + params->google_quic_params.get()); + connection_options_.ToHandshakeMessage(params->google_quic_params.get()); + } else { + QUIC_RESTART_FLAG_COUNT_N(quic_google_transport_param_omit_old, 1, 3); + } + params->custom_parameters = custom_transport_parameters_to_send_; return true; @@ -1242,22 +1259,41 @@ active_connection_id_limit_.SetReceivedValue( params.active_connection_id_limit.value()); - const CryptoHandshakeMessage* peer_params = params.google_quic_params.get(); - if (peer_params != nullptr) { - QuicErrorCode error = initial_round_trip_time_us_.ProcessPeerHello( - *peer_params, hello_type, error_details); - if (error != QUIC_NO_ERROR) { - DCHECK(!error_details->empty()); - return error; + bool google_params_already_parsed = false; + if (GetQuicRestartFlag(quic_google_transport_param_send_new)) { + QUIC_RESTART_FLAG_COUNT_N(quic_google_transport_param_send_new, 2, 3); + if (params.initial_round_trip_time_us.value() > 0) { + google_params_already_parsed = true; + initial_round_trip_time_us_.SetReceivedValue( + params.initial_round_trip_time_us.value()); } - error = connection_options_.ProcessPeerHello(*peer_params, hello_type, - error_details); - if (error != QUIC_NO_ERROR) { - DCHECK(!error_details->empty()); - return error; + if (params.google_connection_options.has_value()) { + google_params_already_parsed = true; + connection_options_.SetReceivedValues( + params.google_connection_options.value()); } } + if (!GetQuicRestartFlag(quic_google_transport_param_omit_old)) { + const CryptoHandshakeMessage* peer_params = params.google_quic_params.get(); + if (peer_params != nullptr && !google_params_already_parsed) { + QuicErrorCode error = initial_round_trip_time_us_.ProcessPeerHello( + *peer_params, hello_type, error_details); + if (error != QUIC_NO_ERROR) { + DCHECK(!error_details->empty()); + return error; + } + error = connection_options_.ProcessPeerHello(*peer_params, hello_type, + error_details); + if (error != QUIC_NO_ERROR) { + DCHECK(!error_details->empty()); + return error; + } + } + } else { + QUIC_RESTART_FLAG_COUNT_N(quic_google_transport_param_omit_old, 2, 3); + } + received_custom_transport_parameters_ = params.custom_parameters; if (!is_resumption) {
diff --git a/quic/core/quic_config.h b/quic/core/quic_config.h index 893e3e7..4d2bacc 100644 --- a/quic/core/quic_config.h +++ b/quic/core/quic_config.h
@@ -331,11 +331,11 @@ uint32_t ReceivedBytesForConnectionId() const; // Estimated initial round trip time in us. - void SetInitialRoundTripTimeUsToSend(uint32_t rtt_us); + void SetInitialRoundTripTimeUsToSend(uint64_t rtt_us); bool HasReceivedInitialRoundTripTimeUs() const; - uint32_t ReceivedInitialRoundTripTimeUs() const; + uint64_t ReceivedInitialRoundTripTimeUs() const; bool HasInitialRoundTripTimeUsToSend() const; - uint32_t GetInitialRoundTripTimeUsToSend() const; + uint64_t GetInitialRoundTripTimeUsToSend() const; // Sets an initial stream flow control window size to transmit to the peer. void SetInitialStreamFlowControlWindowToSend(uint64_t window_bytes); @@ -528,7 +528,7 @@ // the legacy header format used only by Q043 at this point. QuicFixedUint32 bytes_for_connection_id_; // Initial round trip time estimate in microseconds. - QuicFixedUint32 initial_round_trip_time_us_; + QuicFixedUint62 initial_round_trip_time_us_; // Initial IETF QUIC stream flow control receive windows in bytes. // Incoming bidirectional streams.
diff --git a/quic/core/tls_client_handshaker.cc b/quic/core/tls_client_handshaker.cc index 0378ed0..30a9ac4 100644 --- a/quic/core/tls_client_handshaker.cc +++ b/quic/core/tls_client_handshaker.cc
@@ -207,7 +207,14 @@ if (!session()->config()->FillTransportParameters(¶ms)) { return false; } - params.google_quic_params->SetStringPiece(kUAID, user_agent_id_); + if (GetQuicRestartFlag(quic_google_transport_param_send_new)) { + if (!user_agent_id_.empty()) { + params.user_agent_id = user_agent_id_; + } + } + if (!GetQuicRestartFlag(quic_google_transport_param_omit_old)) { + params.google_quic_params->SetStringPiece(kUAID, user_agent_id_); + } std::vector<uint8_t> param_bytes; return SerializeTransportParameters(session()->connection()->version(),