VPCにおける同一サブネット内の通信(2)
パケットの送信と受信
送信元ゲストOSではパケットの送信に必要な情報がそろったので、早速パケットを送信します。送信元Serverは再度パケットを横取りし、VPC IDでカプセル化した上で、送信先Serverへ向かってカプセル化されたパケットを送りつけます。EC2インスタンスにとっては同一サブネット内の通信ですが、送信先Serverは物理的(ネットワーク的)には別の場所にある可能性が高いので、レイヤ2の通信ではなくルーターも含めたレイヤ3の通信となります。
その際のパケットはこんな見かけになります。左側がパケットの先頭になります。"dst Guest MAC"から右が送信元ゲストOSが生成したパケットになり、「VPC IDでカプセル化」とはこんなパケットを生成することを指しています。

パケットを受け取った送信先Serverは、まず送信元ゲストOSが確かに送信元Serverに存在するかを確認するためMapping Serviceにお伺いをたてます。その理由は、各Serverに問題が発生し勝手な(ゲストOSが送ってない)パケットを送りつけてきても、受信側Serverでそれを破棄できるようにするためです。このプロセスを初めて知った時はセキュリティを最優先事項とするAWSらしい設計だなと思いました。
そして、上記の確認がとれ次第、送信先Serverは受信したパケットからVPC IDによるカプセル化を外し、送信元ゲストOSが送った元々のパケットを送信先ゲストOSへと送ります。
このようなプロセスを経ることで同じサブネットにいるゲスト同士が物理的にはネットワークのどこにいてもお互いに(同一サブネットにいるかのように)通信することができ、VPCにおけるL2トポロジーを実際の物理トポロジーから独立させることができるのです!
なお、VPCのサブネットは必ず1つのアベイラビリティーゾーン内に含まれているので、ここまでのプロセスは低遅延で実施されます。
実際のパケット
では、試しに同一サブネット内のEC2インスタンス(instance1)から別のインスタンス(instance2)へpingをして、実際のパケットを見てみましょう。前提として、それ以前にはまったく通信が無かった(ARPのキャッシュが無い)インスタンス間の通信であるとします。
まず、それぞれのインスタンスのMACアドレス、プライベートIPアドレスは次の通りです。
instance1$ ip addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP gup default qlen 1000
link/ether 06:58:86:91:36:49 brd ff:ff:ff:ff:ff:ff
inet 172.31.24.51/20 brd 172.31.31.255 scope global eth0
instance2$ ip addr show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP gup default qlen 1000
link/ether 06:dc:da:0f:68:d9 brd ff:ff:ff:ff:ff:ff
inet 172.31.24.206/20 brd 172.31.31.255 scope global eth0
instance1にinstance2のIPアドレスに関するARPキャッシュが無いことを確認します。
instance1$ arp -n 172.31.24.206
172.31.24.206 (172.31.24.206) -- no entry
この状態でそれぞれのインスタンスでtcpdumpを実行しながらinstance1からinstance2へpingをしてみます。
instance1$ ping -c1 172.31.24.206 PING 172.31.24.206 (172.31.24.206) 56(84) bytes of data. 64 bytes from 172.31.24.206: icmp_seq=1 ttl=64 time=0.640 ms --- 172.31.24.206 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.640/0.640/0.640/0.000 ms
その際のtcpdumpの出力はそれぞれ以下の通りです。
instance1$ sudo tcpdump -etni eth0 icmp or arp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 06:58:86:91:36:49 > Broadcast, ethertype ARP (0x0806), length 42: Request who-h 172.31.24.206 tell 172.31.24.51, length 28 06:dc:da:0f:68:d9 > 06:58:86:91:36:49, ethertype ARP (0x0806), length 56: Reply72.31.24.206 is-at 06:dc:da:0f:68:d9, length 42 06:58:86:91:36:49 > 06:dc:da:0f:68:d9, ethertype IPv4 (0x0800), length 98: 172..24.51 > 172.31.24.206: ICMP echo request, id 25098, seq 1, length 64 06:dc:da:0f:68:d9 > 06:58:86:91:36:49, ethertype IPv4 (0x0800), length 98: 172..24.206 > 172.31.24.51: ICMP echo reply, id 25098, seq 1, length 64 instance2$ sudo tcpdump -etni eth0 arp or icmp tcpdump: verbose output suppressed, use -v or -vv for full protocol decode listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 06:58:86:91:36:49 > 06:dc:da:0f:68:d9, ethertype IPv4 (0x0800), length 98: 172..24.51 > 172.31.24.206: ICMP echo request, id 25098, seq 1, length 64 06:dc:da:0f:68:d9 > Broadcast, ethertype ARP (0x0806), length 42: Request who-h 172.31.24.51 tell 172.31.24.206, length 28 06:58:86:91:36:49 > 06:dc:da:0f:68:d9, ethertype ARP (0x0806), length 56: Reply72.31.24.51 is-at 06:58:86:91:36:49, length 42 06:dc:da:0f:68:d9 > 06:58:86:91:36:49, ethertype IPv4 (0x0800), length 98: 172..24.206 > 172.31.24.51: ICMP echo reply, id 25098, seq 1, length 64
普通のEthernetならinstance2にpingのリクエスト(ICMP echo request)が届く前にinstance1からのARPリクエスト(Request who-has 172.31.24.206)がブロードキャストで届くはずですが、ご覧の通り届いていません。
ARPリクエストが届いていれば、リクエスト元に対するARPキャッシュを生成するので、上記のinstance2からinstance1へのARPリクエストは不要となりますが、実施にはARPリクエストは届いておらず、結果ARPキャッシュにはinstance1のMACアドレスが無いため、instance2は送信元ゲストに対するARPリクエスト(Request who-has 172.31.24.51)を送ってからpingの応答(ICMP echo reply)を返してます。なお、同じくinstance2からinstance1へ送っているARPリクエストもinstance1へは届いていません。
これは、VPCではARPの処理をそのまま実現している(ARPパケットをインスタンス間でやりとりしている)のではなくMapping Serviceによって実現しているためです。逆に言うと、VPCのFAQの「Q: Amazon VPC は、マルチキャストまたはブロードキャストをサポートしますか?」に「いいえ」とあるように、ブロードキャストがサポートされていないため、Mapping Serviceを使う必要がある、ともいえます。
MACアドレスに関する余談
上記出力のMACアドレスを見ると、ぱっと見06とか0aで始まるものが多いことに気づかれるかもしれません。他で適当にピックアップしたMACアドレスも、次の通り06とか0aで始まってます。
- 06:f2:97:41:38:48
- 0a:14:57:86:19:f2
- 06:a9:1a:12:bf:d2
- 06:5a:a0:c6:ab:c5
これは、AWSではGLビットが0の「ローカルアドレス」をMACアドレスとして使っているためです。したがって、最初の一オクテットの値は4n-2(nは1以上の整数)になります(と言いつつ「02」とか「0e」とか「12」で始まるものをすぐに見つけられなかったのですが……)。
ローカルアドレスというのは単に世界中で一意とは限らないというだけで、実際の利用上は普通のMACアドレスと同じです。
Mapping Serviceのキャッシュ
ところで、以上のようにMapping Serviceはすべての通信に関係してくるためSingle Point of Failureになり得ます。しかし、実際には各Serverがそのコンテンツのキャッシュを必ず持っており、毎回Mapping Serviceへリクエストが送られるわけではありません。
たとえば、もし一度目のリクエストがMapping Serviceへ送られ、その後はキャッシュから返される、といった仕組みになっているとすると、Mapping Serviceの応答、つまりARP解決の遅延に多少なりとも変動を観測できるはずです。しかし、簡単に計測した範囲では、新規に立ち上げたインスタンスのARP解決が特に遅いといったことはありませんでした。
これは、上述の通り各Serverが常にMapping Serverのキャッシュを持っていることを示唆しています。
