Multi-VPC ENI Attachmentsを試してみた part1

2023年10月26日にMulti-VPC ENI Attachmentsという機能がAWSでリリースされ、EC2に対して別VPCのENIをアタッチできるようになりました。
https://aws.amazon.com/jp/about-aws/whats-new/2023/10/multi-vpc-eni-attachments/

[公式マニュアル](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/scenarios-enis.html)を確認したところ「Overcome CIDR overlaps between two VPCs that can’t be peered together」(日本語訳：ピアリングできない2つのVPC 間のCIDRの重複を克服する)と記載があるので、同一CIDRの別VPCのENIもEC2にアタッチできることが理解できます。
同一CIDRのVPCを利用するケースがあまり想像できませんが、これまでは「不可能」であったことが「可能」になるということで有用なユースケースもあるのかと思われます。

今回は本機能を利用して2パターンのNW構成で疎通試験を実施してみました。
長いのでpart1とpart2に分けて書いており、本記事はpart1となります。
part2の記事は[こちら](https://qiita.com/Shinya_K/items/26628db3b4f90c4f091b)です。

# Multi-VPC ENI Attachmentsの制限事項
Multi-VPC ENI Attachmentsを利用するにあたって以下の制限事項があります。 ([公式マニュアル](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/scenarios-enis.html)より抜粋)

***You can launch an EC2 instance in one VPC and attach a secondary ENI from another VPC (but in the same Availability Zone) to the instance.***
日本語訳：「1つのVPCでEC2インスタンスを起動し、別のVPC(ただし同じアベイラビリティーゾーン内)からセカンダリENIをインスタンスにアタッチできます。」

***You cannot create multi-homed instances across VPCs across different AWS accounts.***
日本語訳：「異なるAWSアカウントのVPCにまたがってマルチホームインスタンスを作成することはできません。」

つまりアタッチ可能な別VPCのENIは**EC2と同一AZ且つ同一AWSアカウントであることが条件**ということになります。

# 検証のNW構成
検証に使用した環境の構成図です。

![パターン1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268065/90195da4-a2ee-e077-55e8-a4fc93a66a68.png)

ポイントはvpc-1とvpc-4が同一CIDRであることです。
この構成で以下の通信テストを実施しました。
テスト1：host-1から10.0.0.10にping
テスト2：host-4から10.0.1.10にping
テスト3：host-2から172.16.1.10にping

補足事項
・VPC Peeringで接続したvpc-1とvpc-2及びvpc-3とvpc-4の間のルーティングはroutetableに設定済。
・各EC2及びvpc-3のENIのセキュリティグループ設定は以下とする。
　　inbound：RFCで定義されているプライベートIPのCIDRからの通信を全許可
　　outboundは0.0.0.0/0に対する通信を全許可
・全てのsubnetは同一AZに配置。
・各VPCにはインターネットゲートウェイを作成してインターネット経由でEC2のOSにログインする。
・EC2のOSはAmazonLinux2023を使用。

# vpc-2のhost-2にvpc-3のENIをアタッチする
本記事を執筆している2023年11月6日時点ではAWSマネジメントコンソールからは実施できないためAWS CLIを使用してアタッチします。
```
aws ec2 attach-network-interface --device-index 1 --instance-id <ENIをアタッチするインスタンスID> --network-interface-id <アタッチするENIのID>
```


ENIアタッチ前のインターフェースの状態です。
```
[root@host-2 ~]# ifconfig
enX0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
        inet 10.0.0.10  netmask 255.255.255.0  broadcast 10.0.0.255
        inet6 fe80::460:28ff:fe46:3b5  prefixlen 64  scopeid 0x20<link>
        ether 06:60:28:46:03:b5  txqueuelen 1000  (Ethernet)
        RX packets 752  bytes 179722 (175.5 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 861  bytes 98937 (96.6 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 12  bytes 1020 (1020.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 12  bytes 1020 (1020.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
```

ENIアタッチ後のインターフェースの状態です。
```
[root@host-2 ~]# ifconfig
enX0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
        inet 10.0.0.10  netmask 255.255.255.0  broadcast 10.0.0.255
        inet6 fe80::460:28ff:fe46:3b5  prefixlen 64  scopeid 0x20<link>
        ether 06:60:28:46:03:b5  txqueuelen 1000  (Ethernet)
        RX packets 2111  bytes 344356 (336.2 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 2257  bytes 282440 (275.8 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

enX1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 9001
        inet 10.0.1.10  netmask 255.255.255.0  broadcast 10.0.1.255
        inet6 fe80::44c:45ff:fe8f:15a3  prefixlen 64  scopeid 0x20<link>
        ether 06:4c:45:8f:15:a3  txqueuelen 1000  (Ethernet)
        RX packets 15  bytes 2172 (2.1 KiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 32  bytes 4706 (4.5 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        inet6 ::1  prefixlen 128  scopeid 0x10<host>
        loop  txqueuelen 1000  (Local Loopback)
        RX packets 12  bytes 1020 (1020.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 12  bytes 1020 (1020.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
```

# テスト1：host-1から10.0.0.10にping
問題無く疎通できました。
```
[root@host-1 ~]# ping 10.0.0.10 -c 3
PING 10.0.0.10 (10.0.0.10) 56(84) bytes of data.
64 bytes from 10.0.0.10: icmp_seq=1 ttl=127 time=0.857 ms
64 bytes from 10.0.0.10: icmp_seq=2 ttl=127 time=0.470 ms
64 bytes from 10.0.0.10: icmp_seq=3 ttl=127 time=0.493 ms

--- 10.0.0.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2011ms
rtt min/avg/max/mdev = 0.470/0.606/0.857/0.177 ms
```

# テスト2：host-4から10.0.1.10にping
問題無く疎通できました。
```
[root@host-4 ~]# ping 10.0.1.10 -c 3
PING 10.0.1.10 (10.0.1.10) 56(84) bytes of data.
64 bytes from 10.0.1.10: icmp_seq=1 ttl=127 time=0.461 ms
64 bytes from 10.0.1.10: icmp_seq=2 ttl=127 time=0.425 ms
64 bytes from 10.0.1.10: icmp_seq=3 ttl=127 time=0.480 ms

--- 10.0.1.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2101ms
rtt min/avg/max/mdev = 0.425/0.455/0.480/0.022 ms
```

# テスト3：host-2から172.16.1.10にping
問題無く疎通できました。
```
[root@host-2 ~]# ping 172.16.1.10 -c 3
PING 172.16.1.10 (172.16.1.10) 56(84) bytes of data.
64 bytes from 172.16.1.10: icmp_seq=1 ttl=127 time=0.467 ms
64 bytes from 172.16.1.10: icmp_seq=2 ttl=127 time=0.451 ms
64 bytes from 172.16.1.10: icmp_seq=3 ttl=127 time=0.513 ms

--- 172.16.1.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2088ms
rtt min/avg/max/mdev = 0.451/0.477/0.513/0.026 ms
```

ですがこの172.16.1.10はhost-1とhost-4のどちらになるのでしょうか？
これを確認するためにhost-1とhost-4でtcpdumpでパケットキャプチャを実行した状態でhost-2から172.16.1.10にpingを実行してみました。
```
tcpdump -p icmp
```

パケットキャプチャの結果から172.16.1.10はhost-1であること、またhost-2のvpc-2のENI(enX0)のIPである10.0.0.10が通信元となっていることが分かります。
```
[root@host-1 ~]# tcpdump -p icmp
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enX0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
21:47:11.201527 IP 10.0.0.10 > host-1: ICMP echo request, id 2, seq 1, length 64
21:47:11.201554 IP host-1 > 10.0.0.10: ICMP echo reply, id 2, seq 1, length 64
21:47:12.267557 IP 10.0.0.10 > host-1: ICMP echo request, id 2, seq 2, length 64
21:47:12.267579 IP host-1 > 10.0.0.10: ICMP echo reply, id 2, seq 2, length 64
21:47:13.307533 IP 10.0.0.10 > host-1: ICMP echo request, id 2, seq 3, length 64
21:47:13.307555 IP host-1 > 10.0.0.10: ICMP echo reply, id 2, seq 3, length 64
```

これはOSのルートテーブル上、172.16.1.10への通信はenX0が優先して使用されるようになっているためです。
host-2のルートテーブルを見てみましょう。
```
[root@host-2 ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.0.1        0.0.0.0         UG    512    0        0 enX0
0.0.0.0         10.0.1.1        0.0.0.0         UG    522    0        0 enX1
10.0.0.0        0.0.0.0         255.255.255.0   U     512    0        0 enX0
10.0.0.1        0.0.0.0         255.255.255.255 UH    512    0        0 enX0
10.0.0.2        0.0.0.0         255.255.255.255 UH    512    0        0 enX0
10.0.1.0        0.0.0.0         255.255.255.0   U     522    0        0 enX1
10.0.1.1        0.0.0.0         255.255.255.255 UH    522    0        0 enX1
10.0.1.2        0.0.0.0         255.255.255.255 UH    522    0        0 enX1
```
host-2から172.16.1.10への通信は上記ルートテーブルの以下2つの何れかが適用されますが、第5フィールドに表示されているMetricの値がより小さい方が優先して利用されるため結果としてenX0から発信されることになります。
0.0.0.0     10.0.0.1        0.0.0.0         UG    **512**    0        0 enX0　※enX0の方がMetricの値が小さい
0.0.0.0     10.0.1.1        0.0.0.0         UG    **522**    0        0 enX1

# host-2からhost-4へ疎通するにはどうすればよいのか？
それではhost-4の172.16.1.10へ疎通するためにはどのようにすればよいのでしょうか？
方法は2つあります。

## 方法1：インターフェースを明示的に指定する
pingコマンドの -I オプションでインターフェースを指定します。
```
[root@host-2 ~]# ping -I enX1 172.16.1.10 -c 3
PING 172.16.1.10 (172.16.1.10) from 10.0.1.10 enX1: 56(84) bytes of data.
64 bytes from 172.16.1.10: icmp_seq=1 ttl=127 time=0.380 ms
64 bytes from 172.16.1.10: icmp_seq=2 ttl=127 time=0.524 ms
64 bytes from 172.16.1.10: icmp_seq=3 ttl=127 time=0.395 ms

--- 172.16.1.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2065ms
rtt min/avg/max/mdev = 0.380/0.433/0.524/0.064 ms
```

host-4でのパケットキャプチャの実行結果からhost-2にアタッチされているvpc-3のENI(enX1)のIPアドレスである10.0.1.10からpingが届いていることを確認できました。
```
[root@host-4 ~]# tcpdump -p icmp
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enX0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
22:39:55.482581 IP 10.0.1.10 > host-4: ICMP echo request, id 9, seq 1, length 64
22:39:55.482617 IP host-4 > 10.0.1.10: ICMP echo reply, id 9, seq 1, length 64
22:39:56.507575 IP 10.0.1.10 > host-4: ICMP echo request, id 9, seq 2, length 64
22:39:56.507598 IP host-4 > 10.0.1.10: ICMP echo reply, id 9, seq 2, length 64
22:39:57.547480 IP 10.0.1.10 > host-4: ICMP echo request, id 9, seq 3, length 64
22:39:57.547507 IP host-4 > 10.0.1.10: ICMP echo reply, id 9, seq 3, length 64
```

## 方法2：OSのルートテーブルで制御する
host-2でrouteコマンドで172.16.1.10への通信に対するルーティング設定を追加します。
※gatewayにvpc-3の仮想ルーターである10.0.1.1を指定する必要があります
```
[root@host-2 ~]# route add -host 172.16.1.10 gw 10.0.1.1 dev enX1
[root@host-2 ~]# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.0.1        0.0.0.0         UG    512    0        0 enX0
0.0.0.0         10.0.1.1        0.0.0.0         UG    522    0        0 enX1
10.0.0.0        0.0.0.0         255.255.255.0   U     512    0        0 enX0
10.0.0.1        0.0.0.0         255.255.255.255 UH    512    0        0 enX0
10.0.0.2        0.0.0.0         255.255.255.255 UH    512    0        0 enX0
10.0.1.0        0.0.0.0         255.255.255.0   U     522    0        0 enX1
10.0.1.1        0.0.0.0         255.255.255.255 UH    522    0        0 enX1
10.0.1.2        0.0.0.0         255.255.255.255 UH    522    0        0 enX1
172.16.1.10     10.0.1.1        255.255.255.255 UGH   0      0        0 enX1　※追加されたルーティング
```

この状態でhost-2から172.16.1.10へpingを実行します。
```
[root@host-2 ~]# ping 172.16.1.10 -c 3
PING 172.16.1.10 (172.16.1.10) 56(84) bytes of data.
64 bytes from 172.16.1.10: icmp_seq=1 ttl=127 time=0.442 ms
64 bytes from 172.16.1.10: icmp_seq=2 ttl=127 time=0.432 ms
64 bytes from 172.16.1.10: icmp_seq=3 ttl=127 time=0.441 ms

--- 172.16.1.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2114ms
rtt min/avg/max/mdev = 0.432/0.438/0.442/0.004 ms
```

pingコマンドでインターフェースを指定しなくてもhost-2にアタッチされているvpc-3のENI(enX1)のIPアドレスである10.0.1.10からpingが届いていることを確認できました。
```
[root@host-4 ~]# tcpdump -p icmp
dropped privs to tcpdump
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on enX0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
22:52:14.899220 IP 10.0.1.10 > host-4: ICMP echo request, id 12, seq 1, length 64
22:52:14.899243 IP host-4 > 10.0.1.10: ICMP echo reply, id 12, seq 1, length 64
22:52:15.947494 IP 10.0.1.10 > host-4: ICMP echo request, id 12, seq 2, length 64
22:52:15.947519 IP host-4 > 10.0.1.10: ICMP echo reply, id 12, seq 2, length 64
22:52:20.233506 IP 10.0.1.10 > host-4: ICMP echo request, id 13, seq 1, length 64
22:52:20.233530 IP host-4 > 10.0.1.10: ICMP echo reply, id 13, seq 1, length 64
22:52:21.307539 IP 10.0.1.10 > host-4: ICMP echo request, id 13, seq 2, length 64
22:52:21.307564 IP host-4 > 10.0.1.10: ICMP echo reply, id 13, seq 2, length 64
22:52:22.347542 IP 10.0.1.10 > host-4: ICMP echo request, id 13, seq 3, length 64
22:52:22.347573 IP host-4 > 10.0.1.10: ICMP echo reply, id 13, seq 3, length 64
```

# まとめ
通信先に同一CIDRのVPCが存在するというNW環境でMulti-VPC ENI Attachmentsを試してみました。
アプリケーション側(今回はping)でのインターフェース指定やOSのルーティング設定で通信先を制御することが可能であることが分かりましたが、基本的にはCIDRが被らないNW構成を設計しましょう。

part2の記事ではvpc-2とvpc-3も同一CIDRにした更にヤバいNW構成での検証結果を記載しています。
[Multi-VPC ENI Attachmentsを試してみた part2](https://qiita.com/Shinya_K/items/26628db3b4f90c4f091b)