2017年发生了两件大事:

Linux终于获得了能用的原生的MPLS(L3VPN)和VRF支持。不过三年以后,MPLS配置的完整文档尚付阙如。近日我经过研究和阅读各种零散的资料,成功在测试环境中配置了一个标准的MPLS核心网络架构,因此写一篇文章来分享配置过程以及路上遇到的各种坑。

MPLS?

只玩IP路由,不怎么接触大型运营商网络的同学们可能会对MPLS这个技术有些陌生。如果要打一个简单的比方,MPLS(L3VPN)就像是三层意义上的VLAN Trunking:路由器上把三层接口加入VRF(对应交换机上的VLAN),每个路由器上可以有多个虚拟的路由表(对应交换机上的ARP表),核心路由器之间传送的数据报文前面会被加上MPLS标签(对应交换机上的VLAN标签)保证多个路由表内的数据互不干扰。MPLS的好处有很多:

  • 用同一组路由器为不同的客户提供不同种类的服务
  • 允许不同的客户网使用相同的IP地址段
  • 核心节点不需要查找路由表,只需要做简单的MPLS标签替换工作,增加了包转发性能
  • 核心节点不需要保存客户的全量路由表,只需要负责转发,节约内存
  • 可以实现在客户无感知的情况下把多地的同一客户设备连接起来,不同客户互相隔离
  • 简化复杂的三层配置
  • 可以设置特定流量走特定路径,方便负载均衡和冗余

这么好的协议,当然会有一些前提要求。MPLS是一个2.5层协议,也就是说它需要运行在二层之上。所以如果要启用MPLS,首先整个核心网路由器之间需要用支持二层的方式(例如Ethernet)连接,或者使用对MPLS做了特殊支持的非二层隧道(RFC2547bis例如GRE或L2TP)。MPLS的数据报文有8字节的头部,因此你可能需要适当增大核心网的MTU。

MPLS核心网的路由器一般分为两类:P(Provider),即不连接任何客户设备的路由器,以及PE(Provider Edge),即连接客户设备的路由器。直接连接到PE的客户设备称为CE(Customer Edge)。其它理论知识在此就不细讲了,简单来说,MPLS的配置需要以下几步:

  1. 给每台P和PE设置loopback端口的固定IP
  2. 通过IGP让所有P和PE之间loopback IP都可以互相ping通
  3. 在每台P和PE上启动MPLS处理功能和LDP服务
  4. 在PE上配置iBGP,启动MP-BGP功能
  5. 在PE上配置VRF,并且将连接客户的端口加入相应的VRF
  6. 把客户的路由重分发到核心网的VRF路由表

接下来,我用一个简化版的实验室环境来演示一下Linux上MPLS的实际配置过程。

MPLS!

要解决的问题

假设我们是一个跨城市的小ISP,提供一种把客户在两个城市之间的站点连接起来的业务。业务的实现方式很简单,客户在两个城市分别拉一根网线到我们在同一城市的路由器,配置好IP,然后客户在两个城市的站点就魔法般互相接通了。有一天突然来了两个客户——暂且称之为customer1和customer2好了——都要购买我们的服务,巧的是,他们内网用的IP段是一模一样的。如果我们的核心网络使用传统IP技术互联的话,两个客户的内网可能就互通了,并且还会互相打架。但是如果我们的核心网络使用了MPLS技术,这个配置不费吹灰之力就可以完成。

说明:

  • 所有连线均为以太网连接
  • 核心网之间设备互联使用192.168.1.0/24段(点对点配置)
  • 核心网每个设备有一个10.0.0.x/32的IP用于loopback
  • 图片上的e0/e1/e2分别对应下面配置里的ens3/ens4/ens5

暂时不解决的问题

为了限制篇幅,以下问题本文不深入讨论:

  • MTU不匹配问题(建议PE上MSS全部clamp到1410)
  • CE和PE之间的动态路由协议
  • Linux网络栈的开机自动配置(动态创建的interface,sysctl配置之类的重启以后都会丢,如何使用网络管理软件在开机的时候把它们都正确配置起来就留给读者作为练习了。)
  • VPLS(Linux目前还不支持pseudowire接口)

LittleWolf的《[ Linux ] 使用 Debian Linux 构架 MPLS L3 网络》一文中演示了核心网络使用IS-IS以及CE和PE之间使用BGP实现动态路由的配置,推荐和本文一起阅读。

参考软件

图中所有设备使用以下软件:

  • Debian 10(Linux 4.19.0)
  • iproute2 4.20.0
  • FRRouting 7.2.1(FRRouting提供的deb包)

MPLS配置流程

以下每个代码块开头用#标识该代码块在什么环境下运行。其中:

  • # linux shell表示在Linux原生shell(bash等)下运行
  • # vtysh表示在vtysh下运行
  • # vtysh config表示在vtysh的配置模式(conf t)下运行

设置操作系统

安装FRRouting:

# linux shell
curl -s https://deb.frrouting.org/frr/keys.asc | sudo apt-key add -
echo deb https://deb.frrouting.org/frr buster frr-stable | sudo tee -a /etc/apt/sources.list.d/frr.list
apt update
apt install frr

启动FRRouting:

# linux shell
sed -i "s/=no/=yes/g" /etc/frr/daemons
systemctl enable frr
systemctl restart frr

加载MPLS内核模块:

# linux shell
modprobe mpls_router
modprobe mpls_iptunnel
modprobe mpls_gso
modprobe dummy
cat >/etc/modules-load.d/mpls.conf <<EOF
mpls_router
mpls_iptunnel
mpls_gso
dummy
EOF

内核MPLS基础配置:

# linux shell
cat >/etc/sysctl.d/90-mpls-router.conf <<EOF
net.ipv4.ip_forward=1
net.ipv6.conf.all.forwarding=1
net.ipv4.conf.all.rp_filter=0
net.mpls.platform_labels=1048575
net.ipv4.tcp_l3mdev_accept=1
net.ipv4.udp_l3mdev_accept=1
net.mpls.conf.lo.input=1
EOF
sysctl -p /etc/sysctl.d/90-mpls-router.conf

核心网IP网络配置以及设置Loopback IP

Loopback IP其实不是必需的,如果你想给每个端口分别配置各种协议的源IP其实也行。但是只要你的网络稍微有一点规模,配置一下loopback IP可以少打很多其它配置,而且能避免router id不稳定导致的动态路由协议玄学问题。

P

# linux shell
ip link set ens3 up
ip link set ens4 up
ip link set ens5 up
ip addr add 192.168.1.5 peer 192.168.1.6 dev ens3
ip addr add 192.168.1.2 peer 192.168.1.1 dev ens4
ip addr add 192.168.1.3 peer 192.168.1.4 dev ens5
ip link add dummy0 type dummy
ip link set dummy0 up
ip addr add 10.0.0.2/32 dev dummy0

PE1

# linux shell
ip link set ens3 up
ip link set ens4 up
ip link set ens5 up
ip addr add 192.168.1.1 peer 192.168.1.2 dev ens3
ip link add dummy0 type dummy
ip link set dummy0 up
ip addr add 10.0.0.1/32 dev dummy0

PE2

# linux shell
ip link set ens3 up
ip link set ens4 up
ip link set ens5 up
ip addr add 192.168.1.4 peer 192.168.1.3 dev ens3
ip link add dummy0 type dummy
ip link set dummy0 up
ip addr add 10.0.0.3/32 dev dummy0

RR

# linux shell
ip link set ens3 up
ip addr add 192.168.1.6 peer 192.168.1.5 dev ens3
ip link add dummy0 type dummy
ip link set dummy0 up
ip addr add 10.0.0.4/32 dev dummy0

检查

两两邻接的路由器互相ping对面的接口IP应该能够ping通。

在核心网络中启动IGP

IGP的目的是让loopback IP互相能通。因为我不会别的协议为了简单起见,这里以OSPF为例,全默认配置,所有路由都放到Area 0就可以了。因为上面端口配置部分我们给所有核心网路由器之间的以太网链路配置了点对点连接,OSPF这边需要改一下每个接口的类型。

P

# vtysh config
interface ens3
 ip ospf network point-to-point
interface ens4
 ip ospf network point-to-point
interface ens5
 ip ospf network point-to-point
router ospf
 ospf router-id 10.0.0.2
 redistribute connected
 redistribute static
 network 10.0.0.0/24 area 0
 network 192.168.1.0/24 area 0

PE1

# vtysh config
interface ens3
 ip ospf network point-to-point
router ospf
 ospf router-id 10.0.0.1
 redistribute connected
 redistribute static
 network 10.0.0.0/24 area 0
 network 192.168.1.0/24 area 0

PE2

# vtysh config
interface ens3
 ip ospf network point-to-point
router ospf
 ospf router-id 10.0.0.3
 redistribute connected
 redistribute static
 network 10.0.0.0/24 area 0
 network 192.168.1.0/24 area 0

RR

# vtysh config
interface ens3
 ip ospf network point-to-point
router ospf
 ospf router-id 10.0.0.4
 redistribute connected
 redistribute static
 network 10.0.0.0/24 area 0
 network 192.168.1.0/24 area 0

检查

核心网里的每台路由器使用自己的loopback IP去ping任意其它路由器的loopback IP应该都能通。例如在PE1上ping RR:

# linux shell
ping -I 10.0.0.1 10.0.0.4

启动MPLS处理功能和LDP服务

每个有MPLS流量经过的接口都要启用MPLS功能,并且打开LDP。

P

# linux shell
sysctl net.mpls.conf.ens3.input=1
sysctl net.mpls.conf.ens4.input=1
sysctl net.mpls.conf.ens5.input=1
# vtysh config
mpls ldp
 router-id 10.0.0.2
 address-family ipv4
  discovery transport-address 10.0.0.2
  interface ens3
  interface ens4
  interface ens5

PE1

# linux shell
sysctl net.mpls.conf.ens3.input=1
# vtysh config
mpls ldp
 router-id 10.0.0.1
 address-family ipv4
  discovery transport-address 10.0.0.1
  interface ens3

PE2

# linux shell
sysctl net.mpls.conf.ens3.input=1
# vtysh config
mpls ldp
 router-id 10.0.0.3
 address-family ipv4
  discovery transport-address 10.0.0.3
  interface ens3

RR

其实RR本来不用被掺和进这个MPLS网络的,它只要到所有PE的IP网络都通即可。但是FRRouting当前版本的LDP daemon不支持Downstream Unsolicited模式,只能运行在 Downstream-on-Demand模式下,所以如果RR不配置MPLS的话,从PE到RR方向的包会在P上因为转发表里面没有该条目而被丢掉。迫不得已,我们把RR也拖下水。

# linux shell
sysctl net.mpls.conf.ens3.input=1
# vtysh config
mpls ldp
 router-id 10.0.0.4
 address-family ipv4
  discovery transport-address 10.0.0.4
  interface ens3

如果不想把RR拉下水呢,可以在RR上不运行OSPF,让和RR直连的那台P或者PE把到RR的静态路由分发到网内。或者可以用 label local allocate for 强制LDP分发到RR的prefix的label。

检查

两两邻接的路由器之间LDP会话应该已经成功建立。例如在P上:

# vtysh

show mpls ldp neighbor

应该会看到三个会话:

AF   ID              State       Remote Address    Uptime
ipv4 10.0.0.1        OPERATIONAL 10.0.0.1        09:40:33
ipv4 10.0.0.3        OPERATIONAL 10.0.0.3        09:40:01
ipv4 10.0.0.4        OPERATIONAL 10.0.0.4        08:18:28

并且所有路由器之间使用loopback IP互相ping应该仍然能通。(这个时候使用loopback IP的包已经会从MPLS网络转发,而不是走原来的IP网络了。)

在PE上配置iBGP

iBGP只需要在PE上配置,P完全不用配置,这是MPLS的好处之一:核心路由器只需要处理转发流量,不需要再费心思存储路由表和处理路由表更新。iBGP呢用full mesh也行,RR也行;既然图片上画了RR,这里就用RR的配置方法:所有PE和RR启动BGP会话。本文只演示IPv4的配置方法,对每个会话禁用IPv4 unicast的SAFI,启动IPv4 VPN(VPNv4)的SAFI。IPv6配置方法类似,启用IPv6 VPN的SAFI即可,不再赘述。

对于生产环境而言,单台RR是一个非常容易出问题的单点故障源。Packet Pusher有很不错的关于RR冗余架构设计的文章可供参考。

RR

# vtysh config
router bgp 65000
 no bgp default ipv4-unicast
 neighbor 10.0.0.1 remote-as 65000
 neighbor 10.0.0.1 update-source dummy0
 neighbor 10.0.0.3 remote-as 65000
 neighbor 10.0.0.3 update-source dummy0
 address-family ipv4 vpn
  neighbor 10.0.0.1 activate
  neighbor 10.0.0.1 route-reflector-client
  neighbor 10.0.0.3 activate
  neighbor 10.0.0.3 route-reflector-client

PE1和PE2

# vtysh config
router bgp 65000
 no bgp default ipv4-unicast
 neighbor 10.0.0.4 remote-as 65000
 neighbor 10.0.0.4 update-source dummy0
 address-family ipv4 vpn
  neighbor 10.0.0.4 activate

检查

BGP会话应当正常建立。

在PE上配置VRF

Linux配置VRF有两种方法,一种是net namespace,一种是VRF interface。前者是Linux专有的抽象方式,在多个net namespace之间实现路由泄露和数据交换很不方便,但是对于容器和半虚拟化等应用场景很实用。由于这里只是把Linux系统作为路由器使用,本文选择了相对更加贴近商业路由器系统设计的VRF interface模式。

配置的思路很简单:每个VRF interface在创建的时候会被关联到一个路由表(由table ID唯一标识),然后就像把二层端口加入bridge一样,你可以把三层端口加入VRF。为了保证新建立的路由表能被特定程序读取,这边默认会往路由表写一条metrics设为最大值的默认路由,这样既能保证路由表存在,又不影响其它路由的正常使用。

为了防止操作失误,我们在每台PE上对同一个客户使用同样的VRF名称和table ID。

PE1

# linux shell

# customer 1
ip link add customer1 type vrf table 100
ip link set customer1 up
ip route add vrf customer1 unreachable default metric 4278198272
ip -6 route add vrf customer1 unreachable default metric 4278198272
ip link set ens4 vrf customer1 
ip link set ens4 up
ip addr add 172.19.1.1/24 dev ens4

# customer 2
ip link add customer2 type vrf table 200
ip link set customer2 up
ip route add vrf customer2 unreachable default metric 4278198272
ip -6 route add vrf customer2 unreachable default metric 4278198272
ip link set ens5 vrf customer2 
ip link set ens5 up
ip addr add 172.19.1.1/24 dev ens5

PE2

# linux shell

# customer 1
ip link add customer1 type vrf table 100
ip link set customer1 up
ip route add vrf customer1 unreachable default metric 4278198272
ip -6 route add vrf customer1 unreachable default metric 4278198272
ip link set ens4 vrf customer1 
ip link set ens4 up
ip addr add 172.19.2.1/24 dev ens4

# customer 2
ip link add customer2 type vrf table 200
ip link set customer2 up
ip route add vrf customer2 unreachable default metric 4278198272
ip -6 route add vrf customer2 unreachable default metric 4278198272
ip link set ens5 vrf customer2 
ip link set ens5 up
ip addr add 172.19.2.1/24 dev ens5

把客户的路由重分发到核心网的VRF路由表

限于篇幅,只演示connected路由的重分发,不在PE和CE之间配置动态路由协议了。这里要做的事情是,在每个VRF上创建一个空的(没有peer的)BGP配置,把VRF内要分发的路由先导入该BGP的RIB,然后让它把路由表打上特定route distinguisher community以后导入主BGP的RIB。

为了防止操作失误,我们在每台PE上对同一个客户使用同样(和table ID对应)的RT和RD。另外需要注意的是,FRRouting的vtysh虽然语法上和Cisco IOS很像,但是在这里是不太一样的。

PE1和PE2

# vtysh config
router bgp 65000 vrf customer1
 address-family ipv4 unicast
  redistribute connected
  redistribute static
  label vpn export auto
  rd vpn export 65000:100
  rt vpn both 65000:100
  export vpn
  import vpn
router bgp 65000 vrf customer2
 address-family ipv4 unicast
  redistribute connected
  redistribute static
  label vpn export auto
  rd vpn export 65000:200
  rt vpn both 65000:200
  export vpn
  import vpn

配置的时候FRRouting会报一个错,不要惊慌,这是个bug,功能还是能用的。

检查

这时候我们应该可以在每个PE上看到每个VRF里面出现通过BGP重分发来的connected路由了。例如在PE1上:

# vtysh
show ip bgp vrf customer1

应该会看到类似这样的输出:

BGP table version is 2, local router ID is 172.19.1.1, vrf id 9
Default local pref 100, local AS 65000
Status codes:  s suppressed, d damped, h history, * valid, > best, = multipath,
               i internal, r RIB-failure, S Stale, R Removed
Nexthop codes: @NNN nexthop's vrf id, < announce-nh-self
Origin codes:  i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*> 172.19.1.0/24    0.0.0.0                  0         32768 ?
*> 172.19.2.0/24    10.0.0.3@0<              0    100      0 ?

Displayed  2 routes and 2 total paths

配置模拟的客户侧设备

为了模拟MPLS对不同客户相同IP段的隔离功能,我们把两个客户同一侧的设备的IP配置配成完全相同。

customer1-site1和customer2-site1

# linux shell
ip link set ens3 up
ip addr add 172.19.1.2/24 dev ens3
ip route add default via 172.19.1.1

customer1-site2和customer2-site2

# linux shell
ip link set ens3 up
ip addr add 172.19.2.2/24 dev ens3
ip route add default via 172.19.2.1

检查

首先我们确认同一个客户的两个站点之间可以互相ping通。由于我们的配置启用了PHP和TTL propagation,同一个客户的两台设备中间经过三台MPLS路由器以后会出现两跳非IP路由,所以如果我们使用traceroute或者mtr来测试的话会看到以下输出:

可惜的是,Linux部分支持RFC4884,完全不支持RFC4950,所以traceroute -emtr --mpls没法显示路上的MPLS详细信息。

如果想要简单地验证一下确实是同一个客户的两个站点被连接起来了的话,可以使用netcat启动一个简单的TCP聊天服务,只需要在一个客户的其中一个站点启动服务器:

# linux shell
nc -l -p 8888

然后在同一个客户的另一个站点连接服务器:

# linux shell
nc 172.19.2.2 8888

连接上以后,在两边输入任意字符并按回车,对端都会显示相同的字符。

MPLS……

Linux的发展史可以概括为一个小故事:某天你想要造个模型飞机玩,就照着真飞机的样子用木棍和纸片随手搭了一个起来放在后院;第二天起床一看,昨天随手搭的那个飞机已经被邻居开上了天,上面坐着十几二十个乘客,还有两三个人正趴在机翼上边维持平衡边修发动机呢。

“反正是免费提供的,摔下来也不关我事,管他呢。”你在心里默默地想,然后该干啥干啥去了。


致谢:

两位CCIE大佬对本文的写作做出了巨大贡献。

参考:

点赞 ({{click_count}}) 收藏 (0)

如何安装双系统

linux系统命令

linux系统教程