quic協議最早是google提出來的,所以狗家的源碼肯定是最“正宗”的!google把quic協議的源碼放在了chromium里面,所以要看quic的源碼原則上需要下載chromium源碼!但是這份源碼體積很大,並且還需要FQ,所以多年前就有好心人把quic源碼剝離出來單獨放github了,在文章末尾的參考2處;
1、quic相比tcp實現的tls,前面省略了3~4個RTT,根因就是發起連接請求時就發送自己的公鑰給對方,讓對方利用自己的公鑰計算后續對稱加密的key,這就是所謂的handshake;在libquic-master\src\net\quic\core\quic_crypto_client_stream.cc中有具體實現握手的代碼,先看DoHandshakeLoop函數:
void QuicCryptoClientStream::DoHandshakeLoop(const CryptoHandshakeMessage* in) { QuicCryptoClientConfig::CachedState* cached = crypto_config_->LookupOrCreate(server_id_); QuicAsyncStatus rv = QUIC_SUCCESS; do { CHECK_NE(STATE_NONE, next_state_); const State state = next_state_; next_state_ = STATE_IDLE; rv = QUIC_SUCCESS; switch (state) { case STATE_INITIALIZE: DoInitialize(cached); break; case STATE_SEND_CHLO: DoSendCHLO(cached); return; // return waiting to hear from server. case STATE_RECV_REJ: DoReceiveREJ(in, cached); break; case STATE_VERIFY_PROOF: rv = DoVerifyProof(cached); break; case STATE_VERIFY_PROOF_COMPLETE: DoVerifyProofComplete(cached); break; case STATE_GET_CHANNEL_ID: rv = DoGetChannelID(cached); break; case STATE_GET_CHANNEL_ID_COMPLETE: DoGetChannelIDComplete(); break; case STATE_RECV_SHLO: DoReceiveSHLO(in, cached); break; case STATE_IDLE: // This means that the peer sent us a message that we weren't expecting. CloseConnectionWithDetails(QUIC_INVALID_CRYPTO_MESSAGE_TYPE, "Handshake in idle state"); return; case STATE_INITIALIZE_SCUP: DoInitializeServerConfigUpdate(cached); break; case STATE_NONE: NOTREACHED(); return; // We are done. } } while (rv != QUIC_PENDING && next_state_ != STATE_NONE); }
只要quic的狀態不是pending,並且下一個狀態不是NONE,就根據不同的狀態調用不同的處理函數!具體發送handshake小的函數是DoSendCHLO,代碼如下:
/*發送client hello消息*/ void QuicCryptoClientStream::DoSendCHLO( QuicCryptoClientConfig::CachedState* cached) { if (stateless_reject_received_) {//如果收到了server拒絕的消息 // If we've gotten to this point, we've sent at least one hello // and received a stateless reject in response. We cannot // continue to send hellos because the server has abandoned state // for this connection. Abandon further handshakes. next_state_ = STATE_NONE; if (session()->connection()->connected()) { session()->connection()->CloseConnection(//關閉連接 QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT, "stateless reject received", ConnectionCloseBehavior::SILENT_CLOSE); } return; } // Send the client hello in plaintext. //注意:這是client hello消息,沒必要加密 session()->connection()->SetDefaultEncryptionLevel(ENCRYPTION_NONE); encryption_established_ = false; if (num_client_hellos_ > kMaxClientHellos) {//握手消息已經發送了很多,不能再發了 CloseConnectionWithDetails( QUIC_CRYPTO_TOO_MANY_REJECTS, base::StringPrintf("More than %u rejects", kMaxClientHellos).c_str()); return; } num_client_hellos_++; //開始構造握手消息了 CryptoHandshakeMessage out; DCHECK(session() != nullptr); DCHECK(session()->config() != nullptr); // Send all the options, regardless of whether we're sending an // inchoate or subsequent hello. /*填充握手消息的各個字段*/ session()->config()->ToHandshakeMessage(&out); // Send a local timestamp to the server. out.SetValue(kCTIM, session()->connection()->clock()->WallNow().ToUNIXSeconds()); if (!cached->IsComplete(session()->connection()->clock()->WallNow())) { crypto_config_->FillInchoateClientHello( server_id_, session()->connection()->supported_versions().front(), cached, session()->connection()->random_generator(), /* demand_x509_proof= */ true, &crypto_negotiated_params_, &out); // Pad the inchoate client hello to fill up a packet. const QuicByteCount kFramingOverhead = 50; // A rough estimate. const QuicByteCount max_packet_size = session()->connection()->max_packet_length(); if (max_packet_size <= kFramingOverhead) { DLOG(DFATAL) << "max_packet_length (" << max_packet_size << ") has no room for framing overhead."; CloseConnectionWithDetails(QUIC_INTERNAL_ERROR, "max_packet_size too smalll"); return; } if (kClientHelloMinimumSize > max_packet_size - kFramingOverhead) { DLOG(DFATAL) << "Client hello won't fit in a single packet."; CloseConnectionWithDetails(QUIC_INTERNAL_ERROR, "CHLO too large"); return; } // TODO(rch): Remove this when we remove: // FLAGS_quic_use_chlo_packet_size out.set_minimum_size( static_cast<size_t>(max_packet_size - kFramingOverhead)); next_state_ = STATE_RECV_REJ; /*做hash簽名,接收方會根據hash驗證消息是否完整*/ CryptoUtils::HashHandshakeMessage(out, &chlo_hash_); //發送消息 SendHandshakeMessage(out); return; } // If the server nonce is empty, copy over the server nonce from a previous // SREJ, if there is one. if (FLAGS_enable_quic_stateless_reject_support && crypto_negotiated_params_.server_nonce.empty() && cached->has_server_nonce()) { crypto_negotiated_params_.server_nonce = cached->GetNextServerNonce(); DCHECK(!crypto_negotiated_params_.server_nonce.empty()); } string error_details; /*繼續填充client hello消息*/ QuicErrorCode error = crypto_config_->FillClientHello( server_id_, session()->connection()->connection_id(), session()->connection()->version(), session()->connection()->supported_versions().front(), cached, session()->connection()->clock()->WallNow(), //這個隨機數會被server用來計算生成對稱加密的key session()->connection()->random_generator(), channel_id_key_.get(), //保存了nonce、key、token相關信息;后續對稱加密的方法是CTR,需要NONCE值 &crypto_negotiated_params_, &out, &error_details); if (error != QUIC_NO_ERROR) { // Flush the cached config so that, if it's bad, the server has a // chance to send us another in the future. cached->InvalidateServerConfig(); CloseConnectionWithDetails(error, error_details); return; } /*繼續對消息做hash,便於server驗證收到的消息是否完整*/ CryptoUtils::HashHandshakeMessage(out, &chlo_hash_); channel_id_sent_ = (channel_id_key_.get() != nullptr); if (cached->proof_verify_details()) { proof_handler_->OnProofVerifyDetailsAvailable( *cached->proof_verify_details()); } next_state_ = STATE_RECV_SHLO; SendHandshakeMessage(out); // Be prepared to decrypt with the new server write key. session()->connection()->SetAlternativeDecrypter( ENCRYPTION_INITIAL, crypto_negotiated_params_.initial_crypters.decrypter.release(), true /* latch once used */); // Send subsequent packets under encryption on the assumption that the // server will accept the handshake. session()->connection()->SetEncrypter( ENCRYPTION_INITIAL, crypto_negotiated_params_.initial_crypters.encrypter.release()); session()->connection()->SetDefaultEncryptionLevel(ENCRYPTION_INITIAL); // TODO(ianswett): Merge ENCRYPTION_REESTABLISHED and // ENCRYPTION_FIRST_ESTABLSIHED encryption_established_ = true; session()->OnCryptoHandshakeEvent(QuicSession::ENCRYPTION_REESTABLISHED); }
個人覺得最核心的代碼就是FillClientHello函數了,這里會生成隨機數,后續server會利用這個隨機數生成對稱加密的key!部分通信的參數也會通過這個函數的執行保存在crypto_negotiated_params_對象中!client發送了hello包,接下來該server處理這個包了,代碼在libquic-master\src\net\quic\core\quic_crypto_server_stream.cc和quic_crypto_server_config.cc中,代碼如下:核心功能是生成自己的公鑰,還有后續對稱加密的key!
QuicErrorCode QuicCryptoServerConfig::ProcessClientHello( const ValidateClientHelloResultCallback::Result& validate_chlo_result, bool reject_only, QuicConnectionId connection_id, const IPAddress& server_ip, const IPEndPoint& client_address, QuicVersion version, const QuicVersionVector& supported_versions, bool use_stateless_rejects, QuicConnectionId server_designated_connection_id, const QuicClock* clock, QuicRandom* rand,//發送給client用於計算對稱key QuicCompressedCertsCache* compressed_certs_cache, QuicCryptoNegotiatedParameters* params, QuicCryptoProof* crypto_proof, QuicByteCount total_framing_overhead, QuicByteCount chlo_packet_size, CryptoHandshakeMessage* out, DiversificationNonce* out_diversification_nonce, string* error_details) const { DCHECK(error_details); const CryptoHandshakeMessage& client_hello = validate_chlo_result.client_hello; const ClientHelloInfo& info = validate_chlo_result.info; QuicErrorCode valid = CryptoUtils::ValidateClientHello( client_hello, version, supported_versions, error_details); if (valid != QUIC_NO_ERROR) return valid; StringPiece requested_scid; client_hello.GetStringPiece(kSCID, &requested_scid); const QuicWallTime now(clock->WallNow()); scoped_refptr<Config> requested_config; scoped_refptr<Config> primary_config; { base::AutoLock locked(configs_lock_); if (!primary_config_.get()) { *error_details = "No configurations loaded"; return QUIC_CRYPTO_INTERNAL_ERROR; } if (!next_config_promotion_time_.IsZero() && next_config_promotion_time_.IsAfter(now)) { SelectNewPrimaryConfig(now); DCHECK(primary_config_.get()); DCHECK_EQ(configs_.find(primary_config_->id)->second, primary_config_); } // Use the config that the client requested in order to do key-agreement. // Otherwise give it a copy of |primary_config_| to use. primary_config = crypto_proof->config; requested_config = GetConfigWithScid(requested_scid); } if (validate_chlo_result.error_code != QUIC_NO_ERROR) { *error_details = validate_chlo_result.error_details; return validate_chlo_result.error_code; } out->Clear(); if (!ClientDemandsX509Proof(client_hello)) { *error_details = "Missing or invalid PDMD"; return QUIC_UNSUPPORTED_PROOF_DEMAND; } DCHECK(proof_source_.get()); string chlo_hash; CryptoUtils::HashHandshakeMessage(client_hello, &chlo_hash); // No need to get a new proof if one was already generated. if (!crypto_proof->chain && !proof_source_->GetProof(server_ip, info.sni.as_string(), primary_config->serialized, version, chlo_hash, &crypto_proof->chain, &crypto_proof->signature, &crypto_proof->cert_sct)) { return QUIC_HANDSHAKE_FAILED; } StringPiece cert_sct; if (client_hello.GetStringPiece(kCertificateSCTTag, &cert_sct) && cert_sct.empty()) { params->sct_supported_by_client = true; } if (!info.reject_reasons.empty() || !requested_config.get()) { BuildRejection(version, clock->WallNow(), *primary_config, client_hello, info, validate_chlo_result.cached_network_params, use_stateless_rejects, server_designated_connection_id, rand, compressed_certs_cache, params, *crypto_proof, total_framing_overhead, chlo_packet_size, out); return QUIC_NO_ERROR; } if (reject_only) { return QUIC_NO_ERROR; } const QuicTag* their_aeads; const QuicTag* their_key_exchanges; size_t num_their_aeads, num_their_key_exchanges; if (client_hello.GetTaglist(kAEAD, &their_aeads, &num_their_aeads) != QUIC_NO_ERROR || client_hello.GetTaglist(kKEXS, &their_key_exchanges, &num_their_key_exchanges) != QUIC_NO_ERROR || num_their_aeads != 1 || num_their_key_exchanges != 1) { *error_details = "Missing or invalid AEAD or KEXS"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } size_t key_exchange_index; if (!QuicUtils::FindMutualTag(requested_config->aead, their_aeads, num_their_aeads, QuicUtils::LOCAL_PRIORITY, ¶ms->aead, nullptr) || !QuicUtils::FindMutualTag(requested_config->kexs, their_key_exchanges, num_their_key_exchanges, QuicUtils::LOCAL_PRIORITY, ¶ms->key_exchange, &key_exchange_index)) { *error_details = "Unsupported AEAD or KEXS"; return QUIC_CRYPTO_NO_SUPPORT; } if (!requested_config->tb_key_params.empty()) { const QuicTag* their_tbkps; size_t num_their_tbkps; switch (client_hello.GetTaglist(kTBKP, &their_tbkps, &num_their_tbkps)) { case QUIC_CRYPTO_MESSAGE_PARAMETER_NOT_FOUND: break; case QUIC_NO_ERROR: if (QuicUtils::FindMutualTag( requested_config->tb_key_params, their_tbkps, num_their_tbkps, QuicUtils::LOCAL_PRIORITY, ¶ms->token_binding_key_param, nullptr)) { break; } default: *error_details = "Invalid Token Binding key parameter"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } } StringPiece public_value; /*提取client hello數據包發送的公鑰,server要用來生成對稱加密的key*/ if (!client_hello.GetStringPiece(kPUBS, &public_value)) { *error_details = "Missing public value"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } const KeyExchange* key_exchange = requested_config->key_exchanges[key_exchange_index]; if (!key_exchange->CalculateSharedKey(public_value, ¶ms->initial_premaster_secret)) { *error_details = "Invalid public value"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } if (!info.sni.empty()) { std::unique_ptr<char[]> sni_tmp(new char[info.sni.length() + 1]); memcpy(sni_tmp.get(), info.sni.data(), info.sni.length()); sni_tmp[info.sni.length()] = 0; params->sni = CryptoUtils::NormalizeHostname(sni_tmp.get()); } string hkdf_suffix; //client hello消息序列化,便於提取? const QuicData& client_hello_serialized = client_hello.GetSerialized(); /*根據一個原始密鑰材料,用hkdf算法推導出指定長度的密鑰; 這里明顯是要根據client hello的數據生成對稱加密的密鑰了 */ hkdf_suffix.reserve(sizeof(connection_id) + client_hello_serialized.length() + requested_config->serialized.size()); hkdf_suffix.append(reinterpret_cast<char*>(&connection_id), sizeof(connection_id)); hkdf_suffix.append(client_hello_serialized.data(), client_hello_serialized.length()); hkdf_suffix.append(requested_config->serialized); DCHECK(proof_source_.get()); if (crypto_proof->chain->certs.empty()) { *error_details = "Failed to get certs"; return QUIC_CRYPTO_INTERNAL_ERROR; } hkdf_suffix.append(crypto_proof->chain->certs.at(0)); StringPiece cetv_ciphertext; if (requested_config->channel_id_enabled && client_hello.GetStringPiece(kCETV, &cetv_ciphertext)) { CryptoHandshakeMessage client_hello_copy(client_hello); client_hello_copy.Erase(kCETV); client_hello_copy.Erase(kPAD); const QuicData& client_hello_copy_serialized = client_hello_copy.GetSerialized(); string hkdf_input; hkdf_input.append(QuicCryptoConfig::kCETVLabel, strlen(QuicCryptoConfig::kCETVLabel) + 1); hkdf_input.append(reinterpret_cast<char*>(&connection_id), sizeof(connection_id)); hkdf_input.append(client_hello_copy_serialized.data(), client_hello_copy_serialized.length()); hkdf_input.append(requested_config->serialized); CrypterPair crypters; if (!CryptoUtils::DeriveKeys(params->initial_premaster_secret, params->aead, info.client_nonce, info.server_nonce, hkdf_input, Perspective::IS_SERVER, CryptoUtils::Diversification::Never(), &crypters, nullptr /* subkey secret */)) { *error_details = "Symmetric key setup failed"; return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED; } char plaintext[kMaxPacketSize]; size_t plaintext_length = 0; const bool success = crypters.decrypter->DecryptPacket( kDefaultPathId, 0 /* packet number */, StringPiece() /* associated data */, cetv_ciphertext, plaintext, &plaintext_length, kMaxPacketSize); if (!success) { *error_details = "CETV decryption failure"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } std::unique_ptr<CryptoHandshakeMessage> cetv( CryptoFramer::ParseMessage(StringPiece(plaintext, plaintext_length))); if (!cetv.get()) { *error_details = "CETV parse error"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } StringPiece key, signature; if (cetv->GetStringPiece(kCIDK, &key) && cetv->GetStringPiece(kCIDS, &signature)) { if (!ChannelIDVerifier::Verify(key, hkdf_input, signature)) { *error_details = "ChannelID signature failure"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } params->channel_id = key.as_string(); } } string hkdf_input; size_t label_len = strlen(QuicCryptoConfig::kInitialLabel) + 1; hkdf_input.reserve(label_len + hkdf_suffix.size()); hkdf_input.append(QuicCryptoConfig::kInitialLabel, label_len); hkdf_input.append(hkdf_suffix); string* subkey_secret = ¶ms->initial_subkey_secret; CryptoUtils::Diversification diversification = CryptoUtils::Diversification::Never(); if (version > QUIC_VERSION_32) { rand->RandBytes(out_diversification_nonce->data(), out_diversification_nonce->size()); diversification = CryptoUtils::Diversification::Now(out_diversification_nonce); } if (!CryptoUtils::DeriveKeys(params->initial_premaster_secret, params->aead, info.client_nonce, info.server_nonce, hkdf_input, Perspective::IS_SERVER, diversification, ¶ms->initial_crypters, subkey_secret)) { *error_details = "Symmetric key setup failed"; return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED; } string forward_secure_public_value; if (ephemeral_key_source_.get()) { params->forward_secure_premaster_secret = ephemeral_key_source_->CalculateForwardSecureKey( key_exchange, rand, clock->ApproximateNow(), public_value, &forward_secure_public_value); } else { std::unique_ptr<KeyExchange> forward_secure_key_exchange( key_exchange->NewKeyPair(rand)); forward_secure_public_value = forward_secure_key_exchange->public_value().as_string(); /*生成共享密鑰*/ if (!forward_secure_key_exchange->CalculateSharedKey( public_value, ¶ms->forward_secure_premaster_secret)) { *error_details = "Invalid public value"; return QUIC_INVALID_CRYPTO_MESSAGE_PARAMETER; } } string forward_secure_hkdf_input; label_len = strlen(QuicCryptoConfig::kForwardSecureLabel) + 1; forward_secure_hkdf_input.reserve(label_len + hkdf_suffix.size()); forward_secure_hkdf_input.append(QuicCryptoConfig::kForwardSecureLabel, label_len); forward_secure_hkdf_input.append(hkdf_suffix); string shlo_nonce; shlo_nonce = NewServerNonce(rand, info.now); out->SetStringPiece(kServerNonceTag, shlo_nonce); /*生成密鑰*/ if (!CryptoUtils::DeriveKeys( params->forward_secure_premaster_secret, params->aead, info.client_nonce, shlo_nonce.empty() ? info.server_nonce : shlo_nonce, forward_secure_hkdf_input, Perspective::IS_SERVER, CryptoUtils::Diversification::Never(), ¶ms->forward_secure_crypters, ¶ms->subkey_secret)) { *error_details = "Symmetric key setup failed"; return QUIC_CRYPTO_SYMMETRIC_KEY_SETUP_FAILED; } out->set_tag(kSHLO); QuicTagVector supported_version_tags; for (size_t i = 0; i < supported_versions.size(); ++i) { supported_version_tags.push_back( QuicVersionToQuicTag(supported_versions[i])); } out->SetVector(kVER, supported_version_tags); out->SetStringPiece( kSourceAddressTokenTag, NewSourceAddressToken(*requested_config.get(), info.source_address_tokens, client_address.address(), rand, info.now, nullptr)); QuicSocketAddressCoder address_coder(client_address); out->SetStringPiece(kCADR, address_coder.Encode()); /*server hello包中設置server的公鑰,后續client會利用這個生成對稱加密的key*/ out->SetStringPiece(kPUBS, forward_secure_public_value); return QUIC_NO_ERROR; }
這里用了不同的方法來生成對稱加密的key。這里以橢圓曲線為例,計算對稱加密key的代碼如下:這是直接調用了openssl/curve25519.h的接口計算出來的。一旦雙方都生成了對稱密鑰,后續就可以通過對稱加密通信了!
bool Curve25519KeyExchange::CalculateSharedKey(StringPiece peer_public_value, string* out_result) const { if (peer_public_value.size() != crypto::curve25519::kBytes) { return false; } uint8_t result[crypto::curve25519::kBytes]; if (!crypto::curve25519::ScalarMult( private_key_, reinterpret_cast<const uint8_t*>(peer_public_value.data()), result)) { return false; } out_result->assign(reinterpret_cast<char*>(result), sizeof(result)); return true; } bool ScalarMult(const uint8_t* private_key, const uint8_t* peer_public_key, uint8_t* shared_key) { return !!X25519(shared_key, private_key, peer_public_key); }
通信時給packet加密的方法:
bool AeadBaseEncrypter::EncryptPacket(QuicPathId path_id, QuicPacketNumber packet_number, StringPiece associated_data, StringPiece plaintext, char* output, size_t* output_length, size_t max_output_length) { size_t ciphertext_size = GetCiphertextSize(plaintext.length()); if (max_output_length < ciphertext_size) { return false; } // TODO(ianswett): Introduce a check to ensure that we don't encrypt with the // same packet number twice. const size_t nonce_size = nonce_prefix_size_ + sizeof(packet_number); ALIGNAS(4) char nonce_buffer[kMaxNonceSize]; memcpy(nonce_buffer, nonce_prefix_, nonce_prefix_size_); uint64_t path_id_packet_number = QuicUtils::PackPathIdAndPacketNumber(path_id, packet_number); memcpy(nonce_buffer + nonce_prefix_size_, &path_id_packet_number, sizeof(path_id_packet_number)); /*這里用nonce給明文加密*/ if (!Encrypt(StringPiece(nonce_buffer, nonce_size), associated_data, plaintext, reinterpret_cast<unsigned char*>(output))) { return false; } *output_length = ciphertext_size; return true; }
最后,server hello消息是從這里發出去的,並且在某些情況下server hello已經用server新生成的key加密了,如下:
void QuicCryptoServerStream::FinishProcessingHandshakeMessage( const ValidateClientHelloResultCallback::Result& result, std::unique_ptr<ProofSource::Details> details) { const CryptoHandshakeMessage& message = result.client_hello; // Clear the callback that got us here. DCHECK(validate_client_hello_cb_ != nullptr); validate_client_hello_cb_ = nullptr; if (use_stateless_rejects_if_peer_supported_) { peer_supports_stateless_rejects_ = DoesPeerSupportStatelessRejects(message); } CryptoHandshakeMessage reply; DiversificationNonce diversification_nonce; string error_details; QuicErrorCode error = /*server處理client的hello消息:重點是生成對稱加密key、自己的公鑰和nonce 同時生成給client回復的消息*/ ProcessClientHello(result, std::move(details), &reply, &diversification_nonce, &error_details); if (error != QUIC_NO_ERROR) { CloseConnectionWithDetails(error, error_details); return; } if (reply.tag() != kSHLO) { if (reply.tag() == kSREJ) { DCHECK(use_stateless_rejects_if_peer_supported_); DCHECK(peer_supports_stateless_rejects_); // Before sending the SREJ, cause the connection to save crypto packets // so that they can be added to the time wait list manager and // retransmitted. session()->connection()->EnableSavingCryptoPackets(); } SendHandshakeMessage(reply);//給client發server hello if (reply.tag() == kSREJ) { DCHECK(use_stateless_rejects_if_peer_supported_); DCHECK(peer_supports_stateless_rejects_); DCHECK(!handshake_confirmed()); DVLOG(1) << "Closing connection " << session()->connection()->connection_id() << " because of a stateless reject."; session()->connection()->CloseConnection( QUIC_CRYPTO_HANDSHAKE_STATELESS_REJECT, "stateless reject", ConnectionCloseBehavior::SILENT_CLOSE); } return; } // If we are returning a SHLO then we accepted the handshake. Now // process the negotiated configuration options as part of the // session config. //代碼到這里已經給client發送了client hello,表示server已經准備好接受數據了 //這里保存一些雙方協商好的通信配置 QuicConfig* config = session()->config(); OverrideQuicConfigDefaults(config); error = config->ProcessPeerHello(message, CLIENT, &error_details); if (error != QUIC_NO_ERROR) { CloseConnectionWithDetails(error, error_details); return; } session()->OnConfigNegotiated(); config->ToHandshakeMessage(&reply); // Receiving a full CHLO implies the client is prepared to decrypt with // the new server write key. We can start to encrypt with the new server // write key. 可以開始用服務端新生成的key解密數據了 // // NOTE: the SHLO will be encrypted with the new server write key. /*既然在server已經生成了對稱加密的key,這里可以用這個key加密server hello消息*/ session()->connection()->SetEncrypter( ENCRYPTION_INITIAL, crypto_negotiated_params_.initial_crypters.encrypter.release()); session()->connection()->SetDefaultEncryptionLevel(ENCRYPTION_INITIAL); // Set the decrypter immediately so that we no longer accept unencrypted // packets. session()->connection()->SetDecrypter( ENCRYPTION_INITIAL, crypto_negotiated_params_.initial_crypters.decrypter.release()); if (version() > QUIC_VERSION_32) { session()->connection()->SetDiversificationNonce(diversification_nonce); } SendHandshakeMessage(reply);//發送server hello session()->connection()->SetEncrypter( ENCRYPTION_FORWARD_SECURE, crypto_negotiated_params_.forward_secure_crypters.encrypter.release()); session()->connection()->SetDefaultEncryptionLevel(ENCRYPTION_FORWARD_SECURE); session()->connection()->SetAlternativeDecrypter( ENCRYPTION_FORWARD_SECURE, crypto_negotiated_params_.forward_secure_crypters.decrypter.release(), false /* don't latch */); encryption_established_ = true; handshake_confirmed_ = true; session()->OnCryptoHandshakeEvent(QuicSession::HANDSHAKE_CONFIRMED); }
(2)為了防止tcp的隊頭阻塞,quic在前面丟包的情況下任然繼續發包,丟的包用新的packet number重新發,怎么區別這個新包是以往丟包的重發了?核心是每個包都有stream id和stream offset字段,根據這兩個字段定位包的位置,而不是packet number。整個包結構定義的類在這里:
struct NET_EXPORT_PRIVATE QuicStreamFrame { QuicStreamFrame(); QuicStreamFrame(QuicStreamId stream_id, bool fin, QuicStreamOffset offset, base::StringPiece data); QuicStreamFrame(QuicStreamId stream_id, bool fin, QuicStreamOffset offset, QuicPacketLength data_length, UniqueStreamBuffer buffer); ~QuicStreamFrame(); NET_EXPORT_PRIVATE friend std::ostream& operator<<(std::ostream& os, const QuicStreamFrame& s); QuicStreamId stream_id; bool fin; QuicPacketLength data_length; const char* data_buffer; QuicStreamOffset offset; // Location of this data in the stream. // nullptr when the QuicStreamFrame is received, and non-null when sent. UniqueStreamBuffer buffer; private: QuicStreamFrame(QuicStreamId stream_id, bool fin, QuicStreamOffset offset, const char* data_buffer, QuicPacketLength data_length, UniqueStreamBuffer buffer); DISALLOW_COPY_AND_ASSIGN(QuicStreamFrame); };
收到后自然要把payload取出來拼接成完整的數據,stream id和stream offset必不可少,拼接和處理的邏輯在這里:里面涉及到很多duplicate冗余去重的動作,都是依據offset來判斷的!
QuicErrorCode QuicStreamSequencerBuffer::OnStreamData( QuicStreamOffset starting_offset, base::StringPiece data, QuicTime timestamp, size_t* const bytes_buffered, std::string* error_details) { *bytes_buffered = 0; QuicStreamOffset offset = starting_offset; size_t size = data.size(); if (size == 0) { *error_details = "Received empty stream frame without FIN."; return QUIC_EMPTY_STREAM_FRAME_NO_FIN; } // Find the first gap not ending before |offset|. This gap maybe the gap to // fill if the arriving frame doesn't overlaps with previous ones. std::list<Gap>::iterator current_gap = gaps_.begin(); while (current_gap != gaps_.end() && current_gap->end_offset <= offset) { ++current_gap; } DCHECK(current_gap != gaps_.end()); // "duplication": might duplicate with data alread filled,but also might // overlap across different base::StringPiece objects already written. // In both cases, don't write the data, // and allow the caller of this method to handle the result. if (offset < current_gap->begin_offset && offset + size <= current_gap->begin_offset) { DVLOG(1) << "Duplicated data at offset: " << offset << " length: " << size; return QUIC_NO_ERROR; } if (offset < current_gap->begin_offset && offset + size > current_gap->begin_offset) { // Beginning of new data overlaps data before current gap. *error_details = string("Beginning of received data overlaps with buffered data.\n") + "New frame range " + RangeDebugString(offset, offset + size) + " with first 128 bytes: " + string(data.data(), data.length() < 128 ? data.length() : 128) + "\nCurrently received frames: " + ReceivedFramesDebugString() + "\nCurrent gaps: " + GapsDebugString(); return QUIC_OVERLAPPING_STREAM_DATA; } if (offset + size > current_gap->end_offset) { // End of new data overlaps with data after current gap. *error_details = string("End of received data overlaps with buffered data.\n") + "New frame range " + RangeDebugString(offset, offset + size) + " with first 128 bytes: " + string(data.data(), data.length() < 128 ? data.length() : 128) + "\nCurrently received frames: " + ReceivedFramesDebugString() + "\nCurrent gaps: " + GapsDebugString(); return QUIC_OVERLAPPING_STREAM_DATA; } // Write beyond the current range this buffer is covering. if (offset + size > total_bytes_read_ + max_buffer_capacity_bytes_) { *error_details = "Received data beyond available range."; return QUIC_INTERNAL_ERROR; } if (current_gap->begin_offset != starting_offset && current_gap->end_offset != starting_offset + data.length() && gaps_.size() >= kMaxNumGapsAllowed) { // This frame is going to create one more gap which exceeds max number of // gaps allowed. Stop processing. *error_details = "Too many gaps created for this stream."; return QUIC_TOO_MANY_FRAME_GAPS; } size_t total_written = 0; size_t source_remaining = size; const char* source = data.data(); // Write data block by block. If corresponding block has not created yet, // create it first. // Stop when all data are written or reaches the logical end of the buffer. while (source_remaining > 0) { const size_t write_block_num = GetBlockIndex(offset); const size_t write_block_offset = GetInBlockOffset(offset); DCHECK_GT(blocks_count_, write_block_num); size_t block_capacity = GetBlockCapacity(write_block_num); size_t bytes_avail = block_capacity - write_block_offset; // If this write meets the upper boundary of the buffer, // reduce the available free bytes. if (offset + bytes_avail > total_bytes_read_ + max_buffer_capacity_bytes_) { bytes_avail = total_bytes_read_ + max_buffer_capacity_bytes_ - offset; } if (reduce_sequencer_buffer_memory_life_time_ && blocks_ == nullptr) { blocks_.reset(new BufferBlock*[blocks_count_]()); for (size_t i = 0; i < blocks_count_; ++i) { blocks_[i] = nullptr; } } if (blocks_[write_block_num] == nullptr) { // TODO(danzh): Investigate if using a freelist would improve performance. // Same as RetireBlock(). blocks_[write_block_num] = new BufferBlock(); } const size_t bytes_to_copy = min<size_t>(bytes_avail, source_remaining); char* dest = blocks_[write_block_num]->buffer + write_block_offset; DVLOG(1) << "Write at offset: " << offset << " length: " << bytes_to_copy; memcpy(dest, source, bytes_to_copy); source += bytes_to_copy; source_remaining -= bytes_to_copy; offset += bytes_to_copy; total_written += bytes_to_copy; } DCHECK_GT(total_written, 0u); *bytes_buffered = total_written; UpdateGapList(current_gap, starting_offset, total_written); frame_arrival_time_map_.insert( std::make_pair(starting_offset, FrameInfo(size, timestamp))); num_bytes_buffered_ += total_written; return QUIC_NO_ERROR; }
(3)為了精准測量RTT,quic協議的數據包編號都是單調遞增的,哪怕是重發的包的編號都是增加的,這部分的控制代碼在WritePacket函數里面:函數開頭就判斷數據包編號。一旦發現編號比最后一次發送包的編號還小,說明出錯了,這時就關閉連接退出函數!
bool QuicConnection::WritePacket(SerializedPacket* packet) { /*如果數據包號比最后一個發送包的號還小,說明順序錯了,直接關閉連接*/ if (packet->packet_number < sent_packet_manager_->GetLargestSentPacket(packet->path_id)) { QUIC_BUG << "Attempt to write packet:" << packet->packet_number << " after:" << sent_packet_manager_->GetLargestSentPacket(packet->path_id); CloseConnection(QUIC_INTERNAL_ERROR, "Packet written out of order.", ConnectionCloseBehavior::SEND_CONNECTION_CLOSE_PACKET); return true; } /*沒有連接、沒有加密的包是不能發的*/ if (ShouldDiscardPacket(*packet)) { ++stats_.packets_discarded; return true; } ......................... }
(4)為啥quic協議要基於udp了?應用層現成的協議很復雜,改造的難度大!傳輸層只有tcp和udp兩種協議;tcp的缺點不再贅述,udp的優點就是簡單,只提供最原始的發包功能,完全不管對方有沒有收到,quic就是利用了udp這種最基礎的send package發包能力,在此之上完成了tls(保證數據安全)、擁塞控制(保證鏈路被塞滿)、多路復用(保證數據不丟失)等應用層的功能!
參考:
1、https://www.cnblogs.com/dream397/p/14605040.html quic實現代碼分析
2、https://github.com/devsisters/libquic libquic源碼