前言
- 在上一篇文章中,我们从**QuicFramer **核心成员大致说明了其核心成员的初始化流程,以及核心成员的作用
- 本文将通过分析**QuicFramer **公共Api对报文的封装和解析做详细的代码分析
QuicFramer quic报文封装分析
quic报文的封装和序列化在google quic报文封装和协议分析一文中已经做了详细分析,本文不再做代码分析
-
本文主要做一些总结性的介绍,其流程如下:
QuicFramer模块通过向其他模块提供**BuildDataPacket(...) **api来完成打包工作
QuicFramer quic报文解包分析
-
本文详细分析**QuicFramer **模块的报文解析过程,其大致流程图如下:
上图中的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<char> 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模块完成当前报文的解析工作
-
总结其时序如下图
接下来着重分析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包
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<std::unique_ptr<QuicEncryptedPacket>>
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的抓包实例
从抓包文件来看,一帧PaddingFrame是由若干个0x00 组成,比如上面这帧padding frame占13个字节,其中type为一个字节,编码值为0x00
PING Frame {
Type (i) = 0x01,
}
-
ping 帧用于time alive检测,检测连接直接是否超时抓包如下
Ping Frame无负载,占用一个字节,就是其type = 0x01占用一个字节
CRYPTO Frame {
Type (i) = 0x06,
Offset (i),
Length (i),
Crypto Data (..),
}
offset:可变长度编码的整型数字,指定了该frame中的data在该stream中的偏移量
Length:可变长度编码,指定了Crypto data的字节数
Crypto data:加密的消息
-
以下为一个抓包示意
由于本文的分析背景是基于握手流程,所以这里只对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<size_t>(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发生怎样的交互呢?这里总结如下:
本文两个疑问其一是,握手流程中,当服务端收到Initial报文后,服务端是怎么触发握手流程的?其次是QuicStreamFrame是在什么时机使用的?以及QuicStream在什么时候创建?