当前位置: 首页>后端>正文

google quic服务端报文解析和分发分析

前言

  • 在quic报文封装和序列化分析一文中有学习到google quic的打包和序列化流程,本文接着上文的分析,对quic报文接收解析进行分析
  • 在之前的分析中,可以得出quic的封包、加密都是在QuicFramer模块当中,同理解析和解密应该也是在该模块当中
  • 考虑到服务端需要托管多个连接,所以在数据分发上和客户端有些区别,一般服务端在处理IO数据的时候需要借助QuicPacketReader、QuicDispatcher模块,以下是个例子

class QUIC_EXPORT QuicRawServer : public QuicSocketEventListener {
public:
  // QuicSocketEventListener implementation.
  void OnSocketEvent(QuicEventLoop* event_loop,
                     QuicUdpSocketFd fd,
                     QuicSocketEventMask events) override;
  ....
 protected:
   ....
  // Point to a QuicPacketReader object on the heap. The reader allocates more
  // space than allowed on the stack.
  std::unique_ptr<QuicPacketReader> packet_reader_;
};
void QuicRawServer::OnSocketEvent(QuicEventLoop* /*event_loop*/,
                                  QuicUdpSocketFd fd,
                                  QuicSocketEventMask events) {
  QUICHE_DCHECK_EQ(fd, fd_);

  if (events & kSocketEventReadable) {
    QUIC_DVLOG(1) << "EPOLLIN";
    // 第一次这里是没有的,如果握手包在传输中出现了乱序或者丢包
    dispatcher_->ProcessBufferedChlos(kNumSessionsToCreatePerSocketEvent);

    bool more_to_read = true;
    while (more_to_read) {
      more_to_read = packet_reader_->ReadAndDispatchPackets(
          fd_, port_, *QuicDefaultClock::Get(), dispatcher_.get(),
          overflow_supported_ &packets_dropped_ : nullptr);
    }

    if (dispatcher_->HasChlosBuffered()) {
      // Register EPOLLIN event to consume buffered CHLO(s).
      bool success =
          event_loop_->ArtificiallyNotifyEvent(fd_, kSocketEventReadable);
      QUICHE_DCHECK(success);
    }
    if (!event_loop_->SupportsEdgeTriggered()) {
      bool success = event_loop_->RearmSocket(fd_, kSocketEventReadable);
      QUICHE_DCHECK(success);
    }
  }
  if (events & kSocketEventWritable) {
    dispatcher_->OnCanWrite();
    if (!event_loop_->SupportsEdgeTriggered() &&
        dispatcher_->HasPendingWrites()) {
      bool success = event_loop_->RearmSocket(fd_, kSocketEventWritable);
      QUICHE_DCHECK(success);
    }
  }
}
  • 对于一个新连接,客户端发个来的首个包一定会是Initial包,对于该包QuicPacketReader::ReadAndDispatchPackets()函数的核心流程大致如下:
    google quic服务端报文解析和分发分析,第1张
    0001.png
  • 本文按照上图的三大流程进行逐一分析
  • 对于新客户端,在#3中会创建QuicSession而对于已有QuicSession的客户端,包在#2会进行分发

ReceivedPacketInfo结构公众头部信息解析

  • QuicFramer::ParsePublicHeaderDispatcherShortHeaderLengthUnknown()是一个静态函数,用于从quic 包中解析相关头部信息,然后将这些信息填充到ReceivedPacketInfo当中
void QuicDispatcher::ProcessPacket(const QuicSocketAddress& self_address,
                                   const QuicSocketAddress& peer_address,
                                   const QuicReceivedPacket& packet) {
  ReceivedPacketInfo packet_info(self_address, peer_address, packet);
  std::string detailed_error;
  QuicErrorCode error;
  error = QuicFramer::ParsePublicHeaderDispatcherShortHeaderLengthUnknown(
      packet, &packet_info.form, &packet_info.long_packet_type,
      &packet_info.version_flag, &packet_info.use_length_prefix,
      &packet_info.version_label, &packet_info.version,
      &packet_info.destination_connection_id, &packet_info.source_connection_id,
      &packet_info.retry_token, &detailed_error, connection_id_generator_);
  .....
}
// static
QuicErrorCode QuicFramer::ParsePublicHeaderDispatcherShortHeaderLengthUnknown(
    const QuicEncryptedPacket& packet, PacketHeaderFormat* format,
    QuicLongHeaderType* long_packet_type, bool* version_present,
    bool* has_length_prefix, QuicVersionLabel* version_label,
    ParsedQuicVersion* parsed_version,
    QuicConnectionId* destination_connection_id,
    QuicConnectionId* source_connection_id,
    absl::optional<absl::string_view>* retry_token, std::string* detailed_error,
    ConnectionIdGeneratorInterface& generator) {
  QuicDataReader reader(packet.data(), packet.length());
  // Get the first two bytes.
  if (reader.BytesRemaining() < 2) {
    *detailed_error = "Unable to read first two bytes.";
    return QUIC_INVALID_PACKET_HEADER;
  }
  uint8_t two_bytes[2];
  reader.ReadBytes(two_bytes, 2);
  // 如果长类型格式头部这个值为0
  uint8_t expected_destination_connection_id_length =
      (!QuicUtils::IsIetfPacketHeader(two_bytes[0]) ||
       two_bytes[0] & FLAGS_LONG_HEADER)
          0
          : generator.ConnectionIdLength(two_bytes[1]);
  return ParsePublicHeaderDispatcher(
      packet, expected_destination_connection_id_length, format,
      long_packet_type, version_present, has_length_prefix, version_label,
      parsed_version, destination_connection_id, source_connection_id,
      retry_token, detailed_error);
}
  • 以上函数最终是调用ParsePublicHeaderDispatcher函数对头部进行解析
// static
QuicErrorCode QuicFramer::ParsePublicHeaderDispatcher(
    const QuicEncryptedPacket& packet,
    uint8_t expected_destination_connection_id_length,
    PacketHeaderFormat* format, QuicLongHeaderType* long_packet_type,
    bool* version_present, bool* has_length_prefix,
    QuicVersionLabel* version_label, ParsedQuicVersion* parsed_version,
    QuicConnectionId* destination_connection_id,
    QuicConnectionId* source_connection_id,
    absl::optional<absl::string_view>* retry_token,
    std::string* detailed_error) {
  QuicDataReader reader(packet.data(), packet.length());
  if (reader.IsDoneReading()) {
    *detailed_error = "Unable to read first byte.";
    return QUIC_INVALID_PACKET_HEADER;
  }
  //1) 报文有效性判断
  const uint8_t first_byte = reader.PeekByte();
  if ((first_byte & FLAGS_LONG_HEADER) == 0 &&
      (first_byte & FLAGS_FIXED_BIT) == 0 &&
      (first_byte & FLAGS_DEMULTIPLEXING_BIT) == 0) {
    // All versions of Google QUIC up to and including Q043 set
    // FLAGS_DEMULTIPLEXING_BIT to one on all client-to-server packets. Q044
    // and Q045 were never default-enabled in production. All subsequent
    // versions of Google QUIC (starting with Q046) require FLAGS_FIXED_BIT to
    // be set to one on all packets. All versions of IETF QUIC (since
    // draft-ietf-quic-transport-17 which was earlier than the first IETF QUIC
    // version that was deployed in production by any implementation) also
    // require FLAGS_FIXED_BIT to be set to one on all packets. If a packet
    // has the FLAGS_LONG_HEADER bit set to one, it could be a first flight
    // from an unknown future version that allows the other two bits to be set
    // to zero. Based on this, packets that have all three of those bits set
    // to zero are known to be invalid.
    *detailed_error = "Invalid flags.";
    return QUIC_INVALID_PACKET_HEADER;
  }
  //2) ietf格式判断,也就是判断是走google 标准还是RFC ietf标准的包,默认都是走ietf的
  const bool ietf_format = QuicUtils::IsIetfPacketHeader(first_byte);
  uint8_t unused_first_byte;
  quiche::QuicheVariableLengthIntegerLength retry_token_length_length;
  absl::string_view maybe_retry_token;
  //3 )解析头部信息
  QuicErrorCode error_code = ParsePublicHeader(
      &reader, expected_destination_connection_id_length, ietf_format,
      &unused_first_byte, format, version_present, has_length_prefix,
      version_label, parsed_version, destination_connection_id,
      source_connection_id, long_packet_type, &retry_token_length_length,
      &maybe_retry_token, detailed_error);
  if (retry_token_length_length != quiche::VARIABLE_LENGTH_INTEGER_LENGTH_0) {
    *retry_token = maybe_retry_token;
  } else {
    retry_token->reset();
  }
  return error_code;
}
  • 以上函数分成三步,首先通过第一个字节判断Quic包的有效性,具体可以看代码中的注释,说得很清楚
  • 其次是ietf格式判断,也就是判断是走google 标准还是RFC ietf标准的包,默认都是走ietf的
  • 最后调用ParsePublicHeader对报文做真正的解析,在分析该函数之前,我们再复习一下Initial包的结构定义
Initial Packet {
  Header Form (1) = 1,
  Fixed Bit (1) = 1,
  Long Packet Type (2) = 0,
  Reserved Bits (2),
  Packet Number Length (2),
  Version (32),
  Destination Connection ID Length (8),
  Destination Connection ID (0..160),
  Source Connection ID Length (8),
  Source Connection ID (0..160),
  Token Length (i),
  Token (..),
  Length (i),
  Packet Number (8..32),
  Packet Payload (8..),
}

Figure 15: Initial Packet

// static
QuicErrorCode QuicFramer::ParsePublicHeader(
    QuicDataReader* reader, uint8_t expected_destination_connection_id_length,
    bool ietf_format, uint8_t* first_byte, PacketHeaderFormat* format,
    bool* version_present, bool* has_length_prefix,
    QuicVersionLabel* version_label, ParsedQuicVersion* parsed_version,
    QuicConnectionId* destination_connection_id,
    QuicConnectionId* source_connection_id,
    QuicLongHeaderType* long_packet_type,
    quiche::QuicheVariableLengthIntegerLength* retry_token_length_length,
    absl::string_view* retry_token, std::string* detailed_error) {
  *version_present = false;
  *has_length_prefix = false;
  *version_label = 0;
  *parsed_version = UnsupportedQuicVersion();
  *source_connection_id = EmptyQuicConnectionId();
  *long_packet_type = INVALID_PACKET_TYPE;
  *retry_token_length_length = quiche::VARIABLE_LENGTH_INTEGER_LENGTH_0;
  *retry_token = absl::string_view();
  *detailed_error = "";

  if (!reader->ReadUInt8(first_byte)) {
    *detailed_error = "Unable to read first byte.";
    return QUIC_INVALID_PACKET_HEADER;
  }

  if (!ietf_format) {
    return ParsePublicHeaderGoogleQuic(
        reader, first_byte, format, version_present, version_label,
        parsed_version, destination_connection_id, detailed_error);
  }
  //1) 第一个字节
  *format = GetIetfPacketHeaderFormat(*first_byte);

  if (*format == IETF_QUIC_SHORT_HEADER_PACKET) {
    if (!reader->ReadConnectionId(destination_connection_id,
                                  expected_destination_connection_id_length)) {
      *detailed_error = "Unable to read destination connection ID.";
      return QUIC_INVALID_PACKET_HEADER;
    }
    return QUIC_NO_ERROR;
  }
  //2) Version字段
  QUICHE_DCHECK_EQ(IETF_QUIC_LONG_HEADER_PACKET, *format);
  *version_present = true;
  if (!ProcessVersionLabel(reader, version_label)) {
    *detailed_error = "Unable to read protocol version.";
    return QUIC_INVALID_PACKET_HEADER;
  }

  if (*version_label == 0) {
    *long_packet_type = VERSION_NEGOTIATION;
  }

  // Parse version.
  *parsed_version = ParseQuicVersionLabel(*version_label);

  // Figure out which IETF QUIC invariants this packet follows.
  //3) Destination/Source Connection ID legth和id相关解析
  // 大于QUIC 46的版本都是固定长度,其中80代表RFC9000也就是QUIC V1
  *has_length_prefix = PacketHasLengthPrefixedConnectionIds(
      *reader, *parsed_version, *version_label, *first_byte);

  // Parse connection IDs.
  if (!ParseLongHeaderConnectionIds(*reader, *has_length_prefix, *version_label,
                                    *destination_connection_id,
                                    *source_connection_id, *detailed_error)) {
    return QUIC_INVALID_PACKET_HEADER;
  }

  if (!parsed_version->IsKnown()) {
    // Skip parsing of long packet type and retry token for unknown versions.
    return QUIC_NO_ERROR;
  }
  //4) INITIAL、ZERO_RTT、HANDSHAKE还是RETRY
  // Parse long packet type.
  *long_packet_type = GetLongHeaderType(*first_byte, *parsed_version);

  switch (*long_packet_type) {
    case INVALID_PACKET_TYPE:
      *detailed_error = "Unable to parse long packet type.";
      return QUIC_INVALID_PACKET_HEADER;
    case INITIAL:
      if (!parsed_version->SupportsRetry()) {
        // Retry token is only present on initial packets for some versions.
        return QUIC_NO_ERROR;
      }
      break;
    default:
      return QUIC_NO_ERROR;
  }
  //5) 获取token长度这里使用可变长度编码规则,8个字节,高两位为11
  *retry_token_length_length = reader->PeekVarInt62Length();
  uint64_t retry_token_length;
  if (!reader->ReadVarInt62(&retry_token_length)) {
    *retry_token_length_length = quiche::VARIABLE_LENGTH_INTEGER_LENGTH_0;
    *detailed_error = "Unable to read retry token length.";
    return QUIC_INVALID_PACKET_HEADER;
  }
  //获取token
  if (!reader->ReadStringPiece(retry_token, retry_token_length)) {
    *detailed_error = "Unable to read retry token.";
    return QUIC_INVALID_PACKET_HEADER;
  }

  return QUIC_NO_ERROR;
}
  • 根据RFC9000中报文格式的定义进行解析,1)最先是解析第一个字节,判断报文的长格式报文还是短格式报文
  • 2)解析Version字段,一共是4个固定字节
  • 3)判断connection id长度的描述是否是使用固定字节的,对于当前QuicVersion V1来说,是使用固定1个字节的,这里是用一个字节来表示连接ID的实际尝试,假设这1个字节填满有127,这代表连接ID的长度是127个字节
  • 4)对于Long Header Type的包,解析包的类型,如INITIAL、ZERO_RTT、HANDSHAKE还是RETRY
  • 5)如果存在Token相关信息则按照协议顺序获取token 长度信息,再获取token
  • 该函数结束之后,也就是ParsePublicHeaderDispatcherShortHeaderLengthUnknown()函数返回ReceivedPacketInfo结构中报文头部等信息就已经被填充,第一大步就此结束

QuicDispatcher::MaybeDispatchPacket(...)尝试分发报文

void QuicDispatcher::ProcessPacket(const QuicSocketAddress& self_address,
                                   const QuicSocketAddress& peer_address,
                                   const QuicReceivedPacket& packet) {
  ReceivedPacketInfo packet_info(self_address, peer_address, packet);
  (解析头部信息)
  ....
  if (MaybeDispatchPacket(packet_info)) {
    // Packet has been dropped or successfully dispatched, stop processing.
    return;
  }
  ....
}
  • 通过上一节对报文的解析,packet_info中的头部相关信息已经得到填充
  • 这里以ReceivedPacketInfo作为函数参数,进行尝试分发操作
bool QuicDispatcher::MaybeDispatchPacket(
    const ReceivedPacketInfo& packet_info) {
  .....
  const QuicConnectionId server_connection_id =
      packet_info.destination_connection_id;

  //(此处省略对包头 QUIC 版本号等信息进行校验,如果校验通过继续往下走)

  // Packets with connection IDs for active connections are processed
  // immediately.
  auto it = reference_counted_session_map_.find(server_connection_id);
  if (it != reference_counted_session_map_.end()) {
    QUICHE_DCHECK(!buffered_packets_.HasBufferedPackets(server_connection_id));
    it->second->ProcessUdpPacket(packet_info.self_address,
                                 packet_info.peer_address, packet_info.packet);
    return true;
  }
  
  // 如果initial丢失、或者乱序、再或者由于initial包超过一个MTU,那么会使用这个Buffer缓存
  if (buffered_packets_.HasChloForConnection(server_connection_id)) {
    BufferEarlyPacket(packet_info);
    return true;
  }
  .....

  return false;
}
  • 以上函数由于本文只考虑从Initial报文着手分析,所以省略了其他代码,后面文章会对其进行详细分析
  • Initial报文的角度来看,该函数什么都没做,就是根据连接ID判读当前的连接是否已经存在reference_counted_session_map_表里面了,如果不存在则直接返回false
  • 而对于第一个Initial报文来看,肯定是不存在的所以此处暂且跳过
  • 而对于非Initial报文,在此处会将报文转发到QuicSession模块进行处理,此处不做详细分析
  • 注意对于非Initial报文由于已经有QuicSession了,所以会将报文分发到QuicSession模块处理,并且不会再有QuicDispatcher::ProcessHeader流程了

QuicDispatcher::ProcessHeader报文头部处理


void QuicDispatcher::ProcessHeader(ReceivedPacketInfo* packet_info) {
  QuicConnectionId server_connection_id =
      packet_info->destination_connection_id;
  // 包宿命检测,是kFateProcess、kFateTimeWait还是kFateDrop
  // Packet's connection ID is unknown.  Apply the validity checks.
  //1) 
  QuicPacketFate fate = ValidityChecks(*packet_info);

  // |connection_close_error_code| is used if the final packet fate is
  // kFateTimeWait.
  QuicErrorCode connection_close_error_code = QUIC_HANDSHAKE_FAILED;

  // If a fatal TLS alert was received when extracting Client Hello,
  // |tls_alert_error_detail| will be set and will be used as the error_details
  // of the connection close.
  std::string tls_alert_error_detail;
  //2) 
  if (fate == kFateProcess) {
    ExtractChloResult extract_chlo_result =
        TryExtractChloOrBufferEarlyPacket(*packet_info);
    auto& parsed_chlo = extract_chlo_result.parsed_chlo;

    if (extract_chlo_result.tls_alert.has_value()) {
        ....
    } else if (!parsed_chlo.has_value()) {
      // Client Hello incomplete. Packet has been buffered or (rarely) dropped.
      return;
    } else {
      // 2) 
      // Client Hello fully received.
      fate = ValidityChecksOnFullChlo(*packet_info, *parsed_chlo);

      if (fate == kFateProcess) {
        // 3)
        ProcessChlo(*std::move(parsed_chlo), packet_info);
        return;
      }
    }
  }
  ... 
}
  • QuicDispatcher::ProcessHeader函数处理大致分成三大步骤
  • 1)首先是调用ValidityChecks()函数对报文进行检测,最后判断该包是继续处理还是丢弃,或者是timewait
  • 2)本文以Initial报文作为研究背景,所以这里是继续处理,则调用ValidityChecksOnFullChlo()检测有效性,该函数目前暂未实现最终返回kFateProcess
  • 3)最后调用ProcessChlo()函数对client hello进行处理,也就是握手第一步
  • 对以上流程进行总结如下图:


    google quic服务端报文解析和分发分析,第2张
    0002.png
  • 接下来大致分析ProcessChlo函数的实现
void QuicDispatcher::ProcessChlo(ParsedClientHello parsed_chlo,
                                 ReceivedPacketInfo* packet_info) {
  ....
  //1)
  auto session_ptr = QuicDispatcher::CreateSessionFromChlo(
      packet_info->destination_connection_id, parsed_chlo, packet_info->version,
      packet_info->self_address, packet_info->peer_address);
  if (session_ptr == nullptr) {
    return;
  }
  // 假设乱序了,有报文在Initial报文之前达到,会存到该列表当中
  std::list<BufferedPacket> packets =
      buffered_packets_.DeliverPackets(packet_info->destination_connection_id)
          .buffered_packets;
  if (packet_info->destination_connection_id != session_ptr->connection_id()) {
    // Provide the calling function with access to the new connection ID.
    packet_info->destination_connection_id = session_ptr->connection_id();
    if (!packets.empty()) {
      QUIC_CODE_COUNT(
          quic_delivered_buffered_packets_to_connection_with_replaced_id);
    }
  }
  // 2)
  // Process CHLO at first.
  session_ptr->ProcessUdpPacket(packet_info->self_address,
                                packet_info->peer_address, packet_info->packet);
  // Deliver queued-up packets in the same order as they arrived.
  // Do this even when flag is off because there might be still some packets
  // buffered in the store before flag is turned off.
  DeliverPacketsToSession(packets, session_ptr.get());
  --new_sessions_allowed_per_event_loop_;
}
  • 此处依然基于Initial报文作为研究背景进行分析
  • 该函数最重要的就是基于连接ID信息,创建QuicSession,quic连接以此为基础单位,每个会话对应一个QuicSession实例与此对应,在Initial包收到的时候创建
  • 其次是调用QuicSession::ProcessUdpPacket函数对Initial包进行处理,这一步处理十分复杂,会为当前QuicSession分配QuicConnection,QuicStream等结构信息,同时会将client hello信息输入到TLS(boringssl)引擎进行处理,从而服务端会生成server hello信息回复给客户端
  • 最后调用DeliverPacketsToSession()将对应的缓存包送入到QuicSession模块进行处理,假设有的话

总结:

  • 本文以Initial报文为背景分析了服务端在收到该报文后的处理流程
  • 同时得出当一个新连接到Quic服务端的时候,当服务端收到Initial包的时候,会对新连接分配QuicSession实例,并且会将该实例插入到reference_counted_session_map_哈希Map当中进行保存
  • 同时会将该报文给到QuicSession模块进行处理,最后生成Server Hello消息回复到客户端
  • 到此我们了解到,QuicDispatcher模块中的reference_counted_session_map_哈希Map中记录着每一个客户端的QuicSession
  • 由于QuicSession::ProcessUdpPacket()函数的实现十分复杂,所以本文不做详细分析,后面会对其进行专门分析

参考文献

  • RFC9000

https://www.xamrdz.com/backend/3uu1933952.html

相关文章: