iT邦幫忙

2022 iThome 鐵人賽

DAY 14
0
Modern Web

快還要更快,讀作 quick 的下一代傳輸層協定: QUIC系列 第 14

【Day 14】Address Validation 機制(二): 透過 Retry packet 傳送 token

  • 分享至 

  • xImage
  •  

前言

前一天的最後提到了 Server 如果希望在正式建立連線之前驗證 Client 的地址,可以透過傳送 token 來實現,今天就來探討一下關於 token 交換的議題。

Server 把驗證的 token 傳給 Client 有兩種方式

  • 在建立連線期間由 Server 送出的 Retry packet
  • 連線中使用 NEW_TOKEN 更新 token

Retry Packet

用於驗證 Client 的 token 由 Server 產生,所以至少要有一種方式可以在 Handshake 流程中由 Server 傳給 Client,用於後續的驗證,傳送 token 的方式就是使用 Retry packet。

在第一次建立連線時,Client 會發送 Initial packet 請求建立連線,這時 Server 可以透過傳送 Retry packet 來要求 Client 進行地址驗證。 Server 產生的 token 就包含在 Retry packet 中。

Client 收到 Server 回傳的 Retry packet 後會將 token 解析出來,並且重新傳送帶有 token 的 Initial packet,接下來的流程就照原本 Handshake 的流程繼續下去。

所以 Server 今天如果收到一個從 Client 端傳送的 Initial packet 並且裡面帶有剛剛包含在 Retry packet 的 token 時,就禁止再回傳 Retry 而只能選擇

  • 拒絕 Client 的連線請求
  • 繼續連線流程

如果 Server 端收到包含無效 token 的 Initial packet,Server 端可以丟棄封包,關閉連線,並觸發 INVALID_TOKEN 錯誤。

Token 作為 0-RTT 連線使用

通過 Retry packet 獲得的 token 只能在驗證地址當下使用,不能用於後續連線。如果是想利用 token 驗證後續 0-RTT 連線的場景,Server 必須要使用 NEW_TOKEN frame 來發送一個可以供後續使用的 token,在後續 Client 端要傳 Initial packet 建立 0-RTT 連線時,都必須要將這個 token 包含其中。

但也有一種場景是 Client 端發送包含有舊的 token 的 Initial packet 不過收到 Server 的 Retry packet,這種情況從 Retry packet 中獲得的新 token 必須要把舊的覆蓋。

不論如何,如果 token 是透過 Retry packet 的方式獲得,這個 token 都無法用於後續建立連線的驗證中。

Server 端必須確保每次發送 NEW_TOKEN frame 中的 token 對於單一 Client 是唯一的,不能出現兩個 Client 獲得相同 token 的情況。

通過 NEW_TOKEN frame 生成的 token 必須要設定過期時間,關於這部份的機制供實作者自己決定,可以是把過期時間儲存在 Server 端,也可以把過期時間直接通過加密的方式包含在 token 中。

由於篇幅問題, Token 的處理沒辦法仔細介紹實作,可以參考 ngtcp2 的 server.cc

int Server::verify_retry_token(ngtcp2_cid *ocid, const ngtcp2_pkt_hd *hd,
                               const sockaddr *sa, socklen_t salen) {
  std::array<char, NI_MAXHOST> host;
  std::array<char, NI_MAXSERV> port;

  if (auto rv = getnameinfo(sa, salen, host.data(), host.size(), port.data(),
                            port.size(), NI_NUMERICHOST | NI_NUMERICSERV);
      rv != 0) {
    std::cerr << "getnameinfo: " << gai_strerror(rv) << std::endl;
    return -1;
  }

  if (!config.quiet) {
    std::cerr << "Verifying Retry token from [" << host.data()
              << "]:" << port.data() << std::endl;
    util::hexdump(stderr, hd->token.base, hd->token.len);
  }

  auto t = std::chrono::duration_cast<std::chrono::nanoseconds>(
               std::chrono::system_clock::now().time_since_epoch())
               .count();

  if (ngtcp2_crypto_verify_retry_token(
          ocid, hd->token.base, hd->token.len, config.static_secret.data(),
          config.static_secret.size(), hd->version, sa, salen, &hd->dcid,
          10 * NGTCP2_SECONDS, t) != 0) {
    std::cerr << "Could not verify Retry token" << std::endl;

    return -1;
  }

  if (!config.quiet) {
    std::cerr << "Token was successfully validated" << std::endl;
  }

  return 0;
}

這段程式碼會在 Server 收到 Client 封包的時候會呼叫來驗證 token,有興趣的讀者可以試著 trace 看看。


上一篇
【Day 13】Address Validation 機制(一): 防止流量放大攻擊
下一篇
【Day 15】Connection Migration(一): 簡介
系列文
快還要更快,讀作 quick 的下一代傳輸層協定: QUIC23
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言