linux源码解读(三十一):quic核心源码分析(二)

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

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
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值