前言
- 最近通过对google quiche项目的简单学习,发现其代码实现十分复杂,特别是QuicFramer模块,其内部代码较多,不便于和其他模块串联在一起分析
- QuicFramer模块可以称得上是google quiche项目中的quic报文的打包和解包引擎模块
- QuicFramer模块负责quic报文的封包和加密工作,同时也负责对quic报文的解析和解密工作
- 其中加密和解密QuicFramer模块是通过耦合QuicEncrypter和来实现的
- 另外google quiche项目中大量使用了Visitor(访问者)设计模式,并提供QuicFramerVisitorInterface接口从而做到QuicFramer对其他QuicConnection模块的访问
- 本文将简单分析QuicFramer的设计原理,为后续精度google quiche代码做铺垫
QuicFramer核心成员介绍
// Class for parsing and constructing QUIC packets. It has a
// QuicFramerVisitorInterface that is called when packets are parsed.
class QUIC_EXPORT_PRIVATE QuicFramer {
public:
// Set callbacks to be called from the framer. A visitor must be set, or
// else the framer will likely crash. It is acceptable for the visitor
// to do nothing. If this is called multiple times, only the last visitor
// will be used.
void set_visitor(QuicFramerVisitorInterface* visitor) { visitor_ = visitor; }
void InstallDecrypter(EncryptionLevel level,
std::unique_ptr decrypter);
// Changes the encrypter used for level |level| to |encrypter|.
void SetEncrypter(EncryptionLevel level,
std::unique_ptr encrypter);
void set_data_producer(QuicStreamFrameDataProducer* data_producer) {
data_producer_ = data_producer;
}
private:
...
QuicFramerVisitorInterface* visitor_;
// Decrypters used to decrypt packets during parsing.
std::unique_ptr decrypter_[NUM_ENCRYPTION_LEVELS];
// Encrypters used to encrypt packets via EncryptPayload().
std::unique_ptr encrypter_[NUM_ENCRYPTION_LEVELS];
// If not null, framer asks data_producer_ to write stream frame data. Not
// owned. TODO(fayang): Consider add data producer to framer's constructor.
QuicStreamFrameDataProducer* data_producer_;
};
- QuicConnection作为QuicFramerVisitorInterface接口的子类,在QuicConnection的构造函数中通过调用framer_.set_visitor(this),使得QuicFramer模块通过其成员visitor_成为QuicConnection的访问者
- decrypter_成员负责对quic报文进行解密工作
- encrypter_成员负责对quic报文加密工作
- data_producer_顾名思义是数据消费者API,QuicSession派生QuicStreamFrameDataProducer接口,QuicFramer作为quic packet打包引擎,封装完头部信息后回通过QuicStreamFrameDataProducer接口bypass到QuicSession进行消费(组装payload,然后给到QuicWriter模块进行发送处理)
- 接下来我们简单分析以上核心成员的初始化流程
QuicFramer中Initial阶段加解密擎初始化
- 其中Initial包使用的解密引擎初始化如下:
QuicConnection::QuicConnection(
QuicConnectionId server_connection_id,
QuicSocketAddress initial_self_address,
QuicSocketAddress initial_peer_address,
QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory,
QuicPacketWriter* writer, bool owns_writer, Perspective perspective,
const ParsedQuicVersionVector& supported_versions,
ConnectionIdGeneratorInterface& generator)
: .. {
...
InstallInitialCrypters(default_path_.server_connection_id);
...
}
void QuicConnection::InstallInitialCrypters(QuicConnectionId connection_id) {
CrypterPair crypters;
CryptoUtils::CreateInitialObfuscators(perspective_, version(), connection_id,
&crypters);
SetEncrypter(ENCRYPTION_INITIAL, std::move(crypters.encrypter));
if (version().KnowsWhichDecrypterToUse()) {
InstallDecrypter(ENCRYPTION_INITIAL, std::move(crypters.decrypter));
} else {
SetDecrypter(ENCRYPTION_INITIAL, std::move(crypters.decrypter));
}
}
- 在Initial阶段,统一使用CryptoUtils::CreateInitialObfuscators创建相同算法的加解密引擎
- 并通过InstallDecrypter和SetEncrypter对QuicFramer中对应Level的加解密引擎进行初始化
- 这样服务端和客户端在发送和接收Initial报文的时候使用该加解密引擎进行加解密
QuicFramer中QuicDecrypter非Initial解密引擎初始化
- 非Initial阶段解密引擎的函数调用堆栈如下:
- 上述堆栈为quic服务端的堆栈,当服务端收到客户端的Initial报文的时候会提取client hello信息并将其输入到Boring ssl引擎,之后进行握手工作(生成handshake包的时候),在握手过程中会触发其SSL_QUIC_METHOD结构中的set_read_secret函数指针,最终在TlsHandshaker::SetReadSecret()中创建该解密引擎
bool TlsHandshaker::SetReadSecret(EncryptionLevel level,
const SSL_CIPHER* cipher,
absl::Span read_secret) {
std::unique_ptr decrypter =
QuicDecrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher));
// 秘钥相关设置...
const EVP_MD* prf = Prf(cipher);
CryptoUtils::SetKeyAndIV(prf, read_secret,
handshaker_delegate_->parsed_version(),
decrypter.get());
std::vector header_protection_key =
CryptoUtils::GenerateHeaderProtectionKey(
prf, read_secret, handshaker_delegate_->parsed_version(),
decrypter->GetKeySize());
//设置头部加密秘钥信息
decrypter->SetHeaderProtectionKey(
absl::string_view(reinterpret_cast(header_protection_key.data()),
header_protection_key.size()));
if (level == ENCRYPTION_FORWARD_SECURE) {
QUICHE_DCHECK(latest_read_secret_.empty());
latest_read_secret_.assign(read_secret.begin(), read_secret.end());
one_rtt_read_header_protection_key_ = header_protection_key;
}
return handshaker_delegate_->OnNewDecryptionKeyAvailable(
level, std::move(decrypter),
/*set_alternative_decrypter=*/false,
/*latch_once_used=*/false);
}
-
梳理流程如下:
当QuicFramer解析收到的报文后通过QuicDecrypter::DecryptPacket(...)即可完成解密工作
QuicFramer中QuicEncrypter非Initial加密引擎初始化
- encrypter_成员的初始化和decrypter_成员的函数调用堆栈差不多,都是发生在handshake握手阶段,QuicEncrypter模块的创建是发生在TlsHandshaker::SetWriteSecret当中,其实现如下:
void TlsHandshaker::SetWriteSecret(EncryptionLevel level,
const SSL_CIPHER* cipher,
absl::Span write_secret) {
std::unique_ptr encrypter =
QuicEncrypter::CreateFromCipherSuite(SSL_CIPHER_get_id(cipher));
const EVP_MD* prf = Prf(cipher);
CryptoUtils::SetKeyAndIV(prf, write_secret,
handshaker_delegate_->parsed_version(),
encrypter.get());
std::vector header_protection_key =
CryptoUtils::GenerateHeaderProtectionKey(
prf, write_secret, handshaker_delegate_->parsed_version(),
encrypter->GetKeySize());
encrypter->SetHeaderProtectionKey(
absl::string_view(reinterpret_cast(header_protection_key.data()),
header_protection_key.size()));
if (level == ENCRYPTION_FORWARD_SECURE) {
QUICHE_DCHECK(latest_write_secret_.empty());
latest_write_secret_.assign(write_secret.begin(), write_secret.end());
one_rtt_write_header_protection_key_ = header_protection_key;
}
handshaker_delegate_->OnNewEncryptionKeyAvailable(level,
std::move(encrypter));
}
-
整理函数调用流程如下:
- 当QuicFramer封装好报文后通过QuicFramer::EncryptPacket(...)即可完成加密工作
QuicFramer中QuicStreamFrameDataProducer初始化
void QuicSession::Initialize() {
.....
connection_->SetDataProducer(this);
...
}
void QuicConnection::SetDataProducer(
QuicStreamFrameDataProducer* data_producer) {
framer_.set_data_producer(data_producer);
}
- 以此可以看出QuicSession是QuicFramer模块数据封包后的数据消费者
QuicFramer中QuicFramerVisitorInterface初始化
QuicConnection::QuicConnection(
QuicConnectionId server_connection_id,
QuicSocketAddress initial_self_address,
QuicSocketAddress initial_peer_address,
QuicConnectionHelperInterface* helper, QuicAlarmFactory* alarm_factory,
QuicPacketWriter* writer, bool owns_writer, Perspective perspective,
const ParsedQuicVersionVector& supported_versions,
ConnectionIdGeneratorInterface& generator)
: framer_(supported_versions, helper->GetClock()->ApproximateNow(),
perspective, server_connection_id.length()),
.. {
framer_.set_visitor(this);
...
}
- QuicFramer作为QuicConnection模块的成员变量,在QuicConnection构造函数中会实例化QuicFramer,并且会把自己作为QuicFramerVisitorInterface的子类设置到QuicFramer模块中
- 当QuicFramer模块对收到的quic 报文处理完后可以通过该接口访问QuicConnection模块
QuicFramer 公共Api介绍
class QUIC_EXPORT_PRIVATE QuicFramer {
public:
// Serializes a packet containing |frames| into |buffer|.
// Returns the length of the packet, which must not be longer than
// |packet_length|. Returns 0 if it fails to serialize.
size_t BuildDataPacket(const QuicPacketHeader& header,
const QuicFrames& frames, char* buffer,
size_t packet_length, EncryptionLevel level);
// Pass a UDP packet into the framer for parsing.
// Return true if the packet was processed successfully. |packet| must be a
// single, complete UDP packet (not a frame of a packet). This packet
// might be null padded past the end of the payload, which will be correctly
// ignored.
bool ProcessPacket(const QuicEncryptedPacket& packet);
// Encrypts a payload in |buffer|. |ad_len| is the length of the associated
// data. |total_len| is the length of the associated data plus plaintext.
// |buffer_len| is the full length of the allocated buffer.
size_t EncryptInPlace(EncryptionLevel level, QuicPacketNumber packet_number,
size_t ad_len, size_t total_len, size_t buffer_len,
char* buffer);
// Returns the length of the data encrypted into |buffer| if |buffer_len| is
// long enough, and otherwise 0.
size_t EncryptPayload(EncryptionLevel level, QuicPacketNumber packet_number,
const QuicPacket& packet, char* buffer,
size_t buffer_len);
};
- BuildDataPacket()函数作为封包函数的入口,当需要封装quic报文的时候调用该函数
- ProcessPacket()函数负责解析收到的quic报文
- EncryptPayload()函数负责给payload加密处理
总结:
- 本文从初始化流程阐述QuicFramer的核心成员和其工作原理
- QuicFramer对其他模块提供了大量的Api,主要包括加密、解密、封装报文、解析报文等核心函数
- 同时通过访问者设计模式,在QuicFramer模块解析数据包后,在不同的阶段会通过visitor_成员访问QuicConnection模块从而进行相应的业务处理
参考文献:
Visitor(访问者)设计模式