こちらのサイト と『マスタリングTCP/IP』を参考にゆるふわなまとめ.
OSI参照モデルとカプセル化
/ | レイヤー | データ形式 |
---|---|---|
7 | アプリケーション | X |
6 | プレゼンテーション | X |
5 | セッション | X |
4 | トランスポート | セグメント |
3 | ネットワーク | パケット |
2 | データリンク | フレーム |
1 | 物理 | ビット |
LANの機器
NIC(Network Interface Card)がPCとネットワークの窓口.
レイヤー | 機器 | 説明 |
---|---|---|
ネットワーク層 | ルーター | 送信先までの経路を決定 |
データリンク層 | ブリッジ スイッチングハブ |
近くの機器との伝送制御 |
物理層 | リピーター ハブ |
A/D伝送,モデム |
物理層
同軸ケーブルはご存じの通り.銅線を絶縁体で覆ったもの.
ツイストペアケーブルは8本の銅線を2x4でより合わせている.そうすることで磁気の干渉をキャンセルしている.
その他光ファイバなど.
データリンク層
メディアアクセス制御(MAC)を行う.具体的には,通信方式(TMDA,CMDA)に応じた誤り検出の情報などをパケットに付加してフレームをつくる.
MACアドレスはNICに各ベンダーが製造時に割り当てた固有のIDである.MACアドレスは48bit,つまり16進数で12bitである.表記するときは2x6で区切るので,dc:fb:48:14:0d:16のように表記する.
ここではLAN上のイーサネット内部で完結する通信に話を限定する.イーサネット上の情報はそのバス内すべてに伝達される仕組みになっている.つまりブロードキャストされる.しかし同軸ケーブルはベースバンド方式であるため,あるノードがブロードキャストしているときに別のノードがブロードキャストしてしまうと,信号がケーブル上で混合されてしまう.これを防ぐためにCSMA/CD(Career Sense Multiple Access Collision Detection)という制御を行う.
キャリア検知では,初めにイーサネット上に他の信号が来ていないことを確認する.そして一定時間待ったあと自分の信号を送り始める.フレームを多数送らないといけないノードは,一つ一つのフレームを一定時間待ちながら送ることになる.送信中に別のノードからのフレームを受信してしまったら,自分の送信したフレームと干渉してしまっているので,いったんフレームの送信を中止する.
イーサネット内での通信はブロードキャストされるので,当然XがYに送ったフレームはZにも届いてしまう.フレームには送信先のMACアドレスが記されているので,Zは自分宛てに届いたものなのかを確認することができる.
ネットワーク層
ネットワーク層では,パケットに送信元と受信先のIPアドレスをヘッダーに付加する.またpingで出てくるTTLも付加される.
IPv4ではIPアドレスは32bitである.IPv6では64bitである.
IPv4
RFC791での定義 による.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
そのパケットの合計サイズ,TTL,送信先・元のIPアドレスが付加される.
クラスフルアドレッシング
32bitのIPアドレスのうち,何ビットをネットワーク番号とホスト番号に使うかによってクラスが定められている.
クラス | 1stオクテット | 2ndオクテット | 3rdオクテット | 4thオクテット |
---|---|---|---|---|
クラスA | ネットワーク番号 | ホスト番号 | ホスト番号 | ホスト番号 |
クラスB | ネットワーク番号 | ネットワーク番号 | ホスト番号 | ホスト番号 |
クラスC | ネットワーク番号 | ネットワーク番号 | ネットワーク番号 | ホスト番号 |
受信側は,第一オクテットを見ることによりそのアドレスはどのクラスに属するのかを識別する.それぞれのクラスではIPアドレスは以下のようになる.
クラス | 1stオクテット | IPアドレス |
---|---|---|
A | 0xxxxxxx | 0.host.host.host - 127.host.host.host |
B | 10xxxxxx | 128.network.host.host - 191.network.host.host |
C | 110xxxxx | 192.network.network.host - 223.network.network.host |
予約済みアドレス
ホスト番号が00000000または11111111になるもの.あるネットワークを外から見たとき,そのネットワーク自体を大雑把に指定する際に利用する.ブロードキャストアドレスと呼ばれる.
プライベートIPアドレス
最近はNATが普通に使われているからよく見るIPアドレスだと思う.
1stオクテット | 2ndオクテット | 3rdオクテット | 4thオクテット | 表記 |
---|---|---|---|---|
10(00001010) | x | x | x | 10/8 |
172(10101100) | 16 - 31 | x | x | 172.16/12 |
192(11000000) | 168 | x | x | 192.168/16 |
サブネットワーク
クラスAのネットワークの管理をしているとすると,その管理者は1700万のIPアドレスを自由に割り当てることができる.しかしそれらを1から割り当てることはしない.住所のように「階層」をつくるものである.そこでホスト番号の先頭にサブネット番号と呼ばれるものを付ける.
ホスト番号の先頭を利用する.けどどこまでがサブネットワークを示しているのか分かるのだろうか?
この疑問に答えるのがサブネットマスクである.これはネットワーク部 + サブネットワーク部までのビットを1で,残りを0で埋めた32bit列である.
$ ifconfig
enp0s3: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.255
inet6 fe80::53ca:a4fe:732d:1e18 prefixlen 64 scopeid 0x20<link>
ether 08:00:27:a4:cf:a8 txqueuelen 1000
RX packets 29394 bytes 6371743 (6.3 MB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 19539 bytes 6732159 (6.7 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
上は仮想マシン上でifconfig
コマンドの出力を見たものである.これより
- IPアドレスは
10.0.2.15
つまりタイプ1のプライベートアドレスである - よってネットワーク部は1stカルテットである(
10
) - 一方サブネットマスクが3rdカルテットまで255(b'1)で埋められている
- すなわちサブネットワーク部は2nd,3rdカルテットの
0.2
である - そして純粋なホスト部は
15
である
ことが分かる.
ネットワーク | サブネットワーク | サブネットワーク | ホスト | 表記 |
---|---|---|---|---|
10. | 0. | 2. | 15 | 10.0.2/24 |
もう一つ例題.172.20.100.52/26
の場合,52 = 00110100
の上位2bit00
までがネットワーク部である.よって172.20.100.0-63
が取りうるアドレスの範囲である.したがって
- IPアドレスは
172.20.100.52/26
- ネットワークアドレスは
172.20.100.0 /26
- ブロードキャストアドレスは
172.20.100.63/26
である.
DHCP
ここまでで,パケットからフレームを作るときに
- 送信元IPアドレス
- 送信先IPアドレス
- 送信元MACアドレス
- 送信先MACアドレス
の4つが必要であることが分かる.MACアドレスは初めから各機器に固有であるが,IPアドレスは属するネットワークによって異なる.そのため愚直にこれらを揃えるには利用者がstaticにIPアドレスを決め打ちする必要がある.しかしそれでは拡張性などが損なわれるので動的にIPアドレスを取得するためのプロトコルとしてDHCPというものが用いられている.これはサーバーに「IPアドレスが欲しい」という要求を送信元IPアドレスがない状態で送るためのプロトコルである.
DHCPクライアントとサーバー
DHCPサーバーはあらかじめ割り当てるIPアドレスの範囲のプールを持っている.また割り当てるIPアドレスのリース期限も定めている.
クライアントがLAN内においてDHCPメッセージをブロードキャストすることで,サーバーがそれに応答して以下のようにやり取りを行う.
Client <-> Server | 送信先MAC | 送信元MAC | 送信先IP | 送信元IP | DHCPメッセージ |
---|---|---|---|---|---|
Client -> Server とりあえず探す |
ffff.ffff.ffff ブロードキャスト |
Clientの MAC |
255.255. 255.255 ブロードキャスト |
0.0.0.0 割り当てられていない |
DISCOVER Clnt IP: 0.0.0.0 Srv IP: 0.0.0.0 |
Server -> Client 候補を与える |
ffff.ffff.ffff ブロードキャスト |
Serverの MAC |
255.255. 255.255 ブロードキャスト |
192.168.1.100 | OFFER Clnt IP: 192.168.1.10 Srv IP: 192.168.1.100 |
Client -> Server その候補でOK! |
ffff.ffff.ffff ブロードキャスト |
Clientの MAC |
255.255. 255.255 ブロードキャスト |
0.0.0.0 割り当てられていない |
REQUEST 192.168.1.10を要求! Clnt IP: 0.0.0.0 Srv IP: 192.168.1.100 |
Server -> Client 確定 |
ffff.ffff.ffff ブロードキャスト |
Serverの MAC |
255.255. 255.255 ブロードキャスト |
192.168.1.100 | ACK サブネット情報など Clnt IP: 192.168.1.10 Srv IP: 192.168.1.100 |
ARP
Webブラウザでhttps://google.co.jp
をリクエストするとDNSサーバーがそのIPアドレスを教えてくれる.しかしその時点でまだGoogleのサーバーのMACアドレスは不明だからIP通信は成立しない.そこでARPという仕組みを用いてGoogleのサーバーのMACアドレスを知ることができる(<-説明の便宜上こう書いているが,実は嘘.デフォルトゲートウェイのMACアドレスを使う.わざわざGoogleのサーバーのMACアドレスを調べはしない).
まず(普通はGoogleのMACアドレスを初めから知っていることはないけどルールとして)クライアント自身が持つARPテーブルを参照する.
$ arp -a
_gateway (10.0.2.2) at 52:54:00:12:35:02 [ether] on enp0s3
(当然)ここにはGoogleのサーバーのIPアドレスはないので探す必要がある.
ARP要求
/ | ARPパケット内 | ARPパケット内 | ARPパケット内 | ARPパケット内 | / |
---|---|---|---|---|---|
Client <-> Server | 送信先MAC | 送信元MAC | 送信先IP | 送信元IP | 動作 |
Client -> Server |
0.0.0.0 まだ不明 |
ClientのMAC | DNSから得たIP addr | ClientのIP addr | REQUEST ブロードキャスト して調べる |
Server -> Client |
ClientのMAC | ServerのMAC | ClientのIP addr | ServerのIP addr | リクエストされた送信先IP を持つサーバーが応答 |
ARP要求をやり取りした後,お互いにARPテーブルを更新してお互いを登録する.なおGoogleのサーバーのMACアドレスを知ろうなんて愚直なことは実際にはやらない.そもそもブロードキャストメッセージはLANの外にまで出ていかない(そんなことしたらトラヒックがひっ迫する).
DNSと通信の流れ
コンピュータにはNICが取り付けられているので,それで送信元MACアドレスが決定する.次にLANに接続するとDHCPによりLANでのIPアドレスが決定する.次にDNSによりアクセスしたいFQDNのIPアドレスが分かり,ARPによりそのFQDNのサーバーのMACアドレスが分かる.これにより必要なアドレスが全て判明したので通信を行うことができる.
ルーティング
デフォルトゲートウェイ
ルーターの重要な役割は以下の2つである.
- ブロードキャストを外に出さないこと
- 他ネットワークへの転送
1つ目は2つ目ほど目立たないが重要である.これによりネットワークがブロードキャストメッセージによりひっ迫するのを防ぐ.基本的にブロードキャストされているメッセージはそのLANの内部にしか流されず,他のネットワークには転送されない.そのためARP要求をネットワーク外のWebサーバーにまで届けることができない.
そこでクライアントからのARP要求に対して,デフォルトゲートウェイはそのルーター自身のMACアドレスを返答する.つまりクライアントはARP応答で受け取ったMACアドレスを,Webサーバーのものと勘違いしている状態である.これでARPは完了したことにして,パケットの送信を始める.
以降クライアントがpublishするパケットにおける送信先MACアドレスは,デフォルトゲートウェイのMACアドレスになる.そのパケットは以下のようにしてWebサーバーにまで届けられるとしよう.
(ホスト) ===> (ルーターA) ===> (ルーターB) ===> (Webサーバー)
最終的にパケットがWebサーバーに届くまでに,いくつかのルーターの間のLANを経由する.ルーティングは経路表を見て行われ,ルーターAからルーターBへとルーティングが行われる.その時,パケットの送信元/送信先MACアドレスは以下のように書き換えられる.
/ | 送信元MAC | 送信先MAC |
---|---|---|
通過前 | ホストのMAC | ルーターAのMAC |
通過後 | ルーターAのMAC | ルーターBのMAC |
ルーターAがルーターBのMACアドレスを知るには,ARP要求を出す,またはARPテーブルを参照すればよい.これがWebサーバーに至るまで繰り返され,最終的に送信が完了する.
ルーティングテーブル
ルーティングテーブルには送信先ネットワークまでの距離と,そのネットワークへパケットを転送する際に利用するポート番号が記されている.ルーターはパケットが届いてきたらそれに応じて転送を行う(MACアドレスを書き換えながら).
以下はWindows上のWSLにおいてroute
コマンド(経路表を表示する)を実行した結果である.
受信先サイト ゲートウェイ ネットマスク フラグ Metric Ref 使用数 インタフェース
127.0.0.0 0.0.0.0 255.0.0.0 U 256 0 0 lo
127.0.0.1 0.0.0.0 255.255.255.255 U 256 0 0 lo
127.255.255.255 0.0.0.0 255.255.255.255 U 256 0 0 lo
224.0.0.0 0.0.0.0 240.0.0.0 U 256 0 0 lo
255.255.255.255 0.0.0.0 255.255.255.255 U 256 0 0 lo
255.255.255.255 0.0.0.0 255.255.255.255 U 0 0 0 eth2
224.0.0.0 0.0.0.0 240.0.0.0 U 0 0 0 eth2
0.0.0.0 buffalo.setup 255.255.255.255 U 0 0 0 eth2
192.168.11.255 0.0.0.0 255.255.255.255 U 0 0 0 eth2
192.168.11.0 0.0.0.0 255.255.255.0 U 0 0 0 eth2
192.168.11.6 0.0.0.0 255.255.255.255 U 0 0 0 eth2
192.168.11.0
がネットワークアドレスである.順番に見てみよう.
255.255.255.255
はDHCPで使うようなブロードキャストアドレスである192.168.11.255
はそのネットワーク内におけるブロードキャストに利用する0.0.0.0/0
はすべてのネットワークに用いる.これはデフォルトルートと呼ばれ,0.0.0.0/0
の代わりにdefault
と表記されることもある(IPアドレスを指してはいないため)
ルーターはパケットが届いたらその送信先IPアドレスを調べ,それに最も一致するネットワークアドレスを持つルーターに転送する.例えば172.20.100.52
は172.20/16
と172.20.100/24
の両方にマッチするが,この場合より一致する部分が長い172.20.100/24
を選択する.
ルーティングプロトコル
静的ルーティングでは管理者が決め打ちでルーティングを決める.動的ルーティングはルーティングプロトコルに従ってルーター間で決定される.ルーティングテーブルは適用される範囲によって2種類に分けられる.AS内部で用いられる or AS間で用いられるである.
ASとは ネットワークにおける自律システム(AS)とはICANNが管理するネットワークのグループ.日本ではJPNICが管理しており,ここで で一覧を見ることができるようだ
以下のプロトコルが有名.
AS内部(IGP) | AS間(EGP) |
---|---|
RIP : ディスタンスベクタ OSPF : リンクステート |
BGP |
ルーティングを決定する際にはルーター間の距離を基準に最短経路を求める.その距離関数をメトリックといい,プロトコルによって異なる.RIPではルーター間のホップ数を用いるが,OSPFでは回線速度を用いる.
RIP
下のようなネットワークを例にしてみる.
初めのルーティングテーブルは各ルーターが直接接続されているネットワークのみ登録されている.
ルーター | 送信先 | NextHop | Metric | Port |
---|---|---|---|---|
A | 192.168.2/24 | 0 | P1 | |
192.168.1/24 | 0 | P2 | ||
192.168.10/24 | 0 | P3 | ||
B | 192.168.1/24 | 0 | P1 | |
192.168.100/24 | 0 | P2 | ||
C | 192.168.2/24 | 0 | P1 | |
192.168.200/24 | 0 | P2 |
ルーティングテーブルは連想配列のようなものだと思えばいい.まず初めにAからテーブルの情報を送ると,BCは自身のテーブルに含まれていないエントリーを,メトリックをインクリメントして追加する.すでに含まれているエントリーについては更新しない.
ルーター | 送信先 | NextHop | Metric | Port |
---|---|---|---|---|
B | 192.168.1/24 | 0 | P1 | |
192.168.100/24 | 0 | P2 | ||
192.168.10/24 | A | 1 | P1 | |
192.168.2/24 | A | 1 | P1 | |
C | 192.168.2/24 | 0 | P1 | |
192.168.200/24 | 0 | P2 | ||
192.168.1/24 | 1 | P1 | ||
192.168.10/24 | 1 | P1 |
さらにBとCからもAに送信する.
ルーター | 送信先 | NextHop | Metric | Port |
---|---|---|---|---|
A | 192.168.2/24 | 0 | P1 | |
192.168.1/24 | 0 | P2 | ||
192.168.10/24 | 0 | P3 | ||
192.168.100/24 | B | 1 | P2 | |
192.168.200/24 | C | 1 | P1 |
再度Aから送信する.
ルーター | 送信先 | NextHop | Metric | Port |
---|---|---|---|---|
B | 192.168.1/24 | 0 | P1 | |
192.168.100/24 | 0 | P2 | ||
192.168.10/24 | A | 1 | P1 | |
192.168.2/24 | A | 1 | P1 | |
192.168.200/24 | A | 2 | P1 | |
C | 192.168.2/24 | 0 | P1 | |
192.168.200/24 | 0 | P2 | ||
192.168.1/24 | A | 1 | P1 | |
192.168.10/24 | A | 1 | P1 | |
192.168.100/24 | A | 2 | P2 |
ルーティングテーブルが最適な状態に達した後にネットワークのトポロジーが変化すると,アップデート情報を受け取ったルーターは,それによりメトリックが低下するネットワークについては更新を行う.以下を参照.
ルーティングループ
ディスタンスベクタ型のルーティングプロトコルでは起こりうる問題である.
先ほどの状態で,上の図のようにルーターBとルーターCが接続されているとしよう.そして192.168.200/24
において障害が起きたとしよう.まずルーターCでは192.168.200/24
へのエントリーは削除されるので,以下の状態からスタート.
ルーター | 送信先 | NextHop | Metric | Port |
---|---|---|---|---|
B | 192.168.1/24 | 0 | P1 | |
192.168.100/24 | 0 | P2 | ||
192.168.10/24 | A | 1 | P1 | |
192.168.2/24 | A | 1 | P1 | |
192.168.200/24 | C | 1 | P3 | |
C | 192.168.2/24 | 0 | P1 | |
~192.168.200/24~ | ~0~ | ~P2~ | ||
192.168.1/24 | A | 1 | P1 | |
192.168.10/24 | A | 1 | P1 | |
192.168.100/24 | B | 1 | P3 |
ルーターBは192.168.200/24
へのルートがあると勘違いしていることに注意.そしてBとCが同時に更新情報を送ったとしよう.この時各ルーターがとる行動は
- BがCから受け取るテーブルには
192.168.200/24
へのエントリーがないので,Bでも削除 - CがBから受け取るテーブルには
192.168.200/24
という未知のネットワークへのエントリーがあるので,メトリックをインクリメントして追加
である.よって以下のようなテーブルに更新されてしまう.
ルーター | 送信先 | NextHop | Metric | Port |
---|---|---|---|---|
B | 192.168.1/24 | 0 | P1 | |
192.168.100/24 | 0 | P2 | ||
192.168.10/24 | A | 1 | P1 | |
192.168.2/24 | A | 1 | P1 | |
~192.168.200/24~ | ~C~ | ~1~ | ~P3~ | |
C | 192.168.2/24 | 0 | P1 | |
192.168.1/24 | A | 1 | P1 | |
192.168.10/24 | A | 1 | P1 | |
192.168.100/24 | A | 1 | P2 | |
192.168.200/24 | B | 2 | P3 |
再度交換し合うと,メトリックが加算され続けてしまう.
ルーター | 送信先 | NextHop | Metric | Port |
---|---|---|---|---|
B | 192.168.1/24 | 0 | P1 | |
192.168.100/24 | 0 | P2 | ||
192.168.10/24 | A | 1 | P1 | |
192.168.2/24 | A | 1 | P1 | |
192.168.200/24 | C | 3 | P3 | |
C | 192.168.2/24 | 0 | P1 | |
192.168.1/24 | A | 1 | P1 | |
192.168.10/24 | A | 1 | P1 | |
192.168.100/24 | A | 1 | P2 | |
~192.168.200/24~ | ~B~ | ~2~ | ~P3~ |
ルーターBが更新情報を送る前にCが192.168.200/24
への接続障害を先にBに送ることができるのであれば,この問題は生じない.ディスタンスベクタ型のルーティングプロトコルではすべてのルーターが同じ情報を持つまで更新が収束しない.
これを防ぐ方法として,スプリットホライズンというものが知られている.スプリットホライズンを有効にすると,ルーターXから教えてもらったエントリー(NextHopがXになっているエントリー)はXに送る更新のメッセージから削除される.それにより送信するエントリー数を減らすことができるという仕組みだが,障害が発生している場合は無限カウントを防ぐことができる.上の例だと,ルーターBはNextHopがCになっている192.168.200/24
のエントリーをCに再送はしない.
ICMP
ICMP(Internet Control Message Protocol)はネットワークの状態を管理するのに利用されるプロトコルである.ICMPメッセージはIPヘッダ+ICMPメッセージのみである.トランスポート層の情報は含まれない.
ICMPメッセージの中でも重要なのは以下の5つである.
タイプ | 説明 | 意味 | 種類 |
---|---|---|---|
0 | Echo Reply | Echo応答 | Query |
3 | Destination Unreachable |
IPアドレスの 宛先到達不能 |
Error |
5 | Redirect | 最適(ベターな)経路を通知 | Error |
8 | Echo Request | Echo要求 | Query |
11 | Time Exceeded | 時間超過による パケット破棄 |
Error |
パケットを受け取ったルーターが何かエラーが起きたり要求を受け取ると,ICMPメッセージを発行する.
- Destination Unreachable: 宛先IPアドレスにパケットを送ることができない
- Redirect: ホストがyyy.yyy.yyy.yyyへのパケットを送ろうとしており,デフォルトゲートウェイであるルーターAにパケットを送る.しかしルーターAを経由してからさらにルーターB(ホストのLAN内にある)を経由した方がメトリックが低下することが分かったとしよう.するとルーターAは「yyy.yyy.yyy.yyyへのデフォルトゲートウェイをルーターBに設定するよう経路表の変更を行う」旨のICMPメッセージをホストに送信する.
- 宛先のホストYのIPアドレスを指定してホストXからRequestを送る.それが届いたらホストYはホストX宛てにReplyを送る.pingではひたすらRequestを送るコマンドであり,TTLやDestination Unreachableを知ることができる.
pingはレイヤー3のプロトコルである.もしpingが上手くいくのに繋がらないという事態が発生した場合,それはレイヤー4以上で何らかのエラーが発生しているということである.
TTLは通過できるゲートウェイの個数である.ゲートウェイを通過するたびにデクリメントされる.あるルーターでゼロになるとそのパケットは破棄される.パケットを廃棄したルーターは送信元にICMP::11"Time Exceeded"を送信する.
TCP
TCPヘッダ
TCPヘッダーは以下のようになる(RFC793より ).
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
サイズは4バイトx5=20バイトである.
制御番号のうちACK
,SYN
,FIN
はよく用いられる.TCPにおけるアプリケーション間のコネクションの確立方法はthree-way handshakeと呼ばれる.ホストXからホストYに向けてコネクションの確立を行う際は以下の手順をとる.
X <-> Y | 説明 | ACK | SYN |
---|---|---|---|
X -> Y | Yへ接続要求 | 0 | 1 |
@Y | 受け取る | / | / |
Y -> X | XへとACK & Xへ接続要求 |
1 | 1 |
@X | 受け取る | / | / |
X -> Y | Yに応答する | 1 | 0 |
シーケンス番号
シーケンス番号はデータ本体が初めから数えて何バイト目であるかを示す番号である.
TCPでは送る一つ一つのパケットにシーケンス番号をつける.一つのパケットが何バイトであるかはMTU,そのうちデータ部が何バイトであるかはMSSとして定められている.サーバーは次に何バイト目のデータを要求するかを確認応答で提示する.
あるパケットのサイズを1020バイトとしよう.つまりデータ本体は1000バイトである.そのシーケンス番号が1であった場合,そのパケットを受け取ったサーバーは確認応答(ACK)により1001を要求する.クライアントは同様にシーケンス番号1001から初めて1020バイトのデータを送る,というのを繰り返す.
再送タイムアウト時間
基本的にサーバーは「パケットを送る」「ACKを待つ」「次のパケットを送る」の繰り返しを行うが,途中でACKが消失してしまうこともあり得る.そのため再送タイムアウト時間だけ待ってもACKがこない場合はもう一度そのパケットを送りなおす.TCPではクライアントはパケットを送るたびに,(そのパケットに対応する)確認応答が来るまでの時間RTT(Round Trip Time)を測定している.RTTの分散(ジッタ)を加算して再送タイムアウト時間を更新する.
MSSの決定
TCPではパケットにおけるデータの最大サイズ(バイト)をMSSという.スリーウェイハンドシェイクを行う際に,サーバーとクライアントはTCPヘッダに自分のインターフェース(FDDIなのかEtherなのか)の最大MSSをセットして送る.双方のうち小さいほうをMSSとして採用する.
ウィンドウ制御
一つのパケットを送ってACKを待ち,またパケットを送るというのは効率が悪い.そこで一度に送るパケットの個数を多重化するのがウィンドウ制御である.
ACKを待たずに送信できるデータのサイズをウィンドウサイズという.上の図の場合ウィンドウサイズは4セグメントとなっている.
ウィンドウ制御と再送制御
シーケンス番号が
- 1-1000
- 1001-2000
- 2001-3000
- ~3001-4000~
- 4001-5000 のデータがサーバーに届いたとする.そのときサーバーは4001-5000のデータが届いた時も(まだ3001への要求が応えられていないため)3001に対するACKを返答する.そのため3001-4000が届くまでACKでは3001を要求し続ける.以下の図は途中で消失したパケットに対してACKが何度も送られる様子である.
図のようにまだ受け取っていないパケット(シーケンス番号)に対するACKが複数届くことを重複確認応答という.3つの重複確認応答が確認されるとクライアントはそのパケットを再送する.one-by-oneで送ってタイムアウト処理を行うよりはこの方がまだ高速である.
- 1-1000
- 1001-2000
- 2001-3000
- 3001-4000
- 4001-5000
のパケットを送ったときにACKは4001-5000の分しか届かなかったとしよう.しかしそれだけでも4000までのパケットが届いていることは確認できている.ウィンドウ制御によりACKが必ずしも全て届く必要がないことが分かる.
フロー制御
送信側が勝手に決めたMSSでデータを送りつけると受信側のバッファーがパンクしてしまう可能性がある.TCPのヘッダーにはウィンドウサイズのフィールドがあるため,受信ホストは自分の受信能力に応じてこの値を設定してウィンドウ更新通知により送信ホストに伝える.これによりMSSはadaptiveに変化する.
ふくそう制御
いくらウィンドウ制御によって多重化してパケットを送ることができるとしても,回線のリンク速度や混雑具合によってはネットワークをひっ迫させる可能性がある.そこでTCPではスロースタートと呼ばれるアルゴリズムによってデータの送信量を調節する.
まず初めにウィンドウサイズを初期値(1,2,3などが多そう,リンクによる)に設定し,ACKが来るたびにインクリメントする.ウィンドウサイズがf(n)のときkf(n)個のACKが返ってくるとすると(0 < k < 1)
f(n+1) = f(n) + k * f(n)
よりふくそうウィンドウは指数関数的に増加することが分かる.実際に送るデータサイズには,送信側のウィンドウサイズと受信側の(ウィンドウ更新通知で知らせた)ウィンドウサイズのうち小さいほうを採用する.
ただ指数関数的に増加し続けるのは防がねばならないため,スロースタート閾値(SST,Slow Start Threshold)が定められている.そしてウィンドウサイズf(n)がSSTを超えたらACKが来るたびに以下の値だけインクリメントする.
MSS * MSS
---------
f(n)
f(n)が増えるほどACKの数も増えるので上の式によるインクリメントもより多く行われるようになるが,一回一回のインクリメント量は小さくなる.結果的にウィンドウサイズは直線的に増えることになる.
SSTの決め方
以下ではウィンドウサイズをcwnd(n)(Congetion Window Size)と表記する.
初めはSSTは定義されていない.そしてcwnd(n)を指数関数的に増加させていく.そのうちタイムアウトが起きるので,その時は以下のように更新する.
SST <- cwnd(n)/2
cwnd(n+1) <- 1
次からはSSTを超えた後はcwnd(n)を「少しづつインクリメント」するようになる.それでもいずれタイムアウトまたは重複確認応答がくることになる.もしタイムアウトが起きた場合
SST <- cwnd(n)/2
cwnd(n+1) <- 1
とするが,もし重複確認応答がきた場合は以下のようにcwnd(n)を更新する.
SST <- cwnd(n)/2
cwnd(n+1) <- cwnd(n)/2 + 3
タイムアウトの場合と異なり,この場合は少なくともACKが3回届くだけの帯域幅は確保されているためこのような処理を行う.
DNS
TLDとゾーン
ドメイン名の一番後ろをTLD(Top Level Domain)という..com
,.org
,.net
などトップレベルドメインが3文字以上になっているのはgTLDと呼ばれるドメインで,もともとはアメリカ国内で使われていた.これは.jp
とか.nl
と組み合わせる必要がないドメインである.
一つ一つのドメインに対してそのネームサーバーが存在する.各ネームサーバーは自身とその子ノードのIPアドレスを知っている.この範囲をゾーンという.
リソースレコード
ネームサーバーが各ノードについて有している情報をリソースレコードという.例えばgithub.io
にあるsoblin
のリソースレコードは以下のようになる感じ.
所有者 | 名前 | タイプ | クラス | TTL | RD長 | RDATA |
---|---|---|---|---|---|---|
github.io | soblin | 0001 | 0001 | 86400 | 4 | xxx.xxx.xxx.xxx |
このようにしてsoblin.github.io
のIPアドレスを教えている.タイプは以下のものが有名である.
タイプ | 1 | 2 | 5 | 6 | 12 | 13 |
---|---|---|---|---|---|---|
名前 | A | NS | CNAME | SOA | PTR | MX |
意味 | ホストの IPアドレス |
ネームサーバー | ホスト エイリアス |
ゾーンの 情報 |
ポインタ (逆引き) |
メールサーバー |
Aレコードは上に示したsoblin.github.io
に対するIPアドレスを直接記述しているフィールドである.
NSレコード
NSレコードはネームサーバーのIPアドレスを伝えるフィールドである.soblin.netlify.com
にアクセスする際は
.com
のネームサーバーにアクセスしてnetlify
のネームサーバーのアドレスを知る.netlify.com
にアクセスしてsoblin
のIPアドレスを知る
ことになる.そのためnetlify.com
に対応する部分のネームサーバーのIPアドレスを知る必要がある.以下のように(適当)NSレコードを記述する.
名前 | タイプ | クラス | RDATA |
---|---|---|---|
github.io | NS | IN | ns.github.io |
soblin | NS | IN | ns.soblin.github.io |
そしてns.github.io
などのAレコードも合わせて記述する.
名前 | タイプ | クラス | RDATA |
---|---|---|---|
ns.github.io | A | IN | xxx.xxx. xxx.xxx |
ns.soblin.github.io | A | IN | xxx.xxx. xxx.xxx |
その他
CNAMEはCanonical Nameの略称.ドメイン名にエイリアスを付ける.
SOAはDNSのキャッシュに使われる.
PTRはIPアドレスにドメイン名を対応付ける.逆引きに使われる.
MXはメールサーバーを教える.
名前 | タイプ | クラス | RDATA |
---|---|---|---|
soblin.com | MX | IN | NN mail.soblin.com |
A | IN | xxx.xxx. xxx.xxx |
NNには優先値を示す数値が入る.
DNSレゾルバー
DNSのやり取りはUDPを介して行われる(ポート番号53).DNSサーバーにはフルサービスサーバーとコンテンツサーバー(権威サーバー)の2種類が存在する.DNSサーバーに問い合わせるクライアント(スタブレゾルバー)は初めにフルサービスレゾルバーに再帰問合わせを行う.もしフルサービスレゾルバーがそのクエリをキャッシュしていなければ,次にフルサービスレゾルバーは権威サーバーに反復問合わせを行う.
プロバイダーのネームサーバーはフルサービスレゾルバーにあたる.ブログやサイトを公開しているサーバーがコンテンツサーバーにあたる.コンテンツサーバーはフルサービスレゾルバーから得たレコード情報をキャッシュするのでキャッシュサーバーとも呼ばれる.
家にあるようなブロードバンドルーターが反復問合わせを行うのは非効率的である.そのようなルーター内部のDNSサーバーはスレーブサーバーとして外部にあるフォワーダーと呼ばれるDNSサーバーに名前解決を依頼する.
DNSメッセージ
RFC1035 によりDNSメッセージが定義されている.
1 1 1 1 1 1
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ID |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
|QR| Opcode |AA|TC|RD|RA| Z | RCODE |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| QDCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ANCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| NSCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
| ARCOUNT |
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
フラグには以下8通りである.
bit | Query | Value |
---|---|---|
1 | QR 問合わせ・応答 |
1 : ネームサーバーからの応答 0 : 問合わせ |
4 | OPcode オペコード |
0 : 正引き 1 : 逆引き 2 : サーバー状態要求 |
1 | AA オーソリティー応答 |
1 : 「当たり」のコンテンツサーバーからの応答 その他 |
1 | TX 切り捨て |
/ |
1 | RD 再帰要望 |
1 : レゾルバー <-> フルサービスレゾルバー 0 : その他 |
1 | RA 再帰有効 |
1 : 再帰応答可能 0 : 不可能 |
3 | 予約 | / |
4 | Rcode 戻りコード |
0 : 応答成功 |
HTTP
リクエスト開始行
HTTPはクライアント・サーバーモデルであり,クライアントはユーザーエージェント(UA),サーバーはWebサーバー(apache,nginxなど)である.HTTPのメッセージの形式は以下の通りである.
開始行 + メッセージヘッダー(+CRLF) + (CRLF + ) メッセージ本体
メッセージヘッダーの終わりとメッセージ本体の直前にCRLF(\r\n)がある.開始行はGET soblin.netlify.com HTTP/1.0
のような感じである.
メソッド + リクエストURI + HTTPバージョン
GET
の部分は一般的にメソッドと呼ばれ,以下の3つがよく使われる.
- GET
- HEAD
- POST
HEADはヘッダーのみ要求する.GETは(html)ファイル名を要求する(データはないよ).POSTはCGIに送るデータなども加えて送る.
レスポンス開始行
レスポンスも同様に以下のような形式である.
開始行 + メッセージヘッダー(+CRLF) + (CRLF + ) メッセージ本体
レスポンスの開始行は以下の形式である.
HTTPバージョン + ステータスコード + 応答フレーズ
ステータスコードと応答フレーズは以下が有名.
ステータスコード | 応答フレーズ | 説明 |
---|---|---|
200 | OK | OK |
301 | Moved Permanently | リクエストURIは別のURIに割り当てられている |
400 | Bad Request | HTTPの書式が間違っている |
403 | Forbidden | リクエストは受け取ったが実行できない |
404 | Not Found | リクエストURIは存在しない |
500 | Internal Server Error | サーバーがリクエストの処理でエラー状態 |
メッセージヘッダー
メッセージヘッダーは以下の形式である.
Field: val
Field: val
よく使われるのは以下の通り.
ヘッダー | 説明 |
---|---|
Host | 宛先のドメイン名 |
User-Agent | ブラウザ情報など |
Referer | ハイパーリンクする前のページ |
Accept | 取り扱い可能なファイル拡張子 |
Accept/Content Encoding |
圧縮方式 |
Last-Modified | 応答の最終更新日 |
GET
GETは基本的にリクエストURIをサーバーに教えるのみである.特徴的なGETメソッドとしては条件付きGETが挙げられる.ユーザーエージェントが取得しているページの更新日時をIf-Modified-Since
にセットしてGETすることで,サーバーは「その日時以降に該当URIが更新されていたら」ページを送信して,もし更新されていなかったら304 Not Modified
を返答する(ページは送信しない).
またURIがCGIである場合,GETにより取得されるページはそのプログラムではなくそれにより生成されたHTMLである.
POST
これはGETに加えて要求メッセージに何らかのデータを付加するメソッドである.卑近な例だと入力ボックスの入力データも付加してサーバーにリクエストを送ることで,レスポンスのHTMLファイルを動的に変化させることができる.
telnetでGETしてみる
仮想マシン上のUbuntuでApacheを動かしている.
$ telnet
telnet> open localhost 80
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /index.html HTTP/1.0
HTTP/1.1 200 OK
Date: Wed, 22 Apr 2020 18:06:00 GMT
Server: Apache/2.4.29 (Ubuntu)
Last-Modified: Wed, 22 Apr 2020 14:31:24 GMT
Etags: "2d35-5a3e1fc963046"
Accept-Ranges: bytes
Content-Length: 11573
Vary: Accept-Encoding
Connection: close
Content-Type: text/html
<!DOCTYPE html>
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
<head>
<meta charset="utf-8">
<script type="text/javascript">
セッションとCookie
OSI参照モデルで出てくるセッション層はプロセス間の通信である.HTTPでは一回のGETからレスポンスまでが一つのセッションであり,その間にデータ転送のコネクションが何度か行われる.しかし一度のセッションの情報はHTTPでは保存されない(セッションステートレス).そのため同一ホストから何度もリクエストがある場合にそれが「同一のホストから何度もリクエストされている」ことに気づけない.例えばECサイトではXさんが注文を追加してページを遷移するたびに「新しいユーザーが注文してきた」と勘違いしてしまうため,同一のユーザーの買い物かごに注文を追加するみたいなことができない.そこであるリクエストが来た時にサーバー側からクライアントにCookieと呼ばれるIDを渡して,以降それを携帯してセッションを行う.
- 初めにクライアントからリクエストがあると,サーバーは
Set-Cookie
をメッセージヘッダーにセットして返答 - 以降クライアントは
Cookie
をその値にセットしてメッセージヘッダーを送信する
Cookieには以下の情報が保存される.
- 名前(userid)
- 有効期限(expire)
- パス属性(path)
- サーバードメイン名(domain)
- その他
もしExpireが設定されているのであればそのCookieはセッションの終了後もファイルとして保存される(永続化される).設定されていないCookieはブラウザが閉じると消去されるためセッションCookieと呼ばれる.
ドメイン名とパスはやり取りをするサーバーを特定するためにセットされる(ドメイン名が同じでも各サブディレクトリは別々のサーバーによって管理されている可能性があるため).
プロキシサーバー
クライアントはWebサーバーに接続する代わりにプロキシーサーバーに接続する(というよりDNSで教えてもらったサーバーが実はプロキシーになっていて,本体のWebサーバーには直接接続しないようにするのでは).そしてプロキシーサーバーはWebサーバーに接続してレスポンスを取得し,クライアントに横流しする.
その時プロキシーはファイルをキャッシュするので,クライアントからのすべてのリクエストに対していちいちWebサーバーに問い合わせる必要がなくなる.その他,クライアントからの要求のうち一部をブロックするコンテンツフィルターの役割も果たすことがある.
認証
HTTPではベーシック認証とダイジェスト認証が利用できる.特定のWebサーバーにアクセスするときにサーバーのレスポンスヘッダーにWWW-Authorize
がセットされている場合,その値(Basic or Digest)に基づいてユーザー名とパスワードを求められるようになっている.クライアントはリクエストヘッダーにおいてAuthorization
をユーザー名とパスワードに設定して再度アクセスする.ベーシック認証ではユーザー名とパスワードはBASE64により暗号化されて送られる.しかしHTTPヘッダーにユーザー名とパスワードが(暗号化されていはいるものの)そのまま書いてあるようなものである(暗号化データから直接(他のパラメータなしに)解読される可能性がある).
そこでダイジェスト認証ではハッシュ関数を使う.まずサーバーはランダムな文字列を生成してそれをクライアントに送る.クライアントはその文字列とユーザー名とパスワードをハッシュ関数により一つのビット列に変換し,それをサーバーに送る(ランダムな文字列がある分解読しにくい,あと当然ハッシュ関数は共通だよ).サーバーはそのハッシュ値と,手元で計算したハッシュ値(ランダムな文字列と正解のユーザー名,パスワードから計算)と比較し,一致すれば認証成功とする.このときのハッシュ関数としてはMD5やSHA(いろいろバージョンがある)が知られている.実際SSL/TLSの方がセキュリティーは高いが.
メール
初期の電子メールでは送信者と受信者がTCPで直接コネクションを作りメールの送受信をお互いのハードディスク上で行っていた.それだと(特に)受信者が電源をOFFにしているときにメールを送信することができないので,メールサーバーにメールを送信するようになった.そして受信者はメールサーバーからPOP(IMAP)というプロトコルによってメールをフェッチする形式になった.下の図のようにメール本体をローカル(から送信|にフェッチ)するクライアントをMUA,転送を行うサーバーをMTAという.
MXレコード
MUAからxxx@yyy.com
宛てのメールを受け取ったMTAは,DNSにその宛先を問い合わせる.具体的にはyyy.com
のサーバーのMXレコードを問合わせ,そのAレコードと合わせて取得する.
Domain | Record | Addr |
---|---|---|
yyy.com | MX | mail.yyy.com |
mail.yyy.com | A | xxx.xxx. xxx.xxx |
そしてその宛先のIPアドレスのサーバーにメールを転送する.
SMTPとPOP/IMAP
MTAがMUAからメールを受け取る,またはMTAへ転送するときに使うのがSMTPである.MUAがMTAにメールをフェッチする時に使われるのがPOP/IMAPである.
大雑把に言うとメールを送る(配信と転送)のに使うのがSMTP,受け取るの(受信,フェッチ)に使うのがPOP/IMAPである
メールの内容はMIMEと呼ばれる形式によってフォーマットされている.メタ情報を含んだヘッダ,空行を挟んでコンテンツが続く.
POPはメールの送受信をサーバーを経由する方法である.送信者はSMTPプロトコルによってPOPサーバーまでメールを送り,受信者はPOP(IMAP)プロトコルによってユーザーの認証などを行いながらメールをフェッチする.メールの管理は送信者側で行う.
IMAPではメールの管理をサーバー上で行うため既読や未読といったメールボックスの情報をクラウド上で管理してくれる.