TT Lab
はじめる
学ぶ 学習パス コース

ネットワーク基礎 — Linux VM で手を動かす

HTTP はテキストだ — そして層を順に見る

TT Labで続きを見る

一言でいうと

HTTPリクエストは、リクエスト行・ヘッダー・空行の3つの部分から成るテキストなので、ncで手書きできます。レスポンスのステータスコードは「どの層まで成功したか」を示します。404はTCPもHTTPも成功したあとの答えで、refusedはサーバーがないという意味です。

なぜ必要なのか

ブラウザーやSDKがHTTPを隠してくれている間は楽です。しかし、プロキシがヘッダーを書き換えたり、Hostが間違って見当違いのサイトが表示されたり、リダイレクトがA→B→Aと回ったりするときは、隠されているものを見る必要があります。リクエストを一度手で書いたことのある人は、curl -vの>と<が何かを知っています。それがAPIデバッグの半分です。

そしてネットワークの診断は、結局のところ層を順番に切り分ける作業です。リンク → アドレス → ネイバー → 経路 → 名前 → ポート → ファイアウォール → レスポンス。各層には、その層だけを見るコマンドがあります。順序なしに手当たり次第コマンドを打つと、「すべて正常なのにつながらない」に閉じ込められます。

どう動くのか

リクエストは、GET /docs/guide.txt HTTP/1.1の1行、Host: localhostのようなヘッダー数行、そして空行です。行末はCRLFです。空行が「ヘッダーの終わり」なので、書き忘れるとサーバーはヘッダーがまだ続くと思って待ち続けます。HostはHTTP/1.1で唯一の必須ヘッダーです。1台のサーバーが複数のサイトを受け持つので、どのサイトかをこれで選びます。Connection: closeはレスポンスのあとで切れという意味で、なければ接続を再利用します(keep-alive)。

レスポンスは、HTTP/1.0 200 OKのステータス行、ヘッダー、空行、本文です。2xxは成功、3xxは別の場所にある(Locationをたどってください)、4xxはリクエスト側の誤り、5xxはサーバー側の誤りです。HEADはヘッダーだけを受け取り、ディレクトリをスラッシュなしで要求すると、301でスラッシュ付きの場所を指します。

診断の順序をコマンドで書くと、次のとおりです。ip -br link(リンクがUPか) → ip -br addr(アドレスがあるか) → ip neigh(ネイバーがFAILEDか) → ip route get(どこへ出ていくか) → getent hosts(名前が解決できるか、digでサーバーを別に確認) → ss -ltn(ポートが開いているか) → nft list rulesetのcounter(ファイアウォールが数えているか) → curl -v(レスポンスは何か)。前の段階が合っていて初めて、後ろの段階に意味があります。

現場での姿

pingは通るのに開かない。pingはICMPで、サービスはTCPです。ファイアウォールはプロトコルごとに扱いが違います。逆に、pingが通らないからといって切断されているわけでもありません。このコースの最後のラボは、設計上ICMPをブロックします。診断は、実際のサービスと同じプロトコル・ポートで行います。

digは通るのにcurlは通らない。/etc/hostsに古いアドレスが残っています。digはサーバーに直接尋ね、curlはlibcを経由してhostsを先に見ます。同じ名前に2つの答えがあるのです。

ステータスコードで層を切り分ける

「つながらない」という報告を受けたとき、返ってきた結果1つで、どこまで成功したかを判定できます。この対応を覚えておくと、調査範囲がすぐに絞れます。

結果 どこまで届いたか 次に見る場所
名前が見つからない DNSも越えられていない getent hosts、dig、/etc/hosts
Connection refused 相手まで届いたが、そのポートに誰もいない ss -ltnでサービスが起動しているか
タイムアウト(応答なし) どこかで静かに捨てられた ファイアウォール、セキュリティグループ、経路
TLSハンドシェイクの失敗 TCPは成功 証明書の期限・名前・中間証明書
502 / 504 プロキシまでは成功、後ろが問題 上流サービスとそのタイムアウト
404 HTTPまで全部成功 パス・Hostヘッダー・ルーティングルール
401 / 403 サーバーが自分を認識して拒否 認証情報と権限

refusedとタイムアウトの違いは、特に役に立ちます。refusedは、相手のホストが生きていて「そのポートはない」と即座に答えたものなので、ネットワーク経路は正常だという意味で、タイムアウトは答えがまったく来なかったものなので、途中で捨てられた可能性が高いです。ファイアウォールを疑うべきなのは後者であって、前者ではありません。

502と504も分けて見る必要があります。502は、上流に接続したのにおかしな答えが来たか、接続が切れたものです。504は、上流が時間内に答えなかったものです。前者は上流が死んでいるか再起動中のとき、後者は上流が生きているのに遅いときに出ます。プロキシのタイムアウトが、後ろのサービスの処理時間より短いと、正常なリクエストまで504になるので、2つの値を合わせて設定するのが基本です。

最後に、curlで試すときは、実際のクライアントと同じ条件を作る必要があります。Hostヘッダー、プロトコル(HTTP/1.1か2か)、そしてプロキシの環境変数。サーバー上ではcurlが通るのにブラウザーは通らない状況の多くは、この3つのうちのどれかが原因です。

次のラボですること

python3 -m http.serverを起動し、ncでGET・HEAD・404・301を手で作って受け取り、curl -vと突き合わせます。そのあと総合ラボで、2つのサブネット・ルーター・dnsmasq・forwardファイアウォール・マスカレードを1台のVMに構築し、「開かない」という報告をどの順序で調べるかを、レポートとして残します。