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


      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,
                                    &params->aead, nullptr) ||
          !QuicUtils::FindMutualTag(requested_config->kexs, their_key_exchanges,
                                    num_their_key_exchanges,
                                    QuicUtils::LOCAL_PRIORITY,
                                    &params->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, &params->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,
                                            &params->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 = &params->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,
                                   &params->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, &params->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(),
              &params->forward_secure_crypters, &params->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源码

  • 相关阅读:
    echarts-detail---饼图
    echarts-detail--柱状图
    mvc5 错误页如何定义
    MyEclipse编码设置
    java EE 5 Libraries 删掉后怎么重新导入
    Server Library [Apache Tomcat v6.0](unbound)服务未绑定解决办法
    免安装jdk 和 免安装tomcat
    group_concat
    java cookie
    java 分割split
  • 原文地址:https://www.cnblogs.com/theseventhson/p/16010406.html
Copyright © 2020-2023  润新知