勉強のためにHTTPリクエストの詳細を確認した時のメモ。
参考
環境
- サーバー:Amazon Linux AMI 2016.03
- クライアント:Mac OS X 10.11.4
クライアントからサーバーに対してHTTPのGETリクエストを送信し、TCPのハンドシェイク、HTTPリクエスト、HTTPレスポンス、TCP切断の部分を確認する。
サーバー側の準備
# webサーバーのインストール
$sudo yum install httpd -y
# tcpdumpのインストール
$sudo yum install tcpdump -y
また、文字だけのhtmlを作成します。
Hello World!
サービスを起動します。
$sudo service httpd start
サーバー側でパケットをキャプチャする
サーバー側で以下のコマンドでeth0のパケットのキャプチャを開始します。
$sudo tcpdump -Z root -i eth0 -s 0 -w /tmp/dump_eth0.pcap
上記タイミングでMacからEC2のPublicIPにhttpでアクセスしてみます。
その後、Ctrl+Cでtcpdumpを終了し、scp
コマンドでMacへファイルをコピーしてきます。
Wiresharkで確認してみる
確認していきましょう。
No6-No8(3ウェイハンドシェイク)
いわゆるTCP接続の際に必要な3ウェイハンドシェイクというものです。
この作業によってTCPの接続を確立後、HTTP通信を行います。
No6からは以下の内容が読み取れます
- クライアントからサーバーに対するTCPの通信を実施
- クライアントはランダムに割り当てられた56699ポートを利用
- サーバーへはHTTPプロトコルのデフォルトポートの80への通信
- SYN=1とすることでサーバー側に「今からクライアントからサーバーへ通信を行っても問題ないか」という事を確認する
- 送信用シーケンス番号(Sequence number)は0を設定。送信元が送る全データのうち,このデータが何番目のセグメントであるかを表す。(宛先ホストは,この番号に送信元が送ってきたデータの和に1を加えた数値をACKフィールドに入れ込んで返すことで,送信元はこのセグメントが無事相手に届いたことを確認)通常は先に自分が送信した「シーケンス番号+前回送信したセグメントのペイロードデータ量」が返却されることを想定。ただし、接続確率時(TCPハンドシェイクなど)はペイロードデータ量が0となり、シーケンス番号+1された値が返却されることを想定。なお、[relative sequecnce nubmber]と書いてあるように本当はランダムに割り当てられた数字が設定されますが、分かりにくいのでwiresharkでセッションの中での相対的な値を設定しています。
次にNo7を見てみます。
- サーバーからクライアントに対するTCPの通信を実施
- クライアントは5699ポートを利用
- サーバーは80ポートを利用
- ACK=1とすることでNo6に対して「今から通信をしても大丈夫ですよ」という旨を返却
- SYN=1とすることで「今からサーバーからクライアントへ通信を行っても問題ないか」ということを確認する
- 送信用シーケンス番号(Sequence number)は0を設定
- 確認応答番号(Acknowledgetment number)は1として返却。No6でシーケンス番号が0として送信され、正しく受信できたので+1した「1」を設定し、返却している
次にNo8を見てみます。
- クライアントからサーバーに対するTCPの通信を実施
- クライアントは5699ポートを利用
- サーバーは80ポートを利用
- ACK=1とすることでNo7に対して「今から通信をしても大丈夫ですよ」という旨を返却
- 送信用シーケンス番号(Sequence number)は1を設定
- 確認応答番号(Acknowledgetment number)は1として返却。No7でシーケンス番号が0として送信され、正しく受信できたので+1した「1」を設定し、返却
No9-10(HTTPプロトコルのGETメソッドリクエスト)
TCPの3ウェイハンドシェイクが終了し、TCPによる接続ができることが確認できたのでHTTPプロトコルによる通信を行います。
No9を見てみます。こちらはHTTPプロトコルのGETメソッドのリクエストになります。
- クライアントからサーバーに対するHTTP(TCP)の通信を実施
- クライアントは5699ポートを利用
- サーバーは80ポートを利用
- HTTPプロトコルが追加されている。今回はHTTPリクエストが追加。詳細は後述
- シーケンス番号1として設定。[Next Sequene number:323]というWiresharkで補完してくれた情報が表示されている。今まではTCP Segment Lenが0だったので返却される確認応答番号は1だったが、今回はセグメントのペイロードサイズが322だったのサーバー側が正しく情報を受信できてればサーバーからの通信時に確認応答番号が323となるということを想定していることを意味する。
HTTPリクエストの基本概要を以下に記載します。
- 1行目はリクエストラインと呼ばれ、「[メソッド] [パス] [HTTPのバージョン]」それぞれがスペースで区切られている。今回の例では「GET / HTTP1.1¥r¥n」と指定されているのでGETメソッドでパスは/、HTTPバージョンは1.1を要求していることを意味します。
- 2行目以降、空行+¥r¥nまではHTTPヘッダとなります。HTTPヘッダでは「Name: value¥r¥n」という形式で値を設定します。Hostでは通信先のドメイン名やIPアドレスを指定します。
- HTTPヘッダ以降はHTTPのボディとなります。今回はGETメソッドで何も送信していないので空となります。(Wiresharkで[]となっているものはWiresharkが情報を保管しているものなので実際に通信時にはないもの)
次にNo10を見てみます。
こちらはHTTPプロトコルではなくTCPプロトコルですね。
- サーバーからクライアントに対するTCPの通信を実施
- クライアントは5699ポートを利用
- サーバーは80ポートを利用
- 確認応答番号として323を設定。これをクライアントに送信するとことでNo9の通信が問題なく、サーバーで受信できたことを示す
確認応答番号を323で返却する事でHTTPリクエストが問題なく届いたことをクライアントに伝えるトラフィックとなっています。
No11,13(HTTPプロトコルのレスポンス)
先ほどのリクエストに対するHTTPレスポンスを返却します。
- サーバーからクライアントに対するHTTP(TCP)の通信を実施
- クライアントは5699ポートを利用
- サーバーは80ポートを利用
- HTTPプロトコルが追加されている。今回はHTTPレスポンスが追加。詳細は後述
- シーケンス番号1として設定。[Next Sequene number:280]というWiresharkで補完してくれた情報が表示されているので次に確認応答番号で280が返却されることを想定(TCP Segment Lenが279なので)
HTTPレスポンスの基本概要を記載します。
- 1行目はステータス行。「[HTTPバージョン] [ステータスコード] [ステータスコードの説明]¥r¥n」という書式になる。今回の例では「HTTP/1.1 200 OK¥r¥n」ということでHTTPバージョンは1.1,ステータスコードは200,OKというステータスコードの説明
- 2行目以降空行+¥r¥nまでがレスポンスヘッダ。HTTPリクエストヘッダと同じ書式(Name: value¥r¥n)で設定
- 上記以降がレスポンスのボディ。「Line-based text data: text/html」の下の「Hello World¥n」が最初に設定したHTMLの内容でこれが返却された。
No12はコネクション切断なのでNo13を先にみます。
No10と同じようにHTTPレスポンスに対してのクライアントからの返却となります。
こちらもHTTPプロトコルではなくTCPプロトコルですね。
- クライアントからサーバーに対するTCPの通信を実施
- クライアントは5699ポートを利用
- サーバーは80ポートを利用
- 確認応答番号として280を設定。これをクライアントに送信するとことでNo11の通信が問題なく、クライアントで受信できたことを示す
意味としてはNo10と同じですね。
No12,14,15,16(TCPコネクションの切断)
HTTPの通信が終わったのでTCPコネクションを切断します。
接続開始の際は3回の通信が必要でしたが、切断は4回の通信が必要です。
No12を見てみます。
サーバーからクライアントへの切断要求となっており、FIN=1となっています。
- サーバーからクライアントに対するTCPの通信を実施
- クライアントは5699ポートを利用
- サーバーは80ポートを利用 +FIN=1,ACK=1でサーバーからクライアントに対してコネクションの切断をしてよいか確認をしている
- 連続した通信なのでシーケンス番号(280)及び確認応答番号(323)はNo12(HTTPレスポンス)と同じ
次にNo14を見てみます。
これはクライアントからサーバーへの通信で、No12の切断要求に承諾することを意味します。
- クライアントからサーバーに対するTCPの通信を実施
- クライアントは5699ポートを利用
- サーバーは80ポートを利用
- ACK=1とし、切断要求に対して問題ない旨を返却
- シーケンス番号はNo12で取得した応答確認番号の323で、確認応答番号がNo12のシーケンス番号+1をした281となる
次にNo15を見てみます。
こちらもNo14に続き、クライアントからサーバーへの通信となっております。
サーバー->クライアントの切断要求は終わったので次にクライアント->サーバーへの切断要求を行っています。
- クライアントからサーバーに対するTCPの通信を実施
- クライアントは5699ポートを利用
- サーバーは80ポートを利用
- FIN=1,ACK=1とし、切断要求を行っている
- シーケンス番号(323)及び確認応答番号(281)はNO14と同じ
最後にNo16を見てみます。
これはサーバーからクライアントに対する通信で、No15で要求されたクライアント->サーバー間の接続を切ることの承諾となります。
- サーバーからクライアントに対するTCPの通信を実施
- クライアントは5699ポートを利用
- サーバーは80ポートを利用
- ACK=1とし、切断要求を承認する
- シーケンス番号は相手から受信した応答番号なので281となる
- 確認応答番号は受信したシーケンス番号+1なので324となる
まとめ
自分の中で知らなかったことなどをメモ。
- HTTPはステートレスなのでリクエストの都度、TCPによるコネクション確立、切断が発生する(Keep-aliveの設定をすると継続される)
- TCPではシーケンス番号と確認応答番号を利用することで通信が正しく行われているかを確認している
- HTTPのリクエスト、レスポンスに対しても送信が正しく行われたか確認するためにACK=1の通信が発生している
- サーバー側の利用するポートは明示的に指定するが(指定しない場合、HTTPではデフォルトの80を利用)クライアント側は任意のポートが割り当てられ、TCPの切断まで利用される。なお、一度TCPコネクションが切断した後は別のポートとなる。