google quic QuicFramer模块原理-封包和解包

前言

  • 在上一篇文章中,我们从**QuicFramer **核心成员大致说明了其核心成员的初始化流程,以及核心成员的作用
  • 本文将通过分析**QuicFramer **公共Api对报文的封装和解析做详细的代码分析

QuicFramer quic报文封装分析

  • quic报文的封装和序列化在google quic报文封装和协议分析一文中已经做了详细分析,本文不再做代码分析

  • 本文主要做一些总结性的介绍,其流程如下:


    004.png
  • QuicFramer模块通过向其他模块提供**BuildDataPacket(...) **api来完成打包工作

QuicFramer quic报文解包分析

  • 本文详细分析**QuicFramer **模块的报文解析过程,其大致流程图如下:


    005.png
  • 上图中的QuicFramer::ProcessIetfPacketHeader(…)本文不再添代码做说明,就是按照RFCv1的协议一个一个读取协议,最终成功解析出QuicPacketHeader部分

  • 本文的重点是分析QuicFramer::ProcessIetfDataPacket(…)函数的实现

bool QuicFramer::ProcessIetfDataPacket(QuicDataReader* encrypted_reader,
                                       QuicPacketHeader* header,
                                       const QuicEncryptedPacket& packet,
                                       char* decrypted_buffer,
                                       size_t buffer_length) {
  ...
  // 1) 处理ietf 头长度字段(如果收到的是聚合包,这里需要分离聚合包),并将聚合的后面一部分送到QuicConnection模块,在下一个回合使用
  if (!MaybeProcessIetfLength(encrypted_reader, header)) {
    return false;
  }

  absl::string_view associated_data;
  std::vector ad_storage;
  QuicPacketNumber base_packet_number;
  //2) short 格式,或long 格式非版本协商包
  if (header->form == IETF_QUIC_SHORT_HEADER_PACKET ||
      header->long_packet_type != VERSION_NEGOTIATION) {
    QUICHE_DCHECK(header->form == IETF_QUIC_SHORT_HEADER_PACKET ||
                  header->long_packet_type == INITIAL ||
                  header->long_packet_type == HANDSHAKE ||
                  header->long_packet_type == ZERO_RTT_PROTECTED);
    // Process packet number.默认为true
    if (supports_multiple_packet_number_spaces_) {
      PacketNumberSpace pn_space = GetPacketNumberSpace(*header);
      if (pn_space == NUM_PACKET_NUMBER_SPACES) {
        return RaiseError(QUIC_INVALID_PACKET_HEADER);
      }
      base_packet_number = largest_decrypted_packet_numbers_[pn_space];
    } else {
      base_packet_number = largest_packet_number_;
    }
    uint64_t full_packet_number;
    bool hp_removal_failed = false;
    if (version_.HasHeaderProtection()) {
      // 移除头部加密,在封包的时候EncryptPayload(...)函数中会通过ApplyHeaderProtection()对头部做一些特殊处理
      // 作为收包端这个地方需要进行还原操作经过该步骤,我们可以得到packet_number_length字段,以及packet_number字段
      if (!RemoveHeaderProtection(encrypted_reader, packet, header,
                                  &full_packet_number, &ad_storage)) {
        hp_removal_failed = true;
      }
      associated_data = absl::string_view(ad_storage.data(), ad_storage.size());
    } 
    .....
    header->packet_number = QuicPacketNumber(full_packet_number);
  }
  ....

  absl::string_view encrypted = encrypted_reader->ReadRemainingPayload();
  ....

  size_t decrypted_length = 0;
  EncryptionLevel decrypted_level;
  //3) 解密payload data
  if (!DecryptPayload(packet.length(), encrypted, associated_data, *header,
                      decrypted_buffer, buffer_length, &decrypted_length,
                      &decrypted_level)) {
    ....
    return RaiseError(QUIC_DECRYPTION_FAILURE);
  }
  QuicDataReader reader(decrypted_buffer, decrypted_length);
  ....
  // Update the largest packet number after we have decrypted the packet
  // so we are confident is not attacker controlled.
  // 这里更新largest_decrypted_packet_numbers_,根据当前收到的最大的PakcteNumber号
  if (supports_multiple_packet_number_spaces_) {
    largest_decrypted_packet_numbers_[QuicUtils::GetPacketNumberSpace(
                                          decrypted_level)]
        .UpdateMax(header->packet_number);
  } else {
    largest_packet_number_.UpdateMax(header->packet_number);
  }
  
  // 4) 回调到QuicConnection模块进行相应业务处理,如更新connection Id等信息
  if (!visitor_->OnPacketHeader(*header)) {
    RecordDroppedPacketReason(DroppedPacketReason::INVALID_PACKET_NUMBER);
    // The visitor suppresses further processing of the packet.
    return true;
  }
  .....
  //5) Handle the payload.处理payload 
  if (VersionHasIetfQuicFrames(version_.transport_version)) {
    current_received_frame_type_ = 0;
    previously_received_frame_type_ = 0;
    if (!ProcessIetfFrameData(&reader, *header, decrypted_level)) {
      current_received_frame_type_ = 0;
      previously_received_frame_type_ = 0;
      ....
      return false;
    }
    current_received_frame_type_ = 0;
    previously_received_frame_type_ = 0;
  } 
  // 6) 完成一个报文的解析   
  visitor_->OnPacketComplete();
  return true;
}
  • 1)该函数首先通过调用MaybeProcessIetfLength()函数解析判断该报文是否是聚合报文,如果是聚合包,需要对其进行分离,分开解析,那么什么是聚合包呢?(后面进行分析)

  • 2)解析出quic报文的PacketNumber

  • 3)调用DecryptPayload()解密payload data

  • 4)通过OnPacketHeader()回调函数告知QuicConnection模块进行相应处理

  • 5)调用ProcessIetfFrameData()对payload进行处理

  • 6)通过回调OnPacketComplete()通知QuicConnection模块完成当前报文的解析工作

  • 总结其时序如下图


    006.png
  • 接下来着重分析MaybeProcessIetfLength、ProcessIetfFrameData等函数

QuicFramer MaybeProcessIetfLength分析

bool QuicFramer::MaybeProcessIetfLength(QuicDataReader* encrypted_reader,
                                        QuicPacketHeader* header) {
  ...
  //1) 读取quic报头的length信息,包括length是用多少字节来描述的,以及length的值是多少
  // header->length_length 标识当前quic报文的length是用几个字节来描述
  // header->remaining_packet_length 标识当前quic报文截止length字段之后还余下多少字节
  header->length_length = encrypted_reader->PeekVarInt62Length();
  if (!encrypted_reader->ReadVarInt62(&header->remaining_packet_length)) {
    set_detailed_error("Unable to read long header payload length.");
    return RaiseError(QUIC_INVALID_PACKET_HEADER);
  }
  //2) 这里第一个quic报文头解析完后自第一个报文的length字段到整个包末尾还有多少长度
  uint64_t remaining_bytes_length = encrypted_reader->BytesRemaining();
  if (header->remaining_packet_length > remaining_bytes_length) {
    set_detailed_error("Long header payload length longer than packet.");
    return RaiseError(QUIC_INVALID_PACKET_HEADER);
  }
  //3) 聚合报文处理,比如服务端回复客户端的initial的包就是使用的聚合包(initial + handshake)
  MaybeProcessCoalescedPacket(*encrypted_reader, remaining_bytes_length,
                              *header);
  //4) 冗余截断,当聚合包分离后,修改encrypted_reader内存中的偏移信息
  if (!encrypted_reader->TruncateRemaining(header->remaining_packet_length)) {
    set_detailed_error("Length TruncateRemaining failed.");
    QUIC_BUG(quic_bug_10850_54) << "Length TruncateRemaining failed.";
    return RaiseError(QUIC_INVALID_PACKET_HEADER);
  }
  return true;
}
  • 为了更形象的分析上述函数,这里以Initial报文为例进行说明,配合抓包文件进行分析,以下是一个握手抓包文件,服务端回复客户端的initial包的时候使用了聚合包,该报文包含(initial + handshake)两个完整ietf包

    007.png

  • 1)header->length_length得到的值为VARIABLE_LENGTH_INTEGER_LENGTH_2,代表第一个quic ietf(initial)包length字段使用两个字节,header->remaining_packet_length为117,代表当前这个Initial包的packet number+payload一共为117字节

  • 2)remaining_bytes_length = encrypted_reader->BytesRemaining()的值为1232刚好为第一个报文length字段的起始地址到该总报文的末尾的长度

  • 3)聚合处理,将handshake包分离出来bypass到QuicConnection模块

void QuicFramer::MaybeProcessCoalescedPacket(
    const QuicDataReader& encrypted_reader, uint64_t remaining_bytes_length,
    const QuicPacketHeader& header) {
  // 3.1) 以此来判断是否是聚合包
  if (header.remaining_packet_length >= remaining_bytes_length) {
    // There is no coalesced packet.
    return;
  }
 
  absl::string_view remaining_data = encrypted_reader.PeekRemainingPayload();
  QUICHE_DCHECK_EQ(remaining_data.length(), remaining_bytes_length);
  //3.2) 这里得到Handshake包的起始地址
  const char* coalesced_data =
      remaining_data.data() + header.remaining_packet_length;
  uint64_t coalesced_data_length =
      remaining_bytes_length - header.remaining_packet_length;
  QuicDataReader coalesced_reader(coalesced_data, coalesced_data_length);
  //3.3) 解析聚合包(handshake)的头部信息
  QuicPacketHeader coalesced_header;
  if (!ProcessIetfPacketHeader(&coalesced_reader, &coalesced_header)) {
    .....
    return;
  }
  
  QuicEncryptedPacket coalesced_packet(coalesced_data, coalesced_data_length,
                                       /*owns_buffer=*/false);
  // 3.4) bypass到QuicConnection模块,进行缓存,等initial包处理完后会继续处理该包
  visitor_->OnCoalescedPacket(coalesced_packet);
}
  • 聚合包的处理主要是对后面包进行分离出来,并进行头部解析,然后通过OnCoalescedPacket回调,将该包送到QuicConnection模块进行缓存
  • 其实现如下:
class QUIC_EXPORT_PRIVATE QuicConnection
    : public QuicFramerVisitorInterface,
      ... {
 public:
  ...
  void OnCoalescedPacket(const QuicEncryptedPacket& packet) override;
  ...
 protected:
  ...
  // Collection of coalesced packets which were received while processing
  // the current packet.
  quiche::QuicheCircularDeque>
      received_coalesced_packets_;  
};
void QuicConnection::OnCoalescedPacket(const QuicEncryptedPacket& packet) {
  QueueCoalescedPacket(packet);
}

void QuicConnection::QueueCoalescedPacket(const QuicEncryptedPacket& packet) {
  received_coalesced_packets_.push_back(packet.Clone());
  ++stats_.num_coalesced_packets_received;
}
  • 由此可见OnCoalescedPacket()的处理仅仅只是将该包插入到received_coalesced_packets_队列当中

QuicFramer ProcessIetfFrameData分析

  • ProcessIetfFrameData主要对对payload部分进行处理,在quic包中payload会被序列化成各种Frame,在RFC9000中定义不同类型的Frame
  • 以下以PadingFrame、PingFrame、以及CryptoFrame为例来说明Frame的定义
PADDING Frame {
  Type (i) = 0x00,
}
  • 以下是一个padding frame的抓包实例


    008.png
  • 从抓包文件来看,一帧PaddingFrame是由若干个0x00 组成,比如上面这帧padding frame占13个字节,其中type为一个字节,编码值为0x00

PING Frame {
  Type (i) = 0x01,
}
  • ping 帧用于time alive检测,检测连接直接是否超时抓包如下


    009.png
  • Ping Frame无负载,占用一个字节,就是其type = 0x01占用一个字节

CRYPTO Frame {
  Type (i) = 0x06,
  Offset (i),
  Length (i),
  Crypto Data (..),
}
  • offset:可变长度编码的整型数字,指定了该frame中的data在该stream中的偏移量

  • Length:可变长度编码,指定了Crypto data的字节数

  • Crypto data:加密的消息

  • 以下为一个抓包示意


    010.png
  • 由于本文的分析背景是基于握手流程,所以这里只对ping、padding、和crypto frame进行说明,对于更多的frame说明请参考Quic帧类型总结

  • 在有这些背景知识后,接下来分析ProcessIetfFrameData函数的实现

bool QuicFramer::ProcessIetfFrameData(QuicDataReader* reader,
                                      const QuicPacketHeader& header,
                                      EncryptionLevel decrypted_level) {
  ...
  // 直到读完
  while (!reader->IsDoneReading()) {
    ....
    uint64_t frame_type;
    // Will be the number of bytes into which frame_type was encoded.
    size_t encoded_bytes = reader->BytesRemaining();
    if (!reader->ReadVarInt62(&frame_type)) {
      set_detailed_error("Unable to read frame type.");
      return RaiseError(QUIC_INVALID_FRAME_DATA);
    }
    ....
    // Is now the number of bytes into which the frame type was encoded.
    encoded_bytes -= reader->BytesRemaining();
      
    // Check that the frame type is minimally encoded.
    if (encoded_bytes !=
        static_cast(QuicDataWriter::GetVarInt62Len(frame_type))) {
      // The frame type was not minimally encoded.
      return RaiseError(IETF_QUIC_PROTOCOL_VIOLATION);
    }

    if (IS_IETF_STREAM_FRAME(frame_type)) {
      QuicStreamFrame frame;
      if (!ProcessIetfStreamFrame(reader, frame_type, &frame)) {
        return RaiseError(QUIC_INVALID_STREAM_DATA);
      }
      if (!visitor_->OnStreamFrame(frame)) {
        ....
        return true;
      }
    } else {
      switch (frame_type) {
        case IETF_PADDING: {
          QuicPaddingFrame frame;
          ProcessPaddingFrame(reader, &frame);
          if (!visitor_->OnPaddingFrame(frame)) {
            // Returning true since there was no parsing error.
            return true;
          }
          break;
        }
        case IETF_PING: {
          // Ping has no payload.
          QuicPingFrame ping_frame;
          if (!visitor_->OnPingFrame(ping_frame)) {
            // Returning true since there was no parsing error.
            return true;
          }
          break;
        }
        case IETF_CRYPTO: {
          QuicCryptoFrame frame;
          if (!ProcessCryptoFrame(reader, GetEncryptionLevel(header), &frame)) {
            return RaiseError(QUIC_INVALID_FRAME_DATA);
          }
          if (!visitor_->OnCryptoFrame(frame)) {
            return true;
          }
          break;
        }
        .....
      }
    }
  }
  return true;
}
  • 在quic的payload的中是由一个或者多个QuicFrame组成的由于本文以握手为背景,客户端首先发送Initial报文,该报文会携带PingFrame、PaddingFrame以及CryptoFrame,而服务端在收到客户端的Initial报文后会进行回复,服务端回复的报文中就包含了CryptoFrame和AckFrame
  • 以上的核心实现是以Frame为单位循环读取payload,并按照协议进行解析,当解析到不同类型的Frame后,会通过函数回调将该类型的Frame信息回调到QuicConnection模块进行相应的业务处理
  • 以上函数中的ProcessXXXFrame()本文不做详细解释,其实就是完全按照Ietf协议定义进行数据读取反序列化,最终将其值来初始化各Frame的结构定义
  • 本文留下疑问,上述函数中出现QuicStreamFrame,它是在什么时候创建并发送的?以及有什么用途?

总结:

  • 本文通过分析QuicFramer 模块中的一些解析和处理核心函数来学习QuicFramer模块在google quiche项目中的核心地位

  • 通过结合RFC协议定义学习quic中个Frame的定义,同时也学习到了当收到一个普通的quic报文后,经过QuicFramer模块处理后,将发生什么样的处理

  • 通过本文学习亦可以了解到quic数据包的加密和解密都是在该模块中完全,这为以后假设遇到一些非加密场景,需要特殊修改做好基础铺垫

  • 最后回顾一下,对于一个Initial报文,在QuicFramer模块中处理后,将会和QuicConnection发生怎样的交互呢?这里总结如下:

    011.png

  • 本文两个疑问其一是,握手流程中,当服务端收到Initial报文后,服务端是怎么触发握手流程的?其次是QuicStreamFrame是在什么时机使用的?以及QuicStream在什么时候创建?

参考文献:

  • Quic帧类型总结
  • RFC9000
  • google quic报文封装和协议分析

你可能感兴趣的:(google quic QuicFramer模块原理-封包和解包)