使用Chromium quic_toy配置TLS握手的经验

Quic Toy中默认使用的是QuicCrypto握手,现在尝试将其配置为TLS1.3握手。
首先应当让version支持TLS,从quic的流程看,version的设置在QUICToyClient::SendRequestAndPrintResponse中完成。

  quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions();

  std::string quic_version_string = GetQuicFlag(FLAGS_quic_version);
  const int32_t quic_ietf_draft = GetQuicFlag(FLAGS_quic_ietf_draft);
  if (quic_ietf_draft > 0) {
    quic::QuicVersionInitializeSupportForIetfDraft(quic_ietf_draft);
    if (quic_version_string.length() == 0) {
      quic_version_string = "T099";
    }
  }
  if (quic_version_string.length() > 0) {
    if (quic_version_string[0] == 'T') {
      // ParseQuicVersionString checks quic_supports_tls_handshake.
      SetQuicFlag(FLAGS_quic_supports_tls_handshake, true);
    }
    quic::ParsedQuicVersion parsed_quic_version =
        quic::ParseQuicVersionString(quic_version_string);
    if (parsed_quic_version.transport_version ==
        quic::QUIC_VERSION_UNSUPPORTED) {
      return 1;
    }
    versions.clear();
    versions.push_back(parsed_quic_version);
    quic::QuicEnableVersion(parsed_quic_version);
  }

  if (GetQuicFlag(FLAGS_force_version_negotiation)) {
    versions.insert(versions.begin(),
                    quic::QuicVersionReservedForNegotiation());
  }

Version包含两个部分,Transport_version与Handshake_protocol。
从GDB信息看,versions只保存了QUICCrypto部分,TLS全部被过滤掉了。
过滤的TLS算法的逻辑在:

quic::ParsedQuicVersionVector versions = quic::CurrentSupportedVersions();
  ......
  ......
ParsedQuicVersionVector CurrentSupportedVersions() {
  return FilterSupportedVersions(AllSupportedVersions());
}
ParsedQuicVersionVector FilterSupportedVersions(
    ParsedQuicVersionVector versions) {
  ParsedQuicVersionVector filtered_versions;
  filtered_versions.reserve(versions.size());
  for (ParsedQuicVersion version : versions) {
    if (version.handshake_protocol == PROTOCOL_TLS1_3 &&
        !GetQuicFlag(FLAGS_quic_supports_tls_handshake)) {
      continue;
    }
    if (version.transport_version == QUIC_VERSION_99) {
      if (GetQuicReloadableFlag(quic_enable_version_99)) {
        filtered_versions.push_back(version);
      }
    } else if (version.transport_version == QUIC_VERSION_48) {
      if (GetQuicReloadableFlag(quic_enable_version_48_2)) {
        filtered_versions.push_back(version);
      }
    } else if (version.transport_version == QUIC_VERSION_47) {
      if (GetQuicReloadableFlag(quic_enable_version_47)) {
        filtered_versions.push_back(version);
      }
    } else if (version.transport_version == QUIC_VERSION_39) {
      if (!GetQuicReloadableFlag(quic_disable_version_39)) {
        filtered_versions.push_back(version);
      }
    } else {
      filtered_versions.push_back(version);
    }
  }
  return filtered_versions;
}

可见如果要enable TLS1_3,需要使能 FLAGS_quic_supports_tls_handshake。
从上面的QUICToyClient::SendRequestAndPrintResponse代码片段,可以看到:

 if (quic_version_string.length() > 0) {
    if (quic_version_string[0] == 'T') {
      // ParseQuicVersionString checks quic_supports_tls_handshake.
      SetQuicFlag(FLAGS_quic_supports_tls_handshake, true);
    }
    quic::ParsedQuicVersion parsed_quic_version =
        quic::ParseQuicVersionString(quic_version_string);
    if (parsed_quic_version.transport_version ==
        quic::QUIC_VERSION_UNSUPPORTED) {
      return 1;
    }
    versions.clear();
    versions.push_back(parsed_quic_version);
    quic::QuicEnableVersion(parsed_quic_version);
  }

如果quic_version设置为了'T'开头,那会设置TLS FLAG,并且清除掉先前所有的version(QUIC_CRYPTO)。
而quic_version由FLAGS_quic_version决定:

std::string quic_version_string = GetQuicFlag(FLAGS_quic_version);

在quic_toy_client.cc文件中有定义FLAGS_quic_version:

DEFINE_QUIC_COMMAND_LINE_FLAG(
    std::string,
    quic_version,
    "",
    "QUIC version to speak, e.g. 21. If not set, then all available "
    "versions are offered in the handshake. Also supports wire versions "
    "such as Q043 or T099.");

是否设置这个值为T099就可以了?我们还缺少服务端的支持,先暂停客户端,去看看服务端的配置。
在quic_simple_server_bin.cc中,对应用程序参数的解析如下:

  if (line->HasSwitch("quic_ietf_draft")) {
    if (!base::StringToInt(line->GetSwitchValueASCII("quic_ietf_draft"),
                           &FLAGS_quic_ietf_draft)) {
      LOG(ERROR) << "--quic_ietf_draft must be an integer\n";
      return 1;
    }
    if (FLAGS_quic_ietf_draft > 0) {
      quic::QuicVersionInitializeSupportForIetfDraft(FLAGS_quic_ietf_draft);
      quic::QuicEnableVersion(quic::ParsedQuicVersion(quic::PROTOCOL_TLS1_3,
                                                      quic::QUIC_VERSION_99));
    }
  }

嗯,看来只要指定--quic_ietf_draft就可以打开TLS握手,但是这样的话就必须要用IETF QUIC。
先用IETF QUIC试试,在server侧配置IEFT选项

./quic_server --quic_response_cache_dir=/tmp/quic-data/www.example.org --certificate_file=./leaf_cert.pem --key_file=./leaf_cert.pkcs8 --quic_ietf_draft=18

在Client侧,通过配置

DEFINE_QUIC_COMMAND_LINE_FLAG(
    int32_t,
    quic_ietf_draft,
    0,
    "QUIC IETF draft number to use over the wire, e.g. 18. "
    "By default this sets quic_version to T099. "
    "This also enables required internal QUIC flags.");

我错误的将这里的0修改为了1,结果出现错误:

Server talks QUIC, but none of the versions supported by this client: ff000001

奇怪的为什么版本号变成了ff000001。我调试了代码,发现version里面的值是正确的,但是ParsedQuicVersionVectorToString(versions)函数解析成为ff000001。
而在服务端,我调试看到,在QuicDispatcher::ProcessPacket函数中,对packet_info的解析变成了0, 0。
抓包,发现version字段里面也是ff00001,说明客户端发送出来的时候就是该值。
那为什么会编程ff00001呢,答案在ParsedQuicVersionVectorToString函数,该函数实现如下:

switch (parsed_version.transport_version) {
    case QUIC_VERSION_39:
      return MakeVersionLabel(proto, '0', '3', '9');
    case QUIC_VERSION_43:
      return MakeVersionLabel(proto, '0', '4', '3');
    case QUIC_VERSION_46:
      return MakeVersionLabel(proto, '0', '4', '6');
    case QUIC_VERSION_47:
      return MakeVersionLabel(proto, '0', '4', '7');
    case QUIC_VERSION_48:
      return MakeVersionLabel(proto, '0', '4', '8');
    case QUIC_VERSION_99:
      if (parsed_version.handshake_protocol == PROTOCOL_TLS1_3 &&
          GetQuicFlag(FLAGS_quic_ietf_draft_version) != 0) {
        return MakeVersionLabel(0xff, 0x00, 0x00,
                                GetQuicFlag(FLAGS_quic_ietf_draft_version));
      }
      return MakeVersionLabel(proto, '0', '9', '9');
    case QUIC_VERSION_RESERVED_FOR_NEGOTIATION:
      return CreateRandomVersionLabelForNegotiation();
    default:
      // This is a bug because we should never attempt to convert an invalid
      // QuicTransportVersion to be written to the wire.
      QUIC_BUG << "Unsupported QuicTransportVersion: "
               << parsed_version.transport_version;
      return 0;
  }

可见如果是TLS,且开启了IETF,那么就会设置为ff00000x格式,在quic_toy_client.c中我设置为了1,所以出现ff000001。把这个值修改为18,握手成功!
抓包如下:


image.png

第二种尝试:
在quic_toy_client.cc中,将quic_ietf_draft选项配置为0。服务端去掉--quic_ietf_draft=18启动参数。
通过打印,发现client已经使用了T099,服务端也打印支持T099。
握手,出现Error: QUIC_NETWORK_IDLE_TIMEOUT错误。
抓包发现还是IETF的报文,报文里面的version被wireshark解析为Unknown。在服务端查看是在QuicDispatcher::MaybeDispatchPacket函数中被丢弃了,因为server_connection_id.length()检查不通过。
将版本号修改为T048,出现版本不支持问题,原因是supported_version检查不合格。

bool QuicDispatcher::IsSupportedVersion(const ParsedQuicVersion version) {
  for (const ParsedQuicVersion& supported_version :
       version_manager_->GetSupportedVersions()) {
    if (version == supported_version) {
      return true;
    }
  }
  return false;
}

从打印看,支持的版本有:46,43,39版本的QuicCrypto握手,而我们发出的是48版本的TLS握手。
version_manager_的构造函数会过滤掉一些Flag没有enable的版本,其中48版本的Flag为quic_enable_version_48,在server启动前设置此Flag。
重新运行,结果没变还是版本不支持。从打印看,虽然48版本支持了,但是只支持QUICCRYPTO握手。因为如果使用TLS,需要enable FLAGS_quic_supports_tls_handshake,再设置一下server的flag,成功!
抓包看:版本号wireshark还是解析为未知。
另外,从打印的版本号看,48之前的版本,只支持QUIC CRYPTO,48支持TLS,99也支持TLS,且使用48版本是用的IETF格式。
为什么只有48版本以上才支持TLS呢,答案在quic::AllsupportedVersions()中

ParsedQuicVersionVector AllSupportedVersions() {
  ParsedQuicVersionVector supported_versions;
  for (HandshakeProtocol protocol : kSupportedHandshakeProtocols) {
    for (QuicTransportVersion version : kSupportedTransportVersions) {
      if (protocol == PROTOCOL_TLS1_3 &&
          !QuicVersionUsesCryptoFrames(version)) {
        // The TLS handshake is only deployable if CRYPTO frames are also used.
        continue;
      }
      supported_versions.push_back(ParsedQuicVersion(protocol, version));
    }
  }

这里支持的版本为kSupportedHandshakeProtocols与kSupportedTransportVersions的组合。
但是有一个条件,就是如果使用TLS1.3,那必须要满足QuicVersionUsesCryptoFrames(version)

// Returns whether |transport_version| uses CRYPTO frames for the handshake
// instead of stream 1.
QUIC_EXPORT_PRIVATE inline bool QuicVersionUsesCryptoFrames(
    QuicTransportVersion transport_version) {
  return transport_version >= QUIC_VERSION_48;
}

所以,只有48版本及以上才使用TLS1.3。
遗留问题:为什么TLS1.3必然使用的是IETF Quic格式,这个还没明白。

你可能感兴趣的:(使用Chromium quic_toy配置TLS握手的经验)