google quic QuicFramer模块原理-核心成员介绍

前言

  • 最近通过对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<QuicDecrypter> decrypter);    
    
  // Changes the encrypter used for level |level| to |encrypter|.
  void SetEncrypter(EncryptionLevel level,
                    std::unique_ptr<QuicEncrypter> 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<QuicDecrypter> decrypter_[NUM_ENCRYPTION_LEVELS];
  // Encrypters used to encrypt packets via EncryptPayload().
  std::unique_ptr<QuicEncrypter> 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和SetEncrypterQuicFramer中对应Level的加解密引擎进行初始化
  • 这样服务端和客户端在发送和接收Initial报文的时候使用该加解密引擎进行加解密

QuicFramer中QuicDecrypter非Initial解密引擎初始化

  • Initial阶段解密引擎的函数调用堆栈如下:
    002.png
  • 上述堆栈为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<const uint8_t> read_secret) {
  std::unique_ptr<QuicDecrypter> 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<uint8_t> header_protection_key =
      CryptoUtils::GenerateHeaderProtectionKey(
          prf, read_secret, handshaker_delegate_->parsed_version(),
          decrypter->GetKeySize());
  //设置头部加密秘钥信息
  decrypter->SetHeaderProtectionKey(
      absl::string_view(reinterpret_cast<char*>(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);
}
  • 梳理流程如下:


    001.png
  • QuicFramer解析收到的报文后通过QuicDecrypter::DecryptPacket(...)即可完成解密工作

QuicFramer中QuicEncrypter非Initial加密引擎初始化

  • encrypter_成员的初始化和decrypter_成员的函数调用堆栈差不多,都是发生在handshake握手阶段,QuicEncrypter模块的创建是发生在TlsHandshaker::SetWriteSecret当中,其实现如下:
void TlsHandshaker::SetWriteSecret(EncryptionLevel level,
                                   const SSL_CIPHER* cipher,
                                   absl::Span<const uint8_t> write_secret) {
  std::unique_ptr<QuicEncrypter> 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<uint8_t> header_protection_key =
      CryptoUtils::GenerateHeaderProtectionKey(
          prf, write_secret, handshaker_delegate_->parsed_version(),
          encrypter->GetKeySize());
  encrypter->SetHeaderProtectionKey(
      absl::string_view(reinterpret_cast<char*>(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));
}
  • 整理函数调用流程如下:


    003.png
  • QuicFramer封装好报文后通过QuicFramer::EncryptPacket(...)即可完成加密工作

QuicFramer中QuicStreamFrameDataProducer初始化

void QuicSession::Initialize() {
  .....
  connection_->SetDataProducer(this);
  ...
}
void QuicConnection::SetDataProducer(
    QuicStreamFrameDataProducer* data_producer) {
  framer_.set_data_producer(data_producer);
}
  • 以此可以看出QuicSessionQuicFramer模块数据封包后的数据消费者

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(访问者)设计模式

©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容