【入門編】 TCPのFIN/RSTフラグによる接続終了と強制切断の挙動 – ネットワーク基礎とWebセキュリティ実践ガイド

みなさん、こんにちは!日夜、目に見えないパケットの往来に目を光らせているネットワークセキュリティスペシャリストの筆者です。

突然ですが、みなさんはインターネットでWebページを見たり、スマホアプリでデータを送受信したりするとき、その裏側で「パケット」たちがどんな会話をしているか想像したことはありますか?

ネットワークの世界は、まるで1秒間に何百万通もの手紙が行き交う巨大な郵便システムのようなものです。その中でも、私たちが普段最もお世話になっている「TCP(Transmission Control Protocol)」というプロトコルは、非常に礼儀正しく、慎重なキャラクターをしています。

今回は、このTCPが通信を終えるときの2つのドラマ、「美しくお互いに納得して通信を終えるFIN(ファイナル)」と、「トラブル発生時に一瞬で通信を叩き切るRST(リセット)」について、難しい専門用語をできるだけ身近な例えに置き換えて、一歩ずつ丁寧に紐解いていきましょう!

—

そもそもTCPってどんなプロトコル?

具体的なお話に入る前に、少しだけ「TCP」というキャラクターの性格をおさらいしておきましょう。

OSI参照モデルの「トランスポート層(レイヤー4)」で活躍するTCPは、一言でいうと「超・過保護で責任感の強い配達員」です。

  • 送ったデータが相手に届いたか、毎回必ず確認する
  • 順番がバラバラに届いたら、きれいに並べ直す
  • 途中でデータが消えてしまったら、もう一度送り直す

このように、とにかく「信頼性」を第一に考えて通信を行います。そのため、通信を始めるときも、終わるときも、お互いの意思疎通を非常に重んじる性質があります。

そのTCPが、通信の「終わらせ方」として用意しているのが、今回の主役であるFINとRSTという2つのフラグ(パケットのヘッダーに用意された、Yes/Noを示す小さなスイッチのようなもの)です。

—

1. 美しい別れの挨拶:FIN(Normal Close)

まずは、何の問題もなく通信が完了したときの「正常終了」のプロセスを見ていきましょう。ここで使われるのがFIN(Finish)フラグです。

これは、現実世界でいうと「お互いに納得して、丁寧に電話を切るプロセス」にそっくりです。

TCPでは、通信を終えるときに、なんと4回もパケットを行き来させます。専門用語で「4ウェイ・ハンドシェイク(4-way handshake)」と呼びますが、名前は難しく聞こえても、中身はとても人間味あふれる会話なんですよ。

電話の切り方に例えてみよう

Webブラウザ(クライアント)とWebサーバー(サーバー)のやり取りを、電話の会話に例えてみましょう。

クライアント(ブラウザ)               サーバー
       |                               |
       |  ①「もう用事は終わったので、  |
       |    切りますね(FIN)」         |
       |------------------------------>|
       |                               |
       |  ②「了解しました!            |
       |    ちょっと待ってね(ACK)」  |
       |<------------------------------|
       |                               |
       |    (サーバー側も片付けをする) |
       |                               |
       |  ③「お待たせしました。        |
       |    こちらも切りますね(FIN)」 |
       |<------------------------------|
       |                               |
       |  ④「了解です!                |
       |    さようなら(ACK)」        |
       |------------------------------>|
       v                               v
  [通信終了]                      [通信終了]

いかがでしょうか?驚くほど丁寧ですよね。

1. FINを送信(①): クライアントが「私はもう送るデータがありません」と宣言します。
2. ACKを返信(②): サーバーが「了解(Acknowledge)しました。でも、私からはまだ送り残しがあるかもしれないので、少し待ってくださいね」と答えます。
3. FINを送信(③): サーバーもデータの送信がすべて終わったら、「こちらも準備ができました。切りますね」と送ります。
4. ACKを返信(④): 最後にクライアントが「了解しました!」と答えて、お互いに完全にコネクション(回線)を閉じます。

このように、お互いが「もう送るものはないね?」とダブルチェックしながら綺麗に終わるのが、FINフラグを使った正常終了のプロセスです。

—

2. 突然の絶交宣言:RST(Reset)

一方で、世の中はいつも平和な正常終了ばかりではありません。突然のトラブルや、あってはならない異常事態が発生したとき、TCPは一瞬で通信を破棄します。ここで登場するのがRST(Reset)フラグです。

これは現実世界でいうと、「相手が話している最中に、いきなり受話器をガチャンと叩きつける」ような強硬手段です。

RSTパケットが送られると、受け取った側は、前後の文脈や未送信のデータがどうなっていようが、その瞬間にすべての通信処理を強制終了(破棄)します。お互いの同意も、確認の返事(ACK)も一切必要ありません。一方通行の「絶交宣言」なのです。

どんな時にRSTフラグは飛んでくるの?

では、一体どんな時にこの「受話器叩きつけ(RST)」が発生するのでしょうか?代表的なケースを3つご紹介します。

ケースA:存在しないポートに話しかけたとき

サーバーの「80番ポート(HTTP)」は開いているけれど、「8080番ポート」は閉まっているとします。そこにクライアントが「8080番で接続したいです!」とパケットを送ると、サーバーは「そんな窓口(ポート)は存在しない!お引き取りを!」と、即座にRSTを返して通信を拒絶します。

ケースB:アプリが突然クラッシュしたとき

通信の途中で、サーバー上で動いていたWebアプリケーション(ApacheやNginx、Node.jsなど)がエラーで突然異常終了(クラッシュ)してしまったとします。
OSのネットワーク機能は動いているため、クライアントから届いた次のパケットに対して「ごめん、もうその通信を処理できるアプリが死んじゃったから、この会話はなかったことにして!」とRSTを返します。

ケースC:セキュリティ機器(ファイアウォールなど)が通信を遮断したとき

企業のネットワークの境界にあるファイアウォールやIPS(侵入防止システム)が、通信の中に不正なデータや攻撃コードを検知したとします。
このとき、セキュリティ機器はクライアントとサーバーの両方に対して、相手になりすましてRSTパケットを送りつけます。すると、両者は「相手から切断された」と思い込み、一瞬で通信が遮断されます。これを「TCP Resetインジェクション」と呼び、セキュリティ対策の現場では非常によく使われるテクニックです。

—

3. 実務で役立つ!コードとパケットで見る挙動

ここからは、インフラエンジニアやプログラマーが一歩ステップアップするために、実際のコードやパケットキャプチャの視点から、この挙動を観察してみましょう。

Pythonによるソケット制御の例

プログラムからTCP接続を制御するとき、通常はclose()を呼ぶと自動的に丁寧なFINが送信されます。
しかし、オプションを設定することで、意図的にRSTを送信して強制終了させることも可能です。以下のPythonコードでその違いを見てみましょう。

import socket
import time

# 接続先の設定
target_host = "127.0.0.1"
target_port = 8080

# --- パターン1: 丁寧な正常終了 (FINを送信) ---
def normal_close():
    # TCPソケットを作成して接続
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((target_host, target_port))
    
    print("[INFO] 接続しました。データを送信します...")
    client.send(b"Hello Server")
    
    # 通常のclose。OSが自動的に4ウェイ・ハンドシェイク(FIN)を実行します
    print("[INFO] 通常のクローズを実行します (FIN)")
    client.close()

# --- パターン2: 容赦ない強制終了 (RSTを送信) ---
def graceful_reset():
    # TCPソケットを作成して接続
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect((target_host, target_port))
    
    print("[INFO] 接続しました。データを送信します...")
    client.send(b"Hello Server")
    
    # 【ここがポイント!】
    # SO_LINGERオプションを設定し、待ち時間を「0秒」にします。
    # これにより、未送信のデータがあっても即座に破棄し、FINではなくRSTを送信します。
    # struct.pack('ii', 1, 0) は「Linger有効(1), タイムアウト0秒(0)」という意味です。
    import struct
    l_onoff = 1
    l_linger = 0
    client.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack('ii', l_onoff, l_linger))
    
    print("[WARN] 強制リセットを実行します (RST)")
    client.close() # ここでRSTパケットが送信されます

実務において、大量の接続をさばくサーバーを書く際、クライアントが応答しなくなったからといってFINを待っていると、サーバーのメモリ(ソケットリソース)が枯渇してしまうことがあります。そうした時に、あえてRSTを使って「即座にコネクションを消し去る」という設計を行うことがあるのです。

パケットキャプチャ(tcpdump / Wireshark)での見え方

ネットワークのトラブルシューティングで、黒い画面(CLI)からパケットを確認する際、tcpdumpコマンドをよく使います。

正常な通信と異常な通信は、出力される「フラグ(Flags)」の部分で見分けることができます。

1. FINパケットの確認(tcpdump出力例)

# tcpdumpでFINフラグを持つパケットだけをフィルタリングして表示するコマンド
$ tcpdump -i eth0 "tcp[tcpflags] & tcp-fin != 0"

出力結果に、以下のように [F.] や [F] と表示されていれば、それがFINパケットです(.はACKがセットされていることを意味します)。

12:00:01.123456 IP 192.168.1.10.50000 > 192.168.1.20.80: Flags [F.], seq 101, ack 201, win 2048, length 0

2. RSTパケットの確認(tcpdump出力例)

# tcpdumpでRSTフラグを持つパケットだけをフィルタリングして表示するコマンド
$ tcpdump -i eth0 "tcp[tcpflags] & tcp-rst != 0"

出力結果に [R] または [R.] と表示されていれば、それは強制切断のシグナルであるRSTパケットです。

12:05:14.987654 IP 192.168.1.20.80 > 192.168.1.10.50000: Flags [R], seq 201, win 0, length 0

もし本番環境のシステムで、特定の処理の最中にこの Flags [R] が大量に記録されている場合、ネットワークの経路上の問題や、サーバー側アプリの突然死、あるいはセキュリティ機器による遮断が発生している強力な証拠になります。

—

4. まとめ:パケットの終わり方に込められた意味

最後に、今回学んだ内容をすっきりと整理しておきましょう!

| 特徴 | FIN(正常終了) | RST(強制切断) |
| :— | :— | :— |
| ニュアンス | 「お疲れ様でした。お互いに終了しましょう」 | 「何かがおかしい!今すぐ通信を白紙に戻せ!」 |
| やり取りの回数 | 往復で4回(お互いの合意が必要) | 1通送りつけて終わり(一方通行) |
| 主な発生ケース | Webページの読み込みが平和に完了したとき | ポートが閉じている、アプリのクラッシュ、FWによる遮断 |
| 例え話 | 丁寧にお礼を言い合って、そっと受話器を置く | 会話の途中で、無言で受話器を叩きつける |

ネットワークの教科書を読むと、OSI参照モデルやパケットのビット構造など、暗記しなければならない難しい言葉がたくさん並んでいて、頭が痛くなってしまうこともありますよね。

しかし、このように「パケットたちも人間と同じように、ルールを守って、時には慌てて会話をしているんだ」という視点を持つと、目の前のログやプログラムが急に身近に、そして面白く見えてくるはずです。

実務で接続エラーに遭遇したときは、ぜひ「これは丁寧なFINのすれ違いかな?それとも誰かがRSTを叩きつけているのかな?」と、パケットたちの会話に耳を傾けてみてくださいね。

一歩ずつ、楽しみながらネットワークの世界をマスターしていきましょう!

コメント

タイトルとURLをコピーしました