LinuxにおけるVRF基礎
概要
VRF(Virtual Routing and Forwarding)は、単一のルータやサーバにおいて複数のルーティングテーブルを使用できる技術になります。通常Linuxでデフォルトで使用するのはグローバルのルーティングテーブルになりますが、VRFはそれとは別に独立したルーティングテーブルを持ち、独立したVRFに基づいてパケットを分離、ルーティングすることができます。
VRFと類似の機能にNetwork Namespaceがありますが、目的と使い方に違いがあるため用途に合わせて使い分けることが必要です。Network Namespaceは、カーネルレベルでネットワークスタック全体を分離する手法であり、対してVRFは主にルーティングテーブルの分離のみに限定しており軽量に動作します。Network Namespaceは、テーブルの分離だけではなく、スタックを分離しているためインターフェース、ルーティングテーブル、iptables、ip rule、resolverなどを分離して動かすことができます。そのため、両者は包含関係にあり、例えば1つのNetwork Namespace内に複数のVRFのテーブルを持つことができます。
基本操作
LinuxでVRFを作成するには、ip
コマンドを使用します。以下に基本的な手順を示します。
なお今回は、以下の環境を使用しています。基本的にKernel v4.4で動作しますが、l3mdev FIB ルールをサポートするためKernelは v4.8 以上を推奨します。
$ cat /proc/version
Linux version 5.15.0-117-generic (buildd@lcy02-amd64-102) (gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0, GNU ld (GNU Binutils for Ubuntu) 2.38)
-
$ sudo ip link add vrf-blue type vrf table 1001
vrf-blue
はVRFデバイスの名前、1001
はルーティングテーブルIDです。このコマンドで、新しいVRFデバイスが作成されます。 -
$ sudo ip link set dev vrf-blue up
-
$ ip -d link show vrf-blue 4: vrf-blue: <NOARP,MASTER,UP,LOWER_UP> mtu 65575 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 3a:e8:ee:11:68:cd brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 1280 maxmtu 65575 vrf table 1001 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 6553 $ ip -d link show type vrf 4: vrf-blue: <NOARP,MASTER,UP,LOWER_UP> mtu 65575 qdisc noqueue state UP mode DEFAULT group default qlen 1000 link/ether 3a:e8:ee:11:68:cd brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 1280 maxmtu 65575 vrf table 1001 addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 $ ip -br link show type vrf vrf-blue UP 3a:e8:ee:11:68:cd <NOARP,MASTER,UP,LOWER_UP>
いくつか確認方法を示しましたが、
vrf-blue
が正しく設定されているかを確認できます。2番目の例ではtype vrf
とすることで名称を知らなくても一覧を得ることができます。 例に-d
オプションが含まれますが、これはルーティングテーブルIDを表示させるために必要だからです。 -
$ sudo ip link set dev eth0 master vrf-blue
eth0
インターフェースがvrf-blue
VRFに割り当てられます。 -
$ ip link show vrf vrf-blue 3: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master vrf-blue state UP mode DEFAULT group default qlen 1000 link/ether 50:25:5d:00:01:01 brd ff:ff:ff:ff:ff:ff $ ip -br link show vrf vrf-blue eth0 UP 50:25:5d:00:01:01 <BROADCAST,MULTICAST,UP,LOWER_UP>
eth0が
vrf-blue
に割り当てられていることが確認できます。インターフェースが多く存在する場合には-br
オプションを付与することで簡易表示することもできます。 -
$ sudo ip addr add dev eth0 192.168.100.1/24 $ ip -d address show eth0 3: ens4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel master vrf-blue state UP group default qlen 1000 link/ether 50:25:5d:00:01:01 brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535 vrf_slave table 1001 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535 parentbus virtio parentdev virtio2 inet 192.168.100.1/24 scope global ens4 valid_lft forever preferred_lft forever inet6 fe80::5225:5dff:fe00:101/64 scope link valid_lft forever preferred_lft forever
-
$ ip neighbor show vrf vrf-blue 192.168.100.2 dev eth0 lladdr 00:50:79:66:68:02 DELAY
L2解決できているアドレスを確認しています。この例では1つの隣接関係しか表示されていませんが、Broadcast型のLANネットワークの場合には通常複数の隣接関係が表示されるはずです。
-
$ sudo ip route add 192.168.200.0/24 via 192.168.100.2 vrf vrf-blue $ ip route show vrf vrf-blue 192.168.100.0/24 dev eth0 proto kernel scope link src 192.168.100.1 192.168.200.0/24 via 192.168.100.2 dev eth0 $ ip route show table 1001 192.168.100.0/24 dev eth0 proto kernel scope link src 192.168.100.1 local 192.168.100.1 dev eth0 proto kernel scope host src 192.168.100.1 broadcast 192.168.100.255 dev eth0 proto kernel scope link src 192.168.100.1 192.168.200.0/24 via 192.168.100.2 dev eth0 $ ip route get 192.168.200.1 vrf vrf-blue 192.168.200.1 via 192.168.100.2 dev eth0 table 1001 src 192.168.100.1 uid 1000 cache
table 1001
とvrf vrf-blue
の指定方法の違いで表示に差分が生じていますが、これはテーブル指定の場合にはVRFに直接関連しないルート(たとえば、ローカル、ホストルートやブロードキャストルート)も省略されることなく全て表示されるからです。 -
VRF内部からのコマンドを実行したい場合には、複数の方法があります。
ip
コマンドを利用したVRF内部からコマンドを実行する方法とアプリケーションからインターフェースを指定する方法になります。$ sudo ip vrf exec vrf-blue ping -c 1 192.168.100.2 PING 192.168.100.2 (192.168.100.2) 56(84) bytes of data. 64 bytes from 192.168.100.2: icmp_seq=1 ttl=64 time=3.45 ms --- 192.168.100.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 3.449/3.449/3.449/0.000 ms $ ping -c 1 -I eth0 192.168.100.2 PING 192.168.100.2 (192.168.100.2) from 192.168.100.1 ens4: 56(84) bytes of data. 64 bytes from 192.168.100.2: icmp_seq=1 ttl=64 time=1.72 ms --- 192.168.100.2 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 1.724/1.724/1.724/0.000 ms $ sudo ip vrf exec vrf-blue traceroute 192.168.100.2 traceroute to 192.168.100.2 (192.168.100.2), 30 hops max, 60 byte packets 1 192.168.100.2 (192.168.100.2) 5.834 ms 1.943 ms 2.065 ms
-
ディバイスeth0に対して
ip link set dev eth0 master vrf-blue
コマンドでVRFの割り当てを行いました。master指定によりインターフェースを特定の仮装インターフェースに関連づけることができます。今回はVRFに割り当てましたが、他にもBONDING、BRIDGEなどの指定ができます。インターフェースを関連付けから切り離すには、nomaster
キーワードを使用します。$ sudo ip link set dev eth0 nomaster
-
/etc/iproute2/rt_tables
ファイルには、ルーティングテーブルのエントリが含まれています。ここには、デフォルトのテーブルIDとカスタムのテーブル名が一覧表示されています。$cat /etc/iproute2/rt_tables # # reserved values # 255 local 254 main 253 default 0 unspec # # local # #1 inr.ruhep 1001 vrf1 #(カスタムの例)
このファイルに、VRFに関連するテーブルIDが記載されている場合は対応するテーブルIDとVRF名が確認できます。カスタムで使用できるのは確保されていないテーブルIDを使用することになります。
ip
コマンドでVRFを追加し場合には、Kernel内部で保持されることからこのファイルへの書き込みはありません。こちらのファイルを手動で追加した場合には起動時に反映されてVRF名が利用できるようになります。テーブルIDを新たに確保する場合、いくつかのテーブルIDはOSで確保済みであり使用できません。テーブル番号 テーブル名 使用用途 255 local ローカルに設定されたIPアドレスに関するルートを保持します。ループバックアドレスや、ローカルホスト上で設定されたIPアドレスへのルーティングに使用されます。(例:127.0.0.1などの自ホスト宛を処理) 254 main 何もテーブルを指定しない時にデフォルトで選択されるテーブルです。通常はこのテーブルを利用します。 253 default このテーブルは通常、特定の条件に基づいてデフォルトルートを保持します。特に指定がない限り使用されることはありませんが、共通のポリシールーティングに基づくデフォルト動作などの管理で使用されることがあります。 0 unspec これは無指定(unspecified)を意味し、通常の操作では使用されません。特にポリシールーティングを設定する際などに明示的にルーティングテーブルを指定しない場合や、テーブルを削除する場合に使用されることがあります。 -
VRFは同一の物理インターフェースを持つデバイス上で複数のネットワークを隔離して管理することが可能になります。しかし、VRF に関連付けられた
l3mdev
から着信するパケットは、デフォルトではシステム上のプロセスにアクセスできない状態がデフォルトの設定になります。$ sudo sysctl -a | grep l3mdev net.ipv4.raw_l3mdev_accept = 1 net.ipv4.tcp_l3mdev_accept = 0 #not accept net.ipv4.udp_l3mdev_accept = 0 #not accept
これは通常アプリケーション側のソケットがデフォルトではVRFディバイスを意識した実装を行なっていないからです。
setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, dev, strlen(dev)+1);
着信できるようにするためにはLinux システムのネットワーク設定を調整する必要があります。
sysctl
ツールを使用して、TCP および UDP パケットの受信条件を変更することができます。変更後は全てのVRFからパケットの着信が可能になるためアプリケーション側の変更が必要なくなりますが、セキュリティ面では注意が必要となります。$ sudo sysctl -w net.ipv4.tcp_l3mdev_accept=1 net.ipv4.tcp_l3mdev_accept=1 $ sudo sysctl -w net.ipv4.udp_l3mdev_accept=1 net.ipv4.udp_l3mdev_accept=1
これらの設定により、VRF に関連付けられた
l3mdev
デバイスからの TCP および UDP パケットの受信を許可することができます。または、永続的にするためには、/etc/sysctl.conf
ファイルに設定を追加して、sysctl -p
または再起動で反映が必要になります。
ネットワーク構成例
下記の例では、2 つのネットワークネームスペース (NS1, NS2) と 3 つの VRF (VRF1, VRF2, VRF3) を作成し、それぞれを VETH インターフェースで接続します。NS1/VRF1内部のIFからNS2/VRF2を経由してNS2/VRF3までルーチングを確保し、ネットワーク的な到達性を確保します。
+------------------+ +-----------------------------------------------------+
| NS1 | | NS2 |
| | | |
| +-------------+ | | +----------------+ +------------+|
| | VRF1 |.1| 192.168.100.0/24 |.2| VRF2 | 192.168.200.0/24 | VRF3 ||
| | veth2-vrf1 +--|------------------+--+ veth2-vrf2 |.1 .2| ||
| | | | | | veth1-vrf2+-------------------+ veth1-vrf3 ||
| | | | | | | | ||
| | | | | | | | ||
| | | | | | | | ||
| | | | | | | | ||
| +-------------+ | | +----------------+ +------------+|
+------------------+ +-----------------------------------------------------+
ネームスペース,VRFの作成と有効化
まず、2 つのネットワークネームスペース (NS1, NS2) を作成して、NS上でVRFを作成します。NSと紐づいたVRFを作成できたら、VRFディバイスを有効化します。
# ネームスペース NS1, NS2 の作成
$ sudo ip netns add NS1
$ sudo ip netns add NS2
$ ip netns list
NS1
NS2
# VRF の作成
$ sudo ip netns exec NS1 ip link add VRF1 type vrf table 1001
$ sudo ip netns exec NS2 ip link add VRF2 type vrf table 1002
$ sudo ip netns exec NS2 ip link add VRF3 type vrf table 1003
# VRF をネームスペース内で有効化
$ sudo ip netns exec NS1 ip link set dev VRF1 up
$ sudo ip netns exec NS2 ip link set dev VRF2 up
$ sudo ip netns exec NS2 ip link set dev VRF3 up
$ sudo ip -all netns exec ip -br link show type vrf
netns: NS1
VRF1 UP c6:1c:5f:68:01:d2 <NOARP,MASTER,UP,LOWER_UP>
netns: NS2
VRF2 UP 0a:af:fc:b3:e0:cc <NOARP,MASTER,UP,LOWER_UP>
VRF3 UP 62:d7:7a:c5:33:98 <NOARP,MASTER,UP,LOWER_UP>
VETHの接続とアドレス付与
VRF内にVETHのペアを2セット作成して、VETHをまずNSと紐づけた上でVRFに割り当てます。VETHの有効化を行い、L3情報の付与を行います。この時点でNS/VRF越しのIP疎通が可能になりますので、PINGでVETHピア同士の疎通を確認します。
# VETH ペアの作成と VRF2, VRF3 の接続
$ sudo ip link add veth1-vrf2 type veth peer name veth1-vrf3
$ sudo ip link add veth2-vrf1 type veth peer name veth2-vrf2
# netns と veth 紐づけ
$ sudo ip link set veth1-vrf2 netns NS2
$ sudo ip link set veth1-vrf3 netns NS2
$ sudo ip link set veth2-vrf1 netns NS1
$ sudo ip link set veth2-vrf2 netns NS2
# インターフェースをVRFに割り当て
$ sudo ip netns exec NS1 ip link set dev veth2-vrf1 master VRF1
$ sudo ip netns exec NS2 ip link set dev veth2-vrf2 master VRF2
$ sudo ip netns exec NS2 ip link set dev veth1-vrf2 master VRF2
$ sudo ip netns exec NS2 ip link set dev veth1-vrf3 master VRF3
# VETH ペアの有効化
$ sudo ip netns exec NS1 ip link set dev veth2-vrf1 up
$ sudo ip netns exec NS2 ip link set dev veth2-vrf2 up
$ sudo ip netns exec NS2 ip link set dev veth1-vrf2 up
$ sudo ip netns exec NS2 ip link set dev veth1-vrf3 up
#NS1 veth2-vrf1 .1---- 192.168.100.0/24 ---.2 veth2-vrf2 NS2
#NS2 veth1-vrf2 .1---- 192.168.200.0/24 ---.2 veth1-vrf3 NS2
# L3アドレスの付与
$ sudo ip netns exec NS1 ip addr add dev veth2-vrf1 192.168.100.1/24
$ sudo ip netns exec NS2 ip addr add dev veth2-vrf2 192.168.100.2/24
$ sudo ip netns exec NS2 ip addr add dev veth1-vrf2 192.168.200.1/24
$ sudo ip netns exec NS2 ip addr add dev veth1-vrf3 192.168.200.2/24
$ sudo ip -all netns exec ip -br -4 addr show
netns: NS1
lo UNKNOWN 127.0.0.1/8
veth2-vrf1@if6 UP 192.168.100.1/24
netns: NS2
lo UNKNOWN 127.0.0.1/8
veth1-vrf2@veth1-vrf3 UP 192.168.200.1/24
veth1-vrf3@veth1-vrf2 UP 192.168.200.2/24
veth2-vrf2@if5 UP 192.168.100.2/24
$ sudo ip -all netns exec ip -d -4 addr show
netns: NS1
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
5: veth2-vrf1@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master VRF1 state UP group default qlen 1000
link/ether 52:4e:47:47:05:91 brd ff:ff:ff:ff:ff:ff link-netns NS2 promiscuity 0
veth
vrf_slave table 1001 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 192.168.100.1/24 scope global veth2-vrf1
valid_lft forever preferred_lft forever
netns: NS2
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00 promiscuity 0 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
4: veth1-vrf2@veth1-vrf3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master VRF2 state UP group default qlen 1000
link/ether 7a:64:27:34:a0:27 brd ff:ff:ff:ff:ff:ff promiscuity 0
veth
vrf_slave table 1002 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 192.168.200.1/24 scope global veth1-vrf2
valid_lft forever preferred_lft forever
5: veth1-vrf3@veth1-vrf2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master VRF3 state UP group default qlen 1000
link/ether 3a:49:a7:e9:67:61 brd ff:ff:ff:ff:ff:ff promiscuity 0
veth
vrf_slave table 1003 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 192.168.200.2/24 scope global veth1-vrf3
valid_lft forever preferred_lft forever
6: veth2-vrf2@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master VRF2 state UP group default qlen 1000
link/ether a6:7a:47:8f:40:fb brd ff:ff:ff:ff:ff:ff link-netns NS1 promiscuity 0
veth
vrf_slave table 1002 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535
inet 192.168.100.2/24 scope global veth2-vrf2
valid_lft forever preferred_lft forever
$ sudo ip -n NS1 vrf exec VRF1 ping -c 1 -I veth2-vrf1 192.168.100.2
PING 192.168.100.2 (192.168.100.2) from 192.168.100.1 veth2-vrf1: 56(84) bytes of data.
64 bytes from 192.168.100.2: icmp_seq=1 ttl=64 time=0.039 ms
--- 192.168.100.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.039/0.039/0.039/0.000 ms
$ sudo ip -n NS2 vrf exec VRF2 ping -c 1 -I veth1-vrf2 192.168.200.2
PING 192.168.200.2 (192.168.200.2) from 192.168.200.1 veth1-vrf2: 56(84) bytes of data.
64 bytes from 192.168.200.2: icmp_seq=1 ttl=64 time=0.077 ms
--- 192.168.200.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.077/0.077/0.077/0.000 ms
VRFへのルート追加とエンド疎通
前節までの状態では、VRF内にはConnectedルートのみが存在しているため、VRF1からVRF3のエンド同士の疎通はできません。VRF1とVRF3にお互いのルートを追加することで、エンド同士での疎通ができるようにします。
# 現在のルート確認
$ sudo ip -n NS1 route show vrf VRF1
192.168.100.0/24 dev veth2-vrf1 proto kernel scope link src 192.168.100.1
$ sudo ip -n NS2 route show vrf VRF2
192.168.100.0/24 dev veth2-vrf2 proto kernel scope link src 192.168.100.2
192.168.200.0/24 dev veth1-vrf2 proto kernel scope link src 192.168.200.1
$ sudo ip -n NS2 route show vrf VRF3
192.168.200.0/24 dev veth1-vrf3 proto kernel scope link src 192.168.200.2
# 必要なルートの追加
$ sudo ip -n NS1 route add 192.168.200.0/24 via 192.168.100.2 vrf VRF1
$ sudo ip -n NS2 route add 192.168.100.0/24 via 192.168.200.1 vrf VRF3
# 追加ルートの確認
$ sudo ip -n NS1 route show vrf VRF1
192.168.100.0/24 dev veth2-vrf1 proto kernel scope link src 192.168.100.1
192.168.200.0/24 via 192.168.100.2 dev veth2-vrf1
$ sudo ip -n NS2 route show vrf VRF3
192.168.100.0/24 via 192.168.200.1 dev veth1-vrf3
192.168.200.0/24 dev veth1-vrf3 proto kernel scope link src 192.168.200.2
# VRF1からVRF3への疎通確認
$ sudo ip netns exec NS1 ping 192.168.200.2 -I veth2-vrf1 -c 1
PING 192.168.200.2 (192.168.200.2) from 192.168.100.1 veth2-vrf1: 56(84) bytes of data.
64 bytes from 192.168.200.2: icmp_seq=1 ttl=63 time=0.085 ms
--- 192.168.200.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.085/0.085/0.085/0.000 ms
まとめ
VRFは、ネットワークの仮想化とセキュリティ強化するためのツールであり、適切に活用することで、ネットワーク運用の効率化と信頼性向上が期待できます。
Discussion