TCP Fast Open を知る

こんにちは、C とインフラの話が多い masm11 です。 今回もインフラです。

「TCP Fast Open」をご存知でしょうか? 私は知りませんでした。 今回、この機能のせいでインターネットにつながらなくなってしまったので、 ご紹介します…

背景

世の中 HTTP でなく HTTPS が十分に広まってますね。 これは盗聴や改竄といった脅威から身を(通信内容を)守るのが目的です。

しかしこれだけでは不十分という見方もあります。 サイトにアクセスする際には、まず DNS で名前解決をしますよね。 例えば https://blog.ingage.jp/ にアクセスしたければ、まず DNS サーバに blog.ingage.jp の IP アドレスを尋ねて、それからアクセスするわけです。

ということは、その DNS サーバを管理している企業には、 どこのサイトにアクセスしようとしたか、バレてますよね? (もちろん守秘義務とかありますが、それは置いときます) 別企業が管理してる DNS サーバを使えば、プロバイダの DNS サーバそのものには ログは残らないかもしれませんが、DNS の通信が生でプロバイダを通っているので、 覗きたければ簡単です。

そこで、DNS over TLS や DNS over HTTPS といった手法が考え出されました。 DNS 通信を TLS や HTTPS で保護してやろう、というものです。

つまり、信用できる DNS サーバに DNS over TLS でアクセスすれば大丈夫だよね、という ことです。「DNS のログは採らない」と言ってた企業、どこでしたっけ… その言葉を信用するかどうかはユーザ次第です。

DNS over TLS の設定方法

Linux で DNS over TLS を使うには、systemd-resolved の設定をするのが便利です。

/etc/systemd/resolved.conf.d/dns_over_tls.conf などというファイルを作り、

[Resolve]
DNS=8.8.8.8#dns.google 8.8.4.4#dns.google
DNSOverTLS=yes

と設定します。あとは /etc/resolv.conf を systemd (127.0.0.53) に向ければ使えます。

現時点では systemd は DNS over HTTPS には非対応のようです。

TLS なので、SSL 証明書を検証することになります。

通常、HTTPS 通信をしたければ、

  1. DNS でサーバ名から IP アドレスを解決する
  2. その IP アドレスに接続し、TLS handshake する
  3. SSL 証明書を検証する。つなぎたいサーバ名が記載されていることを確認する

という手順を踏みます。サーバ名は最初から判っていますね。

しかし、DNS を使う際には、DNS サーバとしては IP アドレスを指定します。 8.8.8.8 と指定しただけではサーバ名が判らず、SSL 証明書を検証することができません。

そこで、DNS over TLS/HTTPS サービス提供元がサーバ名を公開しています。 8.8.8.8 であれば dns.google です。このサーバ名を systemd-resolved に 設定しておくわけです (#dns.google の部分)。 SSL 証明書にそう書いてあるはずなので、 systemd-resolved がそれを確認してくれる、というわけです。

TCP Fast Open とは

さて、少しの間 DNS over TLS を使っていたのですが、ある時、どのサイトにもつながらなくなり、 調べたら DNS over TLS で問題が起きているようでした。 すぐ収まるだろうと思って、とりあえず素の DNS を使うようにしていたのですが、 しばらく経ってもう一度試したらまだ問題が起きたままだったので、 調べることにしました。

8.8.8.8 を使うとだめなのですが、1.1.1.1 だと大丈夫であることに気づきました。

8.8.8.8 の場合と 1.1.1.1 の場合で、パケットに何か違いがあるはずです。 wireshark でじっくり調べたところ、 8.8.8.8 の場合は SYN パケットが見当たらない…? いえ、TLS handshake の ClientHello に SYN フラグが立っています。

f:id:masm11:20220223211432p:plain

一方、1.1.1.1 の場合は普通に SYN / SYN+ACK / ACK の 3-way handshake の後に TLS handshake が行われています。

f:id:masm11:20220223211452p:plain

8.8.8.8 のこれは何でしょうか? 調べたところ、これは TCP Fast Open というもの だそうです。

素の DNS であれば、UDP を使うので、パケットは1往復で済みます。 しかし、TLS を使う場合、TCP の 3-way handshake に加えて TLS handshake も必要になります。時間がかかるのです。

それを少しでも減らすため、SYN パケットにペイロードを載せられるようにした、 ということだそうです。

調査

さて、私が勉強したサイトでは、TCP Fast Open を使った場合でも、 SYN に対して SYN+ACK が返ってくるそうですが、 私の場合は PSH+ACK が返ってきていました。

f:id:masm11:20220223211511p:plain

え!?

なので、SYN+ACK が返ってこないと見なされ、何度も SYN (+ペイロード) を 送っていたようです。

f:id:masm11:20220223211525p:plain

これは一体… Google が何か間違ってる?

試しにマンションのネットではなくスマホのテザリングにしてみたところ、 問題ありませんでした。ちゃんと SYN+ACK が返ってきてます。 Google は問題なかったようです。

つまり、マンションのルータがフラグを改竄してたんですね。 TCP Fast Open に非対応ってことなのでしょう。

Cookie

もう一つ判らないことがあります。1.1.1.1 で問題ないという点です。

調べると、TCP Fast Open は、最初の接続では普通に 3-way handshake で接続するそうです。

  1. SYN。この時に TCP Fast Open 用 cookie をリクエストする
  2. SYN+ACK。この時に cookie を返す
  3. ACK

そして、次回接続時から、この cookie を使って、

  1. SYN。この時に TCP Fast Open 用 cookie をセットし、ペイロードも載せる
  2. SYN+ACK。必要であればこの時にペイロードも載せる
  3. ACK

とするそうです。

↓8.8.8.8 の場合は、SYN 時に cookie をリクエストして、

f:id:masm11:20220223211603p:plain

↓SYN+ACK で cookie が返ってきているのに対し、

f:id:masm11:20220223211616p:plain

↓1.1.1.1 の場合は、SYN 時に cookie をリクエストしても、

f:id:masm11:20220223211629p:plain

↓SYN+ACK で cookie が返ってきていませんでした。

f:id:masm11:20220223211642p:plain

このため次回以降も TCP Fast Open にならないのでした。 1.1.1.1 は TCP Fast Open に非対応ってことですね。

8.8.8.8 でも最初の名前解決だけは成功してたんですよね… 最初だけは普通に 3-way handshake するから問題なかったんですね…

ちなみに、この cookie は kernel が管理しています。 systemd-resolved を restart しても kernel が cookie を持ったままなので、 検証の際にはこの点を頭に入れておく必要があります。

まとめ

結局 1.1.1.1 を使うことにしました。 少し遅いのかもしれませんが、今のところは気になりません。

マンションのルータが新機種に交換されるなんてイベントは、あまり期待できそうにないですね…

私は TCP Fast Open なんて知りませんでしたが、 こういう新機能もしっかりキャッチアップしてるそこのあなた、 ぜひうちで一緒に仕事しませんか? 詳細は以下のページから!

https://ingage.co.jp/recruit/