Linux上的VRF

Linux上的VRF

VRF 的概念可能是大家熟悉的概念,如果你不熟悉可以查阅Linux文档中关于他的描述。当我们谈论 3 层网络隔离时,它们被广泛的使用。正如我们之前所讨论的,它们广泛用于 MPLS VPN 等应用,真正为第 3 层网络隔离奠定了基础。它们通过允许创建多个路由表来实现此目的。然后,可以将任何第 3 层构造映射到 VRF 中。例如:

  • 我可以为接口分配一个 IP 地址,然后将该接口映射到 VRF 中。
  • 同样,我可以配置静态路由,并指定该路由是给定 VRF 的一部分。
  • 更进一步,我可以在其中一个 VRF 接口上建立 BGP 会话,并接收到 VRF 的远程 BGP 路由。VRF 属于第 3 层,就像 VLAN 属于第 2 层一样。

因此,虽然我们已经讨论了它们通常如何在路由器和交换机等网络硬件上使用和实现,但我们还没有讨论它们如何在 Linux 中实现。实际上,它们对 Linux 领域来说是相当新的。该功能实际上是由 Cumulus Networks 编写的,然后贡献给了 Linux 内核(感谢他们这样做)。VRF 是在内核 4.3 版中引入的,大概是在2015 年左右推出的。那么在4.3版本引入 VRF 之前,人们如何解决相关问题的呢?我们来先谈论这个问题。

VRF推出之前的故事

在 VRF 推出之前,实际上有 2 个选项可用于在 Linux 中创建类似 VRF 的构造。

路由表+策略路由

一种选择是利用多个路由表和策略路由来制作看起来像 VRF 的东西。我尝试过一次,但是我不建议这样做。因为缺点远远大于优点,我认为你很难通过有关网络隔离的任何类型的审计。

网络命名空间

更流行的选择是利用网络命名空间。当容器出现并开始使用网络命名空间来管理容器隔离之后,随着容器化技术火热的发展,这变得非常流行。但是,尽管网络命名空间确实运行良好,但它们对于我们想要的东西来说是矫枉过正的。Cumulus 在他们关于 VRF 的文章中详细讨论了这一点,所以这里就不多费口舌了。但是,有一个缺点还是很值得指出的。网络命名空间隔离了“所有的东西”。在我们的例子中,“所有的东西”包括整个网络堆栈——设备、接口、ARP表、路由表等。

普通路由器上的VRF配置

熟悉 VRF 工作原理的人都知道,这不是我们通常在“普通”(指的是那些供应商提供的)路由器中使用 VRF 功能获得的。如果我们看一下普通路由器上的示例VRF配置,这一点就很明显了。例如,让我们看一个看起来像这样的设置…

img

上面我们有一个有着两个接口的路由器。接口 ge-0/0/0 是 vrf vrf-1的成员,而接口ge-0/0/1vrf-2的成员。我们还在每个 VRF 中定义了一些静态路由。很简单,对吧?路由器上的配置可能如下所示…

set interfaces ge-0/0/0 unit 0 family inet address 192.168.10.1/24
set interfaces ge-0/0/1 unit 0 family inet address 192.168.10.1/24

set routing-instances vrf-1 instance-type virtual-router
set routing-instances vrf-1 interface ge-0/0/0.0
set routing-instances vrf-1 routing-options static route 0.0.0.0/0 next-hop 192.168.10.254
set routing-instances vrf-1 routing-options static route 172.64.32.0/24 next-hop 192.168.10.101

set routing-instances vrf-2 instance-type virtual-router
set routing-instances vrf-2 interface ge-0/0/1.0
set routing-instances vrf-2 routing-options static route 0.0.0.0/0 next-hop 192.168.10.10
set routing-instances vrf-2 routing-options static route 192.168.128.0/24 next-hop 192.168.10.20

注意:在JunOS术语中,本地VRF(思科过去称为VRF-lite)又名未用作MPLS VPN的一部分且未分配RD/RT的VRF类型为“virtual-router”,而不是类型“vrf”。

上面的配置中没有什么难的。但是,让我们看一下路由表等。

root@vmx1.lab> show route table vrf-1.inet.0    

vrf-1.inet.0: 4 destinations, 4 routes (4 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0 *[Static/5] 00:00:02
> to 192.168.10.254 via ge-0/0/0.0
172.64.32.0/24 *[Static/5] 00:00:02
> to 192.168.10.101 via ge-0/0/0.0
192.168.10.0/24 *[Direct/0] 00:01:10
> via ge-0/0/0.0
192.168.10.1/32 *[Local/0] 00:01:10
Local via ge-0/0/0.0

root@vmx1.lab> show route table vrf-2.inet.0

vrf-2.inet.0: 4 destinations, 4 routes (4 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0 *[Static/5] 00:00:06
> to 192.168.10.10 via ge-0/0/1.0
192.168.10.0/24 *[Direct/0] 00:01:14
> via ge-0/0/1.0
192.168.10.1/32 *[Local/0] 00:01:14
Local via ge-0/0/1.0
192.168.128.0/24 *[Static/5] 00:00:06
> to 192.168.10.20 via ge-0/0/1.0

root@vmx1.lab>

这里要注意的重要一点是,我需要告诉路由器我想查看特定的VRF路由表。但我不需要做的是查看 VRF 上下文内部,以查看平台通用的内容。最明显的例子是我上面粘贴的配置。我有一个用于所有 VRF 的全局配置。 当然,我将内容映射到特定的 VRF 中,但我有一个统一的界面来查看所有这些配置。即使我的接口现在映射到 VRF 中,我仍然可以在平台上看到它们,而无需任何特定命令来查看路由实例内部…

root@vmx1.lab> show interfaces terse | grep ge-0/0/ 
ge-0/0/0 up up
ge-0/0/0.0 up up inet 192.168.10.1/24
ge-0/0/1 up up
ge-0/0/1.0 up up inet 192.168.10.1/24
ge-0/0/2 up down
ge-0/0/3 up down
ge-0/0/4 up down
ge-0/0/5 up down
ge-0/0/6 up down
ge-0/0/7 up down
ge-0/0/8 up down
ge-0/0/9 up down

root@vmx1.lab>

或许更重要的是,在路由器上运行的不具备 L3 感知能力的服务只能在整个平台上运行一次。也就是说,我不需要在所有 VRF 中运行所有这些服务。一个很好的例子是像LLDP这样的东西。

LLDP,全称Link Layer Discovery Protocol(链路层发现协议),是一种用于网络设备之间交换信息的标准协议。LLDP可以让网络设备向其他设备广播自己的身份,以及自己的能力和配置。同时,设备也可以接收并解析其他设备发送的LLDP信息,从而了解网络的拓扑结构。

LLDP是IEEE 802.1AB标准的一部分,是一个开放的、供应商中立的协议。它可以在任何类型的网络设备上运行,包括交换机、路由器、服务器、无线接入点等。

LLDPd是一个实现了链路层发现协议(LLDP)的守护进程,用于帮助网络管理员了解和管理复杂的网络环境。以下是一些LLDPd的实际应用场景:

  1. 网络拓扑发现:通过LLDP,网络设备可以广播自己的信息,并接收其他设备的信息。这样,管理员可以了解到哪些设备直接连接在一起,从而了解网络的物理拓扑。这对于大型的、复杂的网络环境尤其有用。
  2. 设备识别和管理:LLDP可以传输各种类型的设备信息,包括设备名称、设备类型、端口信息、设备能力等。这些信息可以帮助管理员了解网络中的设备状态,进行设备管理和故障排查。
  3. 自动配置:在一些高级的应用中,设备可以根据收到的LLDP信息自动进行配置。例如,一个交换机可以根据连接的设备类型自动配置VLAN,或者一个无线接入点可以根据收到的信息自动调整信道和功率。
  4. 资产管理:在大型的IT环境中,LLDP可以帮助管理员跟踪设备的位置和连接状态,从而进行有效的资产管理。
  5. 安全性:LLDP可以用于检测未授权的设备接入,提高网络的安全性。

我已将我的两个“向上”接口映射到 VRF 中,但 LLDP 仍然只是在平台上运行,我仍然可以看到所有 LLDP 邻居等,而无需在给定的 VRF 中运行软件或命令…

root@vmx1.lab> show lldp neighbors 
Local Interface Parent Interface Chassis Id Port info System Name
ge-0/0/1 - 2c:6b:f5:4a:23:c0 ge-0/0/0 vmx3.lab
ge-0/0/0 - 2c:6b:f5:f9:08:c0 ge-0/0/1 vmx2.lab

root@vmx1.lab>

在Linux上配置

使用网络空间

如果这对你来说仍然不足以说服你,那么如果我们要在 Linux 服务上使用网络命名空间来提供 L3 隔离,让我们考虑相同的场景。假设我们有一个看起来像这样的 Linux 服务器。

img

配置这样的东西可能看起来像这样…

ip netns add namespace-1
ip link set dev ens6 netns namespace-1
ip netns exec namespace-1 ip link set dev ens6 up
ip netns exec namespace-1 ip address add 192.168.10.1/24 dev ens6
ip netns exec namespace-1 ip route add 0.0.0.0/0 via 192.168.10.254
ip netns exec namespace-1 ip route add 172.64.32.0/24 via 192.168.10.101

ip netns add namespace-2
ip link set dev ens7 netns namespace-2
ip netns exec namespace-2 ip link set dev ens7 up
ip netns exec namespace-2 ip address add 192.168.10.1/24 dev ens7
ip netns exec namespace-2 ip route add 0.0.0.0/0 via 192.168.10.10
ip netns exec namespace-2 ip route add 192.168.128.0/24 via 192.168.10.20

和我们在路由器上的情形差不多。我们配置命名空间,为其添加接口,然后在命名空间内添加其他路由。我们最终得到了一些与我们上面配置非常接近的东西…

root@vm1:~# ip netns exec namespace-1 ip route show
default via 192.168.10.254 dev ens6
172.64.32.0/24 via 192.168.10.101 dev ens6
192.168.10.0/24 dev ens6 proto kernel scope link src 192.168.10.1
root@vm1:~# ip netns exec namespace-2 ip route show
default via 192.168.10.10 dev ens7
192.168.10.0/24 dev ens7 proto kernel scope link src 192.168.10.1
192.168.128.0/24 via 192.168.10.20 dev ens7
root@vm1:~#

现在,事情开始变得不一样了。如果我想在这个服务器上运行类似 LLDP 的东西——我该怎么做?让我们从安装它开始,看看它看到了什么…

root@vm1:~# apt -y install lldpd
Reading package lists... Done
Building dependency tree
Reading state information... Done
Suggested packages:
snmpd
The following NEW packages will be installed:
lldpd
0 upgraded, 1 newly installed, 0 to remove and 126 not upgraded.
Need to get 0 B/154 kB of archives.
After this operation, 511 kB of additional disk space will be used.
Selecting previously unselected package lldpd.
(Reading database ... 107391 files and directories currently installed.)
Preparing to unpack .../lldpd_1.0.4-1build2_amd64.deb ...
Unpacking lldpd (1.0.4-1build2) ...
Setting up lldpd (1.0.4-1build2) ...
Processing triggers for libc-bin (2.31-0ubuntu9.1) ...
Processing triggers for systemd (245.4-4ubuntu3.3) ...
Processing triggers for man-db (2.9.1-1) ...
root@vm1:~#
root@vm1:~# lldpcli show interfaces
-------------------------------------------------------------------------------
LLDP interfaces:
-------------------------------------------------------------------------------
Interface: ens3, via: unknown, Time: 0 day, 00:19:09
Chassis:
ChassisID: mac 52:ab:54:ab:01:01
SysName: vm1
SysDescr: Ubuntu 20.04.1 LTS Linux 5.9.4-050904-generic #202011042130 SMP Wed Nov 4 21:36:10 UTC 2020 x86_64
MgmtIP: 192.168.127.1
MgmtIP: fe80::50ab:54ff:feab:101
Capability: Bridge, off
Capability: Router, off
Capability: Wlan, off
Capability: Station, on
Port:
PortID: mac 52:ab:54:ab:01:01
PortDescr: ens3
TTL: 120
-------------------------------------------------------------------------------
root@vm1:~# lldpcli show neighbors
-------------------------------------------------------------------------------
LLDP neighbors:
-------------------------------------------------------------------------------
root@vm1:~#

注意,它没有检测到我们移动到命名空间中的两个接口ens6ens7 。但这是正常的吗,接口不再位于全局命名空间中,所以我们在全局命名空间中根本无法看到。同样,将其与我们的路由器进行比较,我们仍然可以看到接口,我们刚刚隔离了 L3 结构。为了像之前一样工作,我们实际上需要在每个命名空间中运行lldpd。由于默认情况下ldapd使用相同的socket,因此我们需要为每个实例创建唯一的socket。就像这样…

root@vm1:~# ip netns exec namespace-1 /usr/sbin/lldpd -u /var/run/lldpd-namespace-1.socket
root@vm1:~# ip netns exec namespace-1 lldpcli -u /var/run/lldpd-namespace-1.socket show interfaces
-------------------------------------------------------------------------------
LLDP interfaces:
-------------------------------------------------------------------------------
Interface: ens6, via: unknown, Time: 18739 days, 20:45:55
Chassis:
ChassisID: mac 52:ab:54:cd:01:01
SysName: vm1
SysDescr: Ubuntu 20.04.1 LTS Linux 5.9.4-050904-generic #202011042130 SMP Wed Nov 4 21:36:10 UTC 2020 x86_64
MgmtIP: 192.168.10.1
MgmtIP: fe80::50ab:54ff:fecd:101
Capability: Bridge, off
Capability: Router, off
Capability: Wlan, off
Capability: Station, on
Port:
PortID: mac 52:ab:54:cd:01:01
PortDescr: ens6
TTL: 120
-------------------------------------------------------------------------------
root@vm1:~# ip netns exec namespace-1 lldpcli -u /var/run/lldpd-namespace-1.socket show neighbors
-------------------------------------------------------------------------------
LLDP neighbors:
-------------------------------------------------------------------------------
Interface: ens6, via: LLDP, RID: 1, Time: 0 day, 00:00:39
Chassis:
ChassisID: mac 52:ab:54:ab:02:01
SysName: vm2
SysDescr: Ubuntu 20.04.1 LTS Linux 5.9.4-050904-generic #202011042130 SMP Wed Nov 4 21:36:10 UTC 2020 x86_64
MgmtIP: 192.168.127.2
MgmtIP: fe80::50ab:54ff:feab:201
Capability: Bridge, off
Capability: Router, off
Capability: Wlan, off
Capability: Station, on
Port:
PortID: mac 52:ab:54:cd:02:02
PortDescr: ens7
TTL: 120
-------------------------------------------------------------------------------
root@vm1:~#

因此,我们可以在命名空间中运行另一个lldpd实例,这使我们能够查看本地命名空间接口和这些接口中可用的邻居。同样的事情也可以在namespace-2中做…

root@vm1:~# ip netns exec namespace-2 /usr/sbin/lldpd -u /var/run/lldpd-namespace-2.socket
root@vm1:~# ip netns exec namespace-2 lldpcli -u /var/run/lldpd-namespace-2.socket show interfaces
-------------------------------------------------------------------------------
LLDP interfaces:
-------------------------------------------------------------------------------
Interface: ens7, via: unknown, Time: 18739 days, 20:47:14
Chassis:
ChassisID: mac 52:ab:54:cd:01:02
SysName: vm1
SysDescr: Ubuntu 20.04.1 LTS Linux 5.9.4-050904-generic #202011042130 SMP Wed Nov 4 21:36:10 UTC 2020 x86_64
MgmtIP: 192.168.10.1
MgmtIP: fe80::50ab:54ff:fecd:102
Capability: Bridge, off
Capability: Router, off
Capability: Wlan, off
Capability: Station, on
Port:
PortID: mac 52:ab:54:cd:01:02
PortDescr: ens7
TTL: 120
-------------------------------------------------------------------------------
root@vm1:~# ip netns exec namespace-2 lldpcli -u /var/run/lldpd-namespace-2.socket show neighbors
-------------------------------------------------------------------------------
LLDP neighbors:
-------------------------------------------------------------------------------
Interface: ens7, via: LLDP, RID: 1, Time: 0 day, 00:00:05
Chassis:
ChassisID: mac 52:ab:54:ab:03:01
SysName: vm3
SysDescr: Ubuntu 20.04.1 LTS Linux 5.9.4-050904-generic #202011042130 SMP Wed Nov 4 21:36:10 UTC 2020 x86_64
MgmtIP: 192.168.127.3
MgmtIP: fe80::50ab:54ff:feab:301
Capability: Bridge, off
Capability: Router, off
Capability: Wlan, off
Capability: Station, on
Port:
PortID: mac 52:ab:54:cd:03:02
PortDescr: ens7
TTL: 120
-------------------------------------------------------------------------------
root@vm1:~#

好的 ,但现在我们有 3 个lldpd进程在运行…

root@vm1:~# ps -fC lldpd
UID PID PPID C STIME TTY TIME CMD
_lldpd 2304 1 0 12:53 ? 00:00:00 lldpd: monitor.
_lldpd 2306 2304 0 12:53 ? 00:00:00 lldpd: no neighbor.
_lldpd 2501 1 0 12:53 ? 00:00:00 lldpd: monitor.
_lldpd 2503 2501 0 12:53 ? 00:00:00 lldpd: connected to vm2.
_lldpd 2511 1 0 12:56 ? 00:00:00 lldpd: monitor.
_lldpd 2513 2511 0 12:56 ? 00:00:00 lldpd: connected to vm3.

其实完全没有必要像这样隔离不需要隔离的东西——我们这样做唯一得到的就是在路由器上产生更多的开销。也就是说,我希望这表明,虽然命名空间确实提供了必要的隔离,但对于我们试图实现的目标来说,它们有点矫枉过正。正如 Cumulus 在他们的文章 “Network Namespace as a VRF? Just say No(用网络命名空间作为 VRF?不可以!)”。

使用VRF

因此,现在我们已经研究了解决此问题的其他可能选项,让我们来谈谈在 Linux 中使用实际的 VRF。因此,我们把再做一些上面的事——但现在用的是VRF!

img

VRF 的初始实现最初可能感觉与你习惯的略有不同——但一旦您开始使用它,我认为它很有意义。Linux 中 VRF 实现的好处是,它都是通过现有工具完成的。这太棒了!因此,我们所需要的只是现有iproute2的软件包,在大多数情况下,这些软件包应该已经在您的系统上。一旦安装好了——创建 VRF 所需要做的就只是…

root@vm1:~# ip link add vrf-1 type vrf table 1
root@vm1:~# ip link set dev vrf-1 up
root@vm1:~# ip vrf
Name Table
-----------------------
vrf-1 1
root@vm1:~#

简单!所以现在我们有了一个 VRF,那么用它做什么?好吧,我们可以很容易地向VRF添加接口…

root@vm1:~# ip link set dev ens6 master vrf-1
root@vm1:~# ip addr add 192.168.10.1/24 dev ens6
root@vm1:~#
root@vm1:~# ip route show vrf vrf-1
192.168.127.0/24 dev ens6 proto kernel scope link src 192.168.127.1
root@vm1:~#

上面我们添加ens6到VRFvrf-1,然后向其添加IP地址。然后,我们可以使用命令ip route show vrf查看vrf-1的路由表。这里要指出,虽然ens6接口现在位于 VRF 中,但这并不意味着它像在切换命名空间中一样从全局命名空间中消失了…

root@vm1:~# ip -br link
lo UNKNOWN 00:00:00:00:00:00 <LOOPBACK,UP,LOWER_UP>
ens3 UP 52:ab:54:ab:01:01 <BROADCAST,MULTICAST,UP,LOWER_UP>
ens6 UP 52:ab:54:cd:01:01 <BROADCAST,MULTICAST,UP,LOWER_UP>
ens7 UP 52:ab:54:cd:01:02 <BROADCAST,MULTICAST,UP,LOWER_UP>
vrf-1 UP 4e:19:81:5c:ad:ee <NOARP,MASTER,UP,LOWER_UP>
root@vm1:~#

因此,现在我们在 VRF 中有了一个接口——我们可以添加静态路由…

root@vm1:~# ip route add 0.0.0.0/0 via 192.168.127.254 vrf vrf-1
root@vm1:~# ip route add 172.64.32.0/24 via 192.168.127.101 vrf vrf-1
root@vm1:~# ip route show vrf vrf-1
default via 192.168.127.254 dev ens6
172.64.32.0/24 via 192.168.127.101 dev ens6
192.168.127.0/24 dev ens6 proto kernel scope link src 192.168.127.1
root@vm1:~#

现在让我们直接把vrf-2的所有东西配置好…

ip link add vrf-2 type vrf table 2
ip link set dev vrf-2 up
ip link set dev ens7 master vrf-2
ip addr add 192.168.127.1/24 dev ens7
ip route add 0.0.0.0/0 via 192.168.127.10 vrf vrf-2
ip route add 192.168.128.0/24 via 192.168.127.20 vrf vrf-2

VRF的理论

VRF的结构

所以,现在我们配置完了所有东西,让我们回顾一下这个流程。当我们创建 VRF 时,它看起来很像我们正在创建另一个网络设备。实际上,我们就是在创建设备,这样创建出设备有时被称为“第 3 层主设备”(简称 l3mdev)。该设备就像一个中心点,可以将所有 VRF 接口连接到该点。如果之前使用过 Linux 网桥,会发现向 VRF 添加接口的过程和添加网桥的方式很像。实际上确实是的,在设计 VRF 功能时,他们采用与将设备添加到桥接域相同的方式对其进行建模。如果我们看一下界面,映射甚至看起来是一样的…

root@vm1:~# ip -d link show dev ens6
3: ens6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq master vrf-1 state UP mode DEFAULT group default qlen 1000
link/ether 52:ab:54:cd:01:01 brd ff:ff:ff:ff:ff:ff promiscuity 0 minmtu 68 maxmtu 65535
vrf_slave table 1 addrgenmode eui64 numtxqueues 2 numrxqueues 2 gso_max_size 65536 gso_max_segs 65535
altname enp0s6
root@vm1:~#

看到它是如何显示一个设备的master是vrf-1的没,以及在详情中我们看到的VRF的路由表序号?这不仅让已经熟悉这些概念的人感觉更自然,而且还意味着您可以使用 VRF 设备做一些有趣的事情。例如,如果您想查看 VRF 中的流量怎么办?没问题 ,只需要在 VRF 接口上执行 TCPDUMP…

root@vm1:~# tcpdump -nnel -i vrf-1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on vrf-1, link-type EN10MB (Ethernet), capture size 262144 bytes
21:04:59.140107 42:f6:86:4a:a6:17 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 98: 192.168.10.1 > 192.168.10.254: ICMP echo request, id 2, seq 466, length 64
21:04:59.140575 52:ab:54:cd:02:02 > 52:ab:54:cd:01:01, ethertype IPv4 (0x0800), length 98: 192.168.10.254 > 192.168.10.1: ICMP echo reply, id 2, seq 466, length 64
21:05:00.164085 42:f6:86:4a:a6:17 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 98: 192.168.10.1 > 192.168.10.254: ICMP echo request, id 2, seq 467, length 64
21:05:00.164390 52:ab:54:cd:02:02 > 52:ab:54:cd:01:01, ethertype IPv4 (0x0800), length 98: 192.168.10.254 > 192.168.10.1: ICMP echo reply, id 2, seq 467, length 64
21:05:01.188086 42:f6:86:4a:a6:17 > 00:00:00:00:00:00, ethertype IPv4 (0x0800), length 98: 192.168.10.1 > 192.168.10.254: ICMP echo request, id 2, seq 468, length 64
21:05:01.188320 52:ab:54:cd:02:02 > 52:ab:54:cd:01:01, ethertype IPv4 (0x0800), length 98: 192.168.10.254 > 192.168.10.1: ICMP echo reply, id 2, seq 468, length 64

同样的事情也适用于 ping – 我们只需将源接口设置为 VRF 设备名称,然后…

root@vm1:~# ping -I vrf-1 192.168.10.254
ping: Warning: source address might be selected on device other than: vrf-1
PING 192.168.10.254 (192.168.10.254) from 192.168.10.1 vrf-1: 56(84) bytes of data.
64 bytes from 192.168.10.254: icmp_seq=1 ttl=64 time=0.250 ms
64 bytes from 192.168.10.254: icmp_seq=2 ttl=64 time=0.253 ms
64 bytes from 192.168.10.254: icmp_seq=3 ttl=64 time=0.444 ms

我们还可以将 IP 地址添加到 VRF 接口本身…

root@vm1:~# ip addr add 1.1.1.1/32 dev vrf-1
root@vm1:~# ip route get vrf vrf-1 1.1.1.1
local 1.1.1.1 dev vrf-1 table 1 src 1.1.1.1 uid 0
cache <local>
root@vm1:~#

此 IP 可以像在 VRF 中使用环回接口一样使用。

实际上VRF的架构是如下图所示:

+-----------------------------+
| vrf-blue | ===> route table 10
+-----------------------------+
| | |
+------+ +------+ +-------------+
| eth1 | | eth2 | ... | bond1 |
+------+ +------+ +-------------+
| |
+------+ +------+
| eth8 | | eth9 |
+------+ +------+

VRF的原理

我们已经展示了如何创建和使用 VRF,但我们还没有真正讨论如何实际将流量引入 VRF。我们知道,当我们创建 VRF 时,我们必须为其分配一个路由表编号。在处理路由表时,我们一般需要制作一些规则集来处理它们,所以让我们来看看现有的 IP 规则…

root@vm1:~# ip rule show
0: from all lookup local
1000: from all lookup [l3mdev-table]
32766: from all lookup main
32767: from all lookup default
root@vm1:~#

注意规则 1000。现在看一个在尚未配置任何 VRF之前的rule…

root@vm2:~# ip rule show
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
root@vm2:~#

请注意,规则 1000 未出现在未定义 VRF 的结果中。规则 1000 是在配置 VRF 时创建的,它允许 VRF正常的工作。但是,让我们在这里深入研究一下。在vm1上设置的 IP 规则实际上有什么作用?更重要的是,规则 0 在我们的 VRF 查找规则之前列出,它有什么作用?

默认情况下,规则 0 存在于所有位置,并提供本地查找。那么这意味着什么呢?

root@vm1:~# ip route show table local
broadcast 127.0.0.0 dev lo proto kernel scope link src 127.0.0.1
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1
broadcast 127.255.255.255 dev lo proto kernel scope link src 127.0.0.1
broadcast 192.168.127.0 dev ens3 proto kernel scope link src 192.168.127.1
local 192.168.127.1 dev ens3 proto kernel scope host src 192.168.127.1
broadcast 192.168.127.255 dev ens3 proto kernel scope link src 192.168.127.1
root@vm1:~#

此表包括所有的本地查找。也就是说,直接连接或本地接口的路由。你会注意到,这里并不包含我们在VRF中配置的任何地址前缀。这是有道理的 – VRF 是第 3 层结构,因此我们不希望 VRF 在不属于它们的路由表中出现路由,即使是本地路由。不过,这里的问题是系统是如何处理这些规则的。我们一共有 4 条规则…

  • 规则 0 – 本地查找
  • 规则 1000 – L3MDEV(我们的 VRF 查找规则)
  • 规则 32766 – 主表查找(默认表)
  • 规则 32767 – “默认”的查找表

规则按从低到高的顺序进行处理。因此,每当我们需要进行路由查找时,无论查找发生在哪里,此处理都会发生,直到我们找到匹配项。这就是为什么在主表中执行查找之前,内核必须创建规则 1000 来执行 VRF 查找的原因。你们中的一些人可能已经看到了这个问题——但现在让我们在讨论规则 1000 的工作原理时提出这个问题。

那么,如果没有规则 1000,会发生什么?现在,如果我们在VRF接口中执行ping操作,则会发生…

root@vm1:~# ping -I vrf-1 192.168.10.254
ping: Warning: source address might be selected on device other than: vrf-1
PING 192.168.10.254 (192.168.10.254) from 192.168.10.1 vrf-1: 56(84) bytes of data.
64 bytes from 192.168.10.254: icmp_seq=1 ttl=64 time=0.437 ms
64 bytes from 192.168.10.254: icmp_seq=2 ttl=64 time=0.260 ms
64 bytes from 192.168.10.254: icmp_seq=3 ttl=64 time=0.186 ms

现在让我们删除规则 1000 再试试…

root@vm1:~# ip rule del pref 1000
root@vm1:~# ping -I vrf-1 192.168.10.254 -c 2
ping: Warning: source address might be selected on device other than: vrf-1
PING 192.168.10.254 (192.168.10.254) from 192.168.127.1 vrf-1: 56(84) bytes of data.

--- 192.168.10.254 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1017ms

root@vm1:~#

没有成功。因此,让我们在 VRF 中进行路由查找,看看发生了什么…

root@vm1:~# ip route get vrf vrf-1 192.168.10.254
192.168.10.254 via 192.168.127.100 dev ens3 src 192.168.127.1 uid 0
cache
root@vm1:~# ip route
default via 192.168.127.100 dev ens3 proto static
10.20.30.0/24 via 192.168.127.100 dev ens3 proto static
192.168.127.0/24 dev ens3 proto kernel scope link src 192.168.127.1
root@vm1:~#

因此,正如预期的那样 - 路由查找落入主表,并返回不在我们的 VRF 中的路由。规则 1000,即 l3mdev 查找规则,通过告诉内核始终为我们执行 VRF 路由表查找,为我们解决了这个问题。但是,规则 1000 严格来说是一种便利,涵盖了我们的所有 VRF 表查找。如果我们愿意,我们也可以手动添加给定 VRF 的规则…

root@vm1:~# ip rule add oif vrf-1 table 1
root@vm1:~# ip rule add iif vrf-1 table 1
root@vm1:~# ip route get vrf vrf-1 192.168.10.254
192.168.10.254 dev ens6 table 1 src 192.168.10.1 uid 0
cache
root@vm1:~# ping -I vrf-1 192.168.10.254 -c 2
ping: Warning: source address might be selected on device other than: vrf-1
PING 192.168.10.254 (192.168.10.254) from 192.168.10.1 vrf-1: 56(84) bytes of data.
64 bytes from 192.168.10.254: icmp_seq=1 ttl=64 time=0.447 ms
64 bytes from 192.168.10.254: icmp_seq=2 ttl=64 time=0.275 ms

--- 192.168.10.254 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1005ms
rtt min/avg/max/mdev = 0.275/0.361/0.447/0.086 ms
root@vm1:~#

上面我们添加了两条规则,分别是“如果输出接口是vrf1,则在路由表 1 中查找路由,以及如果输入接口是vrf1,则在路由表 1 中查找路由”。因此,虽然您当然可以为您创建的每个 VRF 添加这些规则,但让默认的 l3mdev 规则处理这个问题会更容易。当你用rule将路由表查询指向l3mdev-table时,它就已经涵盖了 l3mdev(VRF 接口)接口的所有所需了。这是在内核版本v4.8之后新加的功能,这样只需要一条命令就可以生效多个prf设备,而不是要对每一个设备都像刚刚那样,添加一个ip rule。我们的内核版本是v.4.8之后的,因此,我们可以使用默认的方式,将刚刚新增加的两条规则删除…

root@vm1:~# ip rule show
0: from all lookup local
32764: from all iif vrf-1 lookup 1
32765: from all oif vrf-1 lookup 1
32766: from all lookup main
32767: from all lookup default
root@vm1:~# ip rule del pref 32764
root@vm1:~# ip rule del pref 32765
root@vm1:~# ip rule add l3mdev pref 1000
root@vm1:~# ip rule show
0: from all lookup local
1000: from all lookup [l3mdev-table]
32766: from all lookup main
32767: from all lookup default
root@vm1:~#

现在,一切似乎都在按预期进行——但让我们回到我之前暗示的可能的问题。正如我所提到的,规则查找是从低优先级到高优先级进行的。所以,第一个会被查询的规则是0。请记住,规则 0 是所有本地或直接连接接口的路由表。那么,当我们的 VRF 中的一条本地路由与系统的本地路由表(也就是0)相冲突时,会发生什么?..

root@vm1:~# ping -I vrf-1 192.168.10.254 -c 2
ping: Warning: source address might be selected on device other than: vrf-1
PING 192.168.10.254 (192.168.10.254) from 192.168.10.1 vrf-1: 56(84) bytes of data.
64 bytes from 192.168.10.254: icmp_seq=1 ttl=64 time=0.368 ms
64 bytes from 192.168.10.254: icmp_seq=2 ttl=64 time=0.322 ms

--- 192.168.10.254 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1031ms
rtt min/avg/max/mdev = 0.322/0.345/0.368/0.023 ms
root@vm1:~# ip addr add 192.168.10.254/32 dev lo
root@vm1:~# ping -I vrf-1 192.168.10.254 -c 2
ping: Warning: source address might be selected on device other than: vrf-1
PING 192.168.10.254 (192.168.10.254) from 192.168.10.254 vrf-1: 56(84) bytes of data.

--- 192.168.10.254 ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1030ms
root@vm1:~#

无法工作。如果我们检查路由…

root@vm1:~# ip route get vrf vrf-1 192.168.10.254
local 192.168.10.254 dev lo table local src 192.168.10.254 uid 0
cache <local>
root@vm1:~#

正如预期的那样,路由查找在本地路由表中的查找中捕获,而不是进入l3mdev 规则。要解决此问题 ,通常建议像这样重新排序规则…

root@vm1:~# ip -4 rule add pref 32765 table local
root@vm1:~# ip -4 rule del pref 0
root@vm1:~# ip rule show
1000: from all lookup [l3mdev-table]
32765: from all lookup local
32766: from all lookup main
32767: from all lookup default
root@vm1:~#
root@vm1:~# ping -I vrf-1 192.168.10.254 -c 2
ping: Warning: source address might be selected on device other than: vrf-1
PING 192.168.10.254 (192.168.10.254) from 192.168.10.1 vrf-1: 56(84) bytes of data.
64 bytes from 192.168.10.254: icmp_seq=1 ttl=64 time=0.564 ms
64 bytes from 192.168.10.254: icmp_seq=2 ttl=64 time=0.293 ms

--- 192.168.10.254 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1030ms
rtt min/avg/max/mdev = 0.293/0.428/0.564/0.135 ms
root@vm1:~#

这会将本地查找移至规则 1000 之后,以便始终可以首先在VRF的路由表中查找了。

VRF的使用

现在我们已经了解了它们的工作原理,让我们来谈谈在使用 VRF 时可以使用的几命令…

iproute2从v4.7版本开始,iproute2支持vrf关键字。为了向后兼容,本节在适当的地方列出了两种命令形式 – 一种是带有vrf关键字的,另一种是不带vrf关键字的旧形式。

1. 创建 VRF

要实例化 VRF 设备并将其与路由表关联,请执行以下操作:

$ ip link add dev NAME type vrf table ID

从 v4.8 开始,内核支持 l3mdev FIB 规则,其中单个ip rule规则1000: from all lookup [l3mdev-table]就可以涵盖所有 VRF。不必再为每一个VRF设备创建rule

2. 列出 VRF

要列出已创建的 VRF,请执行以下操作:

$ ip [-d] link show type vrf
NOTE: 需要-d参数展示关联的table表ID

例如:

$ ip -d link show type vrf
11: mgmt: <NOARP,MASTER,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 72:b3:ba:91:e2:24 brd ff:ff:ff:ff:ff:ff promiscuity 0
vrf table 1 addrgenmode eui64
12: red: <NOARP,MASTER,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether b6:6f:6e:f6:da:73 brd ff:ff:ff:ff:ff:ff promiscuity 0
vrf table 10 addrgenmode eui64
13: blue: <NOARP,MASTER,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 36:62:e8:7d:bb:8c brd ff:ff:ff:ff:ff:ff promiscuity 0
vrf table 66 addrgenmode eui64
14: green: <NOARP,MASTER,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether e6:28:b8:63:70:bb brd ff:ff:ff:ff:ff:ff promiscuity 0
vrf table 81 addrgenmode eui64

或者简要输出:

$ ip -br link show type vrf
mgmt UP 72:b3:ba:91:e2:24 <NOARP,MASTER,UP,LOWER_UP>
red UP b6:6f:6e:f6:da:73 <NOARP,MASTER,UP,LOWER_UP>
blue UP 36:62:e8:7d:bb:8c <NOARP,MASTER,UP,LOWER_UP>
green UP e6:28:b8:63:70:bb <NOARP,MASTER,UP,LOWER_UP>

3. 将网络接口分配给 VRF

通过将网络设备从属到 VRF 来将网络接口分配给 VRF 设备:

$ ip link set dev NAME master NAME

在某个设备被纳入到某个 VRF 时,直连路由和本地路由都会自动移动到与该 VRF 设备相关联的路由表中。

例如:

$ ip link set dev eth0 master mgmt

4. 显示分配给 VRF 的设备

要显示已分配给特定 VRF 的设备,请添加master命令选项或vrf关键字:

$ ip link show vrf NAME
$ ip link show master NAME

例如:

$ ip link show vrf red
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master red state UP mode DEFAULT group default qlen 1000
link/ether 02:00:00:00:02:02 brd ff:ff:ff:ff:ff:ff
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master red state UP mode DEFAULT group default qlen 1000
link/ether 02:00:00:00:02:03 brd ff:ff:ff:ff:ff:ff
7: eth5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop master red state DOWN mode DEFAULT group default qlen 1000
link/ether 02:00:00:00:02:06 brd ff:ff:ff:ff:ff:ff

或者使用简短的输出:

$ ip -br link show vrf red
eth1 UP 02:00:00:00:02:02 <BROADCAST,MULTICAST,UP,LOWER_UP>
eth2 UP 02:00:00:00:02:03 <BROADCAST,MULTICAST,UP,LOWER_UP>
eth5 DOWN 02:00:00:00:02:06 <BROADCAST,MULTICAST>

5. 显示 VRF 的相邻条目

列出与从属于 VRF 设备的设备关联的邻居条目——将 master 选项添加到 ip 命令中:

$ ip [-6] neigh show vrf NAME
$ ip [-6] neigh show master NAME

例如:

$  ip neigh show vrf red
10.2.1.254 dev eth1 lladdr a6:d9:c7:4f:06:23 REACHABLE
10.2.2.254 dev eth2 lladdr 5e:54:01:6a:ee:80 REACHABLE

$ ip -6 neigh show vrf red
2002:1::64 dev eth1 lladdr a6:d9:c7:4f:06:23 REACHABLE

6. 显示 VRF 的地址

要显示与 VRF 关联的接口的地址,添加master选项到ip命令中:

$ ip addr show vrf NAME
$ ip addr show master NAME

例如:

$ ip addr show vrf red
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master red state UP group default qlen 1000
link/ether 02:00:00:00:02:02 brd ff:ff:ff:ff:ff:ff
inet 10.2.1.2/24 brd 10.2.1.255 scope global eth1
valid_lft forever preferred_lft forever
inet6 2002:1::2/120 scope global
valid_lft forever preferred_lft forever
inet6 fe80::ff:fe00:202/64 scope link
valid_lft forever preferred_lft forever
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast master red state UP group default qlen 1000
link/ether 02:00:00:00:02:03 brd ff:ff:ff:ff:ff:ff
inet 10.2.2.2/24 brd 10.2.2.255 scope global eth2
valid_lft forever preferred_lft forever
inet6 2002:2::2/120 scope global
valid_lft forever preferred_lft forever
inet6 fe80::ff:fe00:203/64 scope link
valid_lft forever preferred_lft forever
7: eth5: <BROADCAST,MULTICAST> mtu 1500 qdisc noop master red state DOWN group default qlen 1000
link/ether 02:00:00:00:02:06 brd ff:ff:ff:ff:ff:ff

或以简短的格式:

$ ip -br addr show vrf red
eth1 UP 10.2.1.2/24 2002:1::2/120 fe80::ff:fe00:202/64
eth2 UP 10.2.2.2/24 2002:2::2/120 fe80::ff:fe00:203/64
eth5 DOWN

7. 显示 VRF 的路由

要显示 VRF 的路由,请使用 ip 命令显示与VRF设备关联的路由表:

$ ip [-6] route show vrf NAME
$ ip [-6] route show table ID

例如:

$ ip route show vrf red
unreachable default metric 4278198272
broadcast 10.2.1.0 dev eth1 proto kernel scope link src 10.2.1.2
10.2.1.0/24 dev eth1 proto kernel scope link src 10.2.1.2
local 10.2.1.2 dev eth1 proto kernel scope host src 10.2.1.2
broadcast 10.2.1.255 dev eth1 proto kernel scope link src 10.2.1.2
broadcast 10.2.2.0 dev eth2 proto kernel scope link src 10.2.2.2
10.2.2.0/24 dev eth2 proto kernel scope link src 10.2.2.2
local 10.2.2.2 dev eth2 proto kernel scope host src 10.2.2.2
broadcast 10.2.2.255 dev eth2 proto kernel scope link src 10.2.2.2

$ ip -6 route show vrf red
local 2002:1:: dev lo proto none metric 0 pref medium
local 2002:1::2 dev lo proto none metric 0 pref medium
2002:1::/120 dev eth1 proto kernel metric 256 pref medium
local 2002:2:: dev lo proto none metric 0 pref medium
local 2002:2::2 dev lo proto none metric 0 pref medium
2002:2::/120 dev eth2 proto kernel metric 256 pref medium
local fe80:: dev lo proto none metric 0 pref medium
local fe80:: dev lo proto none metric 0 pref medium
local fe80::ff:fe00:202 dev lo proto none metric 0 pref medium
local fe80::ff:fe00:203 dev lo proto none metric 0 pref medium
fe80::/64 dev eth1 proto kernel metric 256 pref medium
fe80::/64 dev eth2 proto kernel metric 256 pref medium
ff00::/8 dev red metric 256 pref medium
ff00::/8 dev eth1 metric 256 pref medium
ff00::/8 dev eth2 metric 256 pref medium
unreachable default dev lo metric 4278198272 error -101 pref medium

8. VRF 的路由查找

可以测试VRF的路由查找:

$ ip [-6] route get vrf NAME ADDRESS
$ ip [-6] route get oif NAME ADDRESS

例如:

$ ip route get 10.2.1.40 vrf red
10.2.1.40 dev eth1 table red src 10.2.1.2
cache

$ ip -6 route get 2002:1::32 vrf red
2002:1::32 from :: dev eth1 table red proto kernel src 2002:1::2 metric 256 pref medium

9. 从 VRF 中删除网络接口

从VRF中删除设备可以通过取消设备与VRF的从属关系:

$ ip link set dev NAME nomaster

直连路由将移回默认路由表,本地条目将本地路由表。

例如:

$ ip link set dev eth0 nomaster

10. 在VRF上运行一个命令

如何将一个VRF设备使用起来呢?使用ip vrf命令

首先进程如果要使用VRF设备,可以使用多个 API 指定一个 VRF,可以通过将套接字绑定到 VRF 设备使用 SO_BINDTODEVICE,使用IP_UNICAST_IFIPV6_UNICAST_IF 设置 VRF 关联,或者使用 IP_PKTINFOIPV6_PKTINFO 为特定消息指定 VRF。

默认情况下,进程未绑定到任何 VRF。可以通过显式地使用上述 API 之一使程序使用一个 VRF,或者在创建套接字时隐式地使用助手为所有 IPv4 和 IPv6 套接字(AF_INET 和 AF_INET6)设置 SO_BINDTODEVICE 来设置关联。这个 ip vrf命令是一个助手,用于针对特定的 VRF 运行命令,并且 VRF 关联被继承从父进程到子进程。

可以使用下面的命令直接运行一个这样的shell

# ip vrf exec NAME bash

11. 持久化

本示例中使用的命令持久化为配置文件:

cat >> /etc/iproute2/rt_tables.d/vrf.conf <<EOF
1 mgmt
10 red
66 blue
81 green
EOF

function vrf_create
{
VRF=$1
TBID=$2

# create VRF device
ip link add ${VRF} type vrf table ${TBID}

if [ "${VRF}" != "mgmt" ]; then
ip route add table ${TBID} unreachable default metric 4278198272
fi
ip link set dev ${VRF} up
}

vrf_create mgmt 1
ip link set dev eth0 master mgmt

vrf_create red 10
ip link set dev eth1 master red
ip link set dev eth2 master red
ip link set dev eth5 master red

vrf_create blue 66
ip link set dev eth3 master blue

vrf_create green 81
ip link set dev eth4 master green


Interface addresses from /etc/network/interfaces:
auto eth0
iface eth0 inet static
address 10.0.0.2
netmask 255.255.255.0
gateway 10.0.0.254

iface eth0 inet6 static
address 2000:1::2
netmask 120

auto eth1
iface eth1 inet static
address 10.2.1.2
netmask 255.255.255.0

iface eth1 inet6 static
address 2002:1::2
netmask 120

auto eth2
iface eth2 inet static
address 10.2.2.2
netmask 255.255.255.0

iface eth2 inet6 static
address 2002:2::2
netmask 120

auto eth3
iface eth3 inet static
address 10.2.3.2
netmask 255.255.255.0

iface eth3 inet6 static
address 2002:3::2
netmask 120

auto eth4
iface eth4 inet static
address 10.2.4.2
netmask 255.255.255.0

iface eth4 inet6 static
address 2002:4::2
netmask 120

12. 更多资料

  • cumulus linux vrf文档——上文提到cumulus就是vrf的贡献公司,所以他们的产品cumulus linux的文档应该记录了最详细的使用方式。在这之中可以查阅如何用ifupdown的配置文件实现vrf和同设备的跨vrf路由

VRF在Linux中的应用

1. 使用不同的网关

如果我本地有多个不同的网关,其中一个网关可能有VPN代理,那么我就可以创建一个VRF设备出来,通过写路由的方式,让所有流量default到那个网关上。随后结合ip vrf命令,我就可以创建出一个以VPN为网关的命令行环境,同时不影响正常环境的路由。

2. 只是想虚拟三层

正如前文举例,有的时候network namespace对于我们来说太重,甚至有的时候我们想要他们共用二层链路信息,那么VRF就很有意义了

3. 多租户环境

在云计算或托管服务的环境中,可能有多个客户(或称为"租户")共享同一个物理基础设施。为了保证每个租户的网络隔离,可以为每个租户创建一个 VRF 实例,这样每个租户就有了自己的路由表和网络接口。

4. 想不出来了。。。

以后遇到使用场景了,再添加吧