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\http://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_negoti

本文深入分析了QUIC协议的Google Chromium源码,探讨了QUIC如何通过提前发送公钥减少握手延迟,以及加密密钥的生成过程。讲解了QUIC如何利用stream ID和stream offset避免队头阻塞问题,以及通过单调递增的包编号精确测量RTT。QUIC协议基于UDP,实现了TLS安全、拥塞控制和多路复用等功能。
最低0.47元/天 解锁文章
750

被折叠的 条评论
为什么被折叠?



