こんにちは、インフラエンジニアのryuです。
今回の記事では、TCPの3ウェイハンドシェイクについて初心者向けに解説します。WebやSSHなどの通信では、データを送る前にTCPが接続の準備をしています。
SYN、ACK、シーケンス番号など、いきなり知らない言葉が出てきて難しそうに感じますよね。ですが、全体の流れは電話で相手を呼び出す場面に置き換えると、それほど複雑ではありません。
接続が成立するまでの流れに加え、再送やタイムアウトも取り上げます。最後にはLinuxのtcpdumpやssで通信を確認してみましょう。
TCPの仕組みが分かると、サーバーへ接続できないときに、どこまで通信が進んでいるのかを判断できるようになります。では、順番に見ていきましょう。
TCPの3ウェイハンドシェイクとは?¶
TCPの3ウェイハンドシェイクとは、TCPでデータを送る前に、クライアントとサーバーが3回のやり取りをして接続を確立する仕組みです。
handshakeは握手という意味です。お互いに通信できる状態か確認してから本題へ進む、と考えると分かりやすいでしょう。
たとえば、ブラウザからWebサーバーへHTTPSでアクセスするとします。この場合、ブラウザはいきなりWebページのデータを要求するのではなく、最初にサーバーのTCP/443へ接続を試みます。
TCP接続のあとにTLSによる暗号化、さらにそのあとでHTTPのリクエストが始まります。
プロトコルという言葉にまだ慣れていない方は、先にプロトコルとは何かを解説した記事を読むと、この先の内容を理解しやすくなります。
TCPはデータが届いたことを確認する¶
TCPは、データが届いたことを確認しながら通信します。Web、SSH、メールなど、正確に届けたい通信で使われます。
UDPは接続を確立せず、速度を優先する音声やDNSなどで使われます。
違いを簡単に整理すると、次のようになります。
| 比較項目 | TCP | UDP |
|---|---|---|
| 接続前の確認 | 3ウェイハンドシェイクを行う | 行わない |
| 到達確認 | ACKで確認する | 基本的に確認しない |
| 届かなかった場合 | 再送する | TCPのような再送は行わない |
| データの順番 | シーケンス番号で整える | 順番を保証しない |
| 主な用途 | Web、SSH、メール、ファイル転送 | 通常のDNS問い合わせ、音声、動画 |
なぜ3回のやり取りが必要なのか¶
2回の確認だけでよいのでは、と疑問に思うかもしれません。3回必要なのは、クライアントとサーバーの両方が、送信と受信を行える状態だと確認するためです。
2回だけではサーバーの返事が届いたか確認できません。最後のACKで双方向の通信とシーケンス番号を確認します。
3ウェイハンドシェイクの流れ¶
ここからは、クライアントがWebサーバーへ接続する例で、3回のやり取りを詳しく見ていきます。
主なフラグはSYNとACKです。SYNは接続開始、ACKは受け取ったことを伝える合図です。
全体の流れを表にすると、次のようになります。
| 順番 | 送信方向 | TCPフラグ | 主な意味 | 接続状態 |
|---|---|---|---|---|
| 1 | クライアントからサーバー | SYN | 接続したい | SYN-SENT |
| 2 | サーバーからクライアント | SYN、ACK | 接続を受け付けた | SYN-RECEIVED |
| 3 | クライアントからサーバー | ACK | 返事を受け取った | ESTABLISHED |
3回目のACKが届くと、両者はESTABLISHEDという接続済みの状態になります。
1回目はクライアントがSYNを送る¶
最初にクライアントがサーバーへSYNを送ります。電話なら、相手を呼び出している段階です。
このときクライアントは、接続先のIPアドレスとポート番号を指定します。HTTPSなら一般的にサーバーの443番ポート、SSHなら22番ポートです。
ポート番号とIPアドレスの関係が分からない場合は、ポート番号をコマンドと一緒に学べる記事も参考にしてください。
クライアントはSYN-SENT状態になり、サーバーからの返事を待ちます。
2回目はサーバーがSYNとACKを返す¶
サーバーの対象ポートでサービスが待ち受けていれば、サーバーはSYNを受け取ります。そして、SYNとACKの2つのフラグを付けたパケットをクライアントへ返します。
接続要求を受け取り、こちらからも接続するという返事です。電話なら、相手が出た段階に近いでしょう。
サーバー側はSYN-RECEIVED状態になり、最後のACKを待ちます。
サービスが待ち受けていない場合はRSTが返ることがあります。これがConnection refusedの代表的な原因です。
3回目はクライアントがACKを返す¶
SYN、ACKを受け取ったクライアントは、最後にACKを返します。
サーバーがACKを受け取ると両方がESTABLISHEDになり、接続は完了です。
HTTPSでは、このあとにTLSハンドシェイクが始まります。TCPとは別の処理なので、混同しないようにしましょう。
シーケンス番号とACK番号の仕組み¶
3ウェイハンドシェイクでは、シーケンス番号の開始位置も伝えます。これは送信データの位置を管理し、受信側で正しい順番に並べるための番号です。
クライアントがシーケンス番号1000のSYNを送ると、サーバーはACK番号1001を返します。SYNが番号を1つ使用するため、ACK番号は受け取った番号に1を足した値です。
この関係を簡略化すると、次のようになります。
クライアント サーバー
| |
| SYN Seq=1000 |
| --------------------------------------> |
| |
| SYN,ACK Seq=5000 Ack=1001 |
| <-------------------------------------- |
| |
| ACK Seq=1001 Ack=5001 |
| --------------------------------------> |
| |
| TCP接続が成立 |
ACK番号は、次に受け取りたいシーケンス番号を表すと理解すれば大丈夫です。
TCPパケットが届かないと再送される¶
パケットは回線混雑や機器障害、ファイアウォールなどにより、途中で失われることがあります。
TCPは一定時間待ってもACKを受け取れない場合、同じ情報を再送します。
SYNが失われた場合¶
最初のSYNが途中で失われると、サーバーには接続要求が届きません。当然、サーバーからSYN、ACKも返ってこないため、クライアントはSYN-SENT状態で待ち続けます。
一定時間が経つとSYNを再送し、返事がなければ待ち時間を延ばします。OSが定めた上限を超えると接続をあきらめます。
SYN、ACKが失われた場合¶
サーバーから返したSYN、ACKが途中で失われることもあります。この場合、サーバー側はSYN-RECEIVED状態、クライアント側はSYN-SENT状態のままです。
クライアントはSYNを再送し、サーバーも必要に応じてSYN、ACKを再送します。
最後のACKが失われた場合¶
3回目のACKが失われると、クライアントは接続できたと考えますが、サーバーはSYN-RECEIVED状態のままACKを待ちます。
サーバーはSYN、ACKを再送し、クライアントがACKを返します。続くデータのACKで確認できる場合もあります。
再送タイムアウトはどのように決まる?¶
再送するまでの待ち時間は、RTOと呼ばれます。RTOはRetransmission Timeoutの略で、日本語では再送タイムアウトです。
短すぎると不要な再送が増え、長すぎるとパケット損失からの復旧が遅れます。
TCPはACKが返るまでの往復時間であるRTTと、遅延のばらつきを基にRTOを調整します。
タイムアウトするたびに待ち時間を延ばす¶
再送してもACKが来ない場合、TCPは次の再送までの時間を段階的に延ばします。一般に指数バックオフと呼ばれる仕組みで、待ち時間をおおむね倍にしていきます。
たとえば1秒、2秒、4秒というように間隔が広がります。実際の値や上限はOSと通信状態によって異なります。
これは再送によってネットワークの混雑を悪化させないためです。
データ通信中は高速再送も使われる¶
接続後のデータ通信では、RTOを待たずに再送する高速再送という仕組みもあります。受信側から同じACK番号が繰り返し届くと、途中のデータが欠けた可能性があると判断します。
送信側が3つの重複ACKを受け取ると、失われたと考えられるデータを再送します。RTOを待たずに復旧できる点が特徴です。
接続拒否とタイムアウトの違い¶
接続エラーをすべて同じものとして扱っていませんか?Connection refusedとConnection timed outでは、止まった場所が異なります。
代表的な状態を整理すると、次のようになります。
| 症状 | パケットの動き | 主な原因 |
|---|---|---|
| Connection refused | SYNに対してRSTが返る | サービス停止、待ち受けポート違い、ファイアウォールのREJECT |
| Connection timed out | SYNを送っても応答がない | 経路障害、ファイアウォールのDROP、宛先停止、戻り経路の問題 |
| 名前解決エラー | SYNを送る前に失敗 | DNS設定、ドメイン名の誤り |
| TLS証明書エラー | TCP接続後に失敗 | 証明書期限切れ、ホスト名不一致、信頼されない認証局 |
| HTTP 500 | TCPとHTTP通信は成立 | Webアプリケーション内部のエラー |
Connection refusedは、相手から拒否の返事が届いています。少なくともIP通信の経路は通っており、相手へSYNが到達した可能性が高いと判断できます。
タイムアウトは返事がない状態です。行きのSYNか、戻りのSYN、ACKが途中で破棄された可能性があります。
なお、pingが成功してもTCPの特定ポートへ接続できるとは限りません。pingとTCPでは使用するプロトコルやファイアウォールの許可条件が異なるためです。
pingコマンドの使い方を解説した記事もあわせて確認してみてください。
tcpdumpで3ウェイハンドシェイクを確認する¶
仕組みを読んだあとは、実際の通信を見ると一気に理解しやすくなります。Linuxではtcpdumpを使ってTCPパケットを取得できます。
まず、次のコマンドでTCP/443の通信を監視します。インターフェース名を指定せず、利用可能なすべてのインターフェースを対象にする例です。
sudo tcpdump -nn -i any tcp port 443
-nnはIPアドレスとポート番号を数字のまま表示するオプションです。
別のターミナルを開き、curlでHTTPSサイトへ接続します。
curl -I https://example.com/
tcpdumpには、次のような流れが表示されます。IPアドレスとシーケンス番号は環境によって変わります。
192.168.1.10.52000 > 93.184.216.34.443: Flags [S], seq 1000
93.184.216.34.443 > 192.168.1.10.52000: Flags [S.], seq 5000, ack 1001
192.168.1.10.52000 > 93.184.216.34.443: Flags [.], ack 5001
[S]がSYN、[S.]がSYNとACK、[.]がACKです。この3行があれば、ハンドシェイクが成立したと判断できます。
本番サーバーでは大量のパケットが流れます。対象IPやポートを絞り、必要な時間だけ取得しましょう。
LinuxコマンドでTCPの状態を調査する¶
tcpdump以外にも、接続状態や再送を確認できるコマンドがあります。ここでは、トラブル対応で使いやすいものを紹介します。
ssコマンドで接続状態を確認する¶
ssを使うと、現在のTCP接続と状態を確認できます。
ss -tan
LISTENは接続待ち、SYN-SENTは返事待ち、ESTABは接続済みです。
SYN-SENT状態だけに絞る場合は、次のように実行できます。
ss -tan state syn-sent
SYN-SENTのままなら、SYN、ACKが戻っていない可能性があります。tcpdumpを併用すると再送も確認できます。
curlで接続時間を制限する¶
Webサーバーの調査では、curlの詳細表示が便利です。接続タイムアウトも明示しておくと、調査コマンドが長時間止まることを防げます。
curl -v --connect-timeout 5 https://example.com/
--connect-timeout 5はcurlが接続を待つ上限です。TCP自体の再送設定を変えるものではありません。
nstatで再送回数を確認する¶
Linux全体でTCPの再送が増えていないか確認するには、nstatを利用できます。
nstat -az | grep TcpRetransSegs
TcpRetransSegsは再送されたTCPセグメントの累計です。短い間隔で確認し、急増していないかを見ます。
実務で感じたTCP調査のポイント¶
私はエンジニアとして10年、接続トラブルを調査してきました。大切なのは、接続できないという現象を一つの問題として見ないことです。
実務では、まず名前解決ができたか、次にTCP接続が成立したか、そのあとTLSやHTTPまで進んだかを順番に分けます。この順番で確認するだけで、調査対象をかなり絞れます。
たとえばHTTP 500が返っているなら、3ウェイハンドシェイクはすでに完了しています。そこでルーターやTCP再送ばかり調べても、原因へは近づきにくいでしょう。
反対に、SYNを何度送ってもSYN、ACKが戻らないなら、アプリケーションのログより先に、ファイアウォール、ルーティング、ロードバランサー、サーバーの待ち受け状態を確認します。
両側でパケットを見ることも重要です。SYNがサーバーで見えなければ途中経路、SYN、ACKがクライアントで見えなければ戻り経路を疑えます。
エラーメッセージ、TCPの状態、実際のパケットを組み合わせることが、思い込みを減らす近道です。設定をすぐ変更するのではなく、どの段階で止まっているかを確認してから対応しましょう。
3ウェイハンドシェイクのよくある疑問¶
ここまでの内容を踏まえて、初心者の方が疑問に感じやすい点を整理します。
3ウェイハンドシェイクは毎回行われますか?¶
新しいTCP接続を作るときに行われます。ただし、HTTPのKeep-Aliveやコネクションプールで既存のTCP接続を再利用する場合、リクエストのたびに新しい3ウェイハンドシェイクを行うわけではありません。
HTTP/3ではTCPではなくQUICが使われます。
4ウェイハンドシェイクではないのですか?¶
サーバーはSYNとACKを1つにまとめて返せるため、接続開始は3回で完了します。
一方、TCP接続を終了するときは、双方がそれぞれ終了を伝える必要があるため、FINとACKを使った4回のやり取りになることがあります。接続開始と終了では流れが異なります。
タイムアウト時間を短く変更してもよいですか?¶
アプリケーションの接続タイムアウトは、利用目的に合わせて設定する必要があります。ただし、LinuxカーネルのTCP再送回数やタイマーを理由なく変更するのはおすすめしません。
短くしすぎると、遅いだけの正常な通信まで失敗します。パケットやメトリクスを確認してから調整しましょう。
TCPの3ウェイハンドシェイクまとめ¶
今回の記事では、TCPの3ウェイハンドシェイクと再送、タイムアウトについて解説しました。
重要なポイントを最後に整理します。
- TCP接続はSYN、SYNとACK、ACKの3回で確立する
- シーケンス番号とACK番号でデータの位置と到達を管理する
- ACKが届かなければRTOを基準に再送し、待ち時間は段階的に延びる
- Connection refusedとタイムアウトでは、通信が止まった場所が異なる
- tcpdump、ss、curlを組み合わせると実際の状態を確認できる
3ウェイハンドシェイクは、ネットワークの教科書だけに登場する知識ではありません。WebやSSHへ接続できないとき、原因を正しく切り分けるための実践的な土台です。
最初はSYN、ACKという単語だけでも構いません。実際にtcpdumpで3つのパケットを確認しながら、少しずつ通信の流れを身につけていきましょう。