Linux 内核 Switchdev 框架学习笔记

Linux 内核 Switchdev 框架学习笔记

Switchdev介绍

switchdev是Linux内核中一种以太网交换设备驱动模型(Ethernet switch device driver model),内核文档的介绍是这样的:

The Ethernet switch device driver model (switchdev) is an in-kernel driver model for switch devices which offload the forwarding (data) plane from the kernel.

Figure 1 is a block diagram showing the components of the switchdev model for an example setup using a data-center-class switch ASIC chip. Other setups with SR-IOV or soft switches, such as OVS, are possible.

                       User-space tools

 user space                   |
+-------------------------------------------------------------------+
 kernel                       | Netlink
                              |
               +--------------+-------------------------------+
               |         Network stack                        |
               |           (Linux)                            |
               |                                              |
               +----------------------------------------------+

                     sw1p2     sw1p4     sw1p6
                sw1p1  +  sw1p3  +  sw1p5  +          eth1
                  +    |    +    |    +    |            +
                  |    |    |    |    |    |            |
               +--+----+----+----+----+----+---+  +-----+-----+
               |         Switch driver         |  |    mgmt   |
               |        (this document)        |  |   driver  |
               |                               |  |           |
               +--------------+----------------+  +-----------+
                              |
 kernel                       | HW bus (eg PCI)
+-------------------------------------------------------------------+
 hardware                     |
               +--------------+----------------+
               |         Switch device (sw1)   |
               |  +----+                       +--------+
               |  |    v offloaded data path   | mgmt port
               |  |    |                       |
               +--|----|----+----+----+----+---+
                  |    |    |    |    |    |
                  +    +    +    +    +    +
                 p1   p2   p3   p4   p5   p6

                       front-panel ports


                              Fig 1.

上面提到了switchdev的作用是从内核中卸载转发(数据)面的工作(offload the forwarding (data) plane from the kernel),图1 是以一个数据中心级别的ASIC交换芯片为例子,其他还支持SR-IOV虚拟化以及软交换OVS等。

图中从上到下分别是用户空间,内核空间和硬件设备,用户空间程序通过netlink消息机制和内核空间中的网络协议栈通信,交换驱动模型(switch dirver)抽象了交换机的行为,支持sw1p1到sw1p6共6个接口,eth1是管理口驱动,内核直接处理管理口驱动而不需要额外工作,内核空间通过硬件总线(比如PCIE)将交换模型的数据卸载到具体硬件上,比如ASIC交换芯片,交换芯片上有6个物理port是p1到p6,管理口有单独的物理口,所以整体上内核把交换转发功能通过交换驱动模型卸载到硬件上。

学习笔记

具体到内核中的代码,其实搜索Linux Kernel代码switchdev只能找到net/switchdev/switchdev.c这个文件,这个文件其实实现了switchdev的抽象层,具体L2,L3业务如何卸载,其实是需要和其他fdb, fib模块配合完成,接下来我们分析一下这是如何实现的。

  • 1. 交换机模型抽象

每一个物理口资源在内核都会通过register_netdev和一个struct net_device关联,这也是内核网络驱动中最重要的设备模型,后面一般将这种端口资源成为netdev。我们平时使用ifconfig(net-tools)或者ip(iproute2)工具看到的物理设备,其实对应的就是这种netdev资源。使用它可以管理进入内核或者从内核发出的网络流量,同时还作为其他更高层次网络模型的锚点(anchor point),比如bridges, bonds, VLANs, tunnels, and L3 routers等。

端口的标识被称为switch ID,在同一个系统内必须唯一,在不同的系统间可以相同,也就是这是个本地概念,不涉及不同系统之间的交互,用户标识具体的一个端口(port)。

  • 2. 端口相关

端口命名一般采用swXpYsZ的形式,其中X是交换芯片名字或者ID, Y是端口名称或者ID,Z是子端口名称或者ID。

如果物理硬件只支持默认的网络命名空间(network namespace),则需要设置标志NETIF_F_NETNS_LOCAL。如果支持多个网络命名空间,则不需要设置这个标志,但是要保证维护网络命名空间的隔离性,即在不同的网络命名空间之间不能互相转发流量,其实就是硬件支持网络功能虚拟化。

代表物理交换端口的netdev资源可以被组织成更高层次的交换模型。默认的模型是独立的路由端口(router port),它一般用来卸载L3转发。两个或者多个端口可以被绑定成为链路聚合口(LAG)。两个或者多个口(或者LAG)可以被桥接起来形成L2网络,这里的接口其实就不是路由端口而是交换端口了。一般用VLAN来划分不同的L2网络。L2-over-L3隧道也可以通过接口构建起来。上述这些模型可以使用标准的Linux工具构建起来,比如桥接驱动(bridge driver),聚合驱动(bonding/team driver),以及基于netlink的工具iproute2。

switchdev驱动通过监测NETDEV_CHANGEUPPER通知来判断一个特定端口的在拓扑中的位置。

  • 3. L2转发卸载

switchdev模型通过镜像bridge中的FDB表项到硬件上来卸载L2转发功能,一个FDB表项包含{port, MAC, VLAN}这种三元组信息。为了实现L2转发卸载功能,switchdev驱动设备需要支持如下一些特性:1)静态FDB表项安装到bridge端口上;2)设备上学习或者老化mac/vlan的时候通知驱动;3)支持端口上stp状态变化配置;4)支持组播广播和未知单播的VLAN 泛洪功能。下面分别看看上面这些功能是如何实现的。

3.1 FDB表项处理

对于支持ndo_fdb_add, ndo_fdb_del和ndo_fdb_dump操作的驱动来说,他们可以支持bridge工具的如下配置命令,这些命令会通过netlink机制调用内核驱动中的上述注册接口。

bridge fdb add dev DEV ADDRESS [vlan VID] [self] static

static关键字是必选的,如果不指定则添加的表项是”local”属性,意味着不能够转发流量。

self关键字是可选的,如果不配置它就是默认值。它的作用是通知内核通过DEV设备注册的ndo_fdb_add接口执行上述操作。如果DEV设备是bridge端口,这会绕过内核的bridge机制,因此会导致软件数据库和硬件数据库不一致。

为了避免这些问题,必须指定master关键字,形如:

bridge fdb add dev DEV ADDRESS [vlan VID] master static

上述命令通知内核查找DEV上的主接口(master interface),并且通过ndo_fdb_add方法执行该动作。这时bridge会产生一个SWITCHDEV_FDB_ADD_TO_DEVICE通知,这样端口驱动就可以处理它进而将表项编程到硬件中。下面是内核代码中bridge通知机制的实现接口br_switchdev_fdb_notify

void
br_switchdev_fdb_notify(struct net_bridge *br,
			const struct net_bridge_fdb_entry *fdb, int type)
{
	struct switchdev_notifier_fdb_info item;

	if (test_bit(BR_FDB_LOCKED, &fdb->flags))
		return;

	/* Entries with these flags were created using ndm_state == NUD_REACHABLE,
	 * ndm_flags == NTF_MASTER( | NTF_STICKY), ext_flags == 0 by something
	 * equivalent to 'bridge fdb add ... master dynamic (sticky)'.
	 * Drivers don't know how to deal with these, so don't notify them to
	 * avoid confusing them.
	 */
	if (test_bit(BR_FDB_ADDED_BY_USER, &fdb->flags) &&
	    !test_bit(BR_FDB_STATIC, &fdb->flags) &&
	    !test_bit(BR_FDB_ADDED_BY_EXT_LEARN, &fdb->flags))
		return;

	br_switchdev_fdb_populate(br, &item, fdb, NULL);

	switch (type) {
	case RTM_DELNEIGH:
		call_switchdev_notifiers(SWITCHDEV_FDB_DEL_TO_DEVICE,
					 item.info.dev, &item.info, NULL);
		break;
	case RTM_NEWNEIGH:
		call_switchdev_notifiers(SWITCHDEV_FDB_ADD_TO_DEVICE,
					 item.info.dev, &item.info, NULL);
		break;
	}
}

通过这种机制,软件和硬件数据库都包含了静态FDB表项。这里可以看到内核有些驱动支持了该事件的处理,也意味着它们卸载了FDB表项到硬件中。那么问题来了,既然驱动可以通过直接注册ndo_fdb_add接口实现FDB添加,那么为什么还要通过switchdev提供的通知(notification)来处理呢?

内核文档中说:对于新的switchdev驱动,如果需要卸载Linux bridge功能,实现ndo_fdb_add和ndo_fdb_del方法(事实上就绕过了内核brdige机制)被强烈不鼓励(strongly discouraged),所有的静态FDB表项都应该通过bridge端口+“master”标志添加。唯一的例外是ndo_fdb_dump,因为如果设备不支持学习或者老化FDB表项后通过中断通知操作系统,硬件中动态学习的表项就不会包含在软件FDB中,ndo_fdb_dump是唯一可以看到他们的方式了。总结一下就是驱动通过switchdev方式处理FDB静态表项添加和删除(这里只用添加add举例,删除del其实是一样的),可以保持软件FDB和硬件FDB的一致,而如果直接实现了ndo_fdb_add和ndo_fdb_del,则bridge中不会添加上述FDB信息,那么软件FDB中就不存在了,所有的软件bridge功能就都无法使用了。

3.2 学习和老化的通知机制

交换设备会学习和老化源MAC+VLAN表项,这通过处理入报文来实现,同时需要通知交换驱动mac/vlan/port这种三元组表项。交换驱动反过来又会通过swtichdev提供的通知调用(notifier call)通知bridge驱动:

err = call_switchdev_notifiers(val, dev, info, extack);

其中val标识动作类型,学习是SWITCHDEV_FDB_ADD,老化是SWITCHDEV_FDB_DEL ,info指向数据结构struct switchdev_notifier_fdb_info。当学习的时候,bridge驱动会安装FDB表项到bridge的FDB记录中,同时标记为NTF_EXT_LEARNED。通过iproute2工具bridge命令可以看到这些表项有标记“offload”。

$ bridge fdb
52:54:00:12:35:01 dev sw1p1 master br0 permanent
00:02:00:00:02:00 dev sw1p1 master br0 offload
00:02:00:00:02:00 dev sw1p1 self
52:54:00:12:35:02 dev sw1p2 master br0 permanent
00:02:00:00:03:00 dev sw1p2 master br0 offload
00:02:00:00:03:00 dev sw1p2 self
33:33:00:00:00:01 dev eth0 self permanent
01:00:5e:00:00:01 dev eth0 self permanent
33:33:ff:00:00:00 dev eth0 self permanent
01:80:c2:00:00:0e dev eth0 self permanent
33:33:00:00:00:01 dev br0 self permanent
01:00:5e:00:00:01 dev br0 self permanent
33:33:ff:12:35:01 dev br0 self permanent

可以通过bridge提供的如下命令关闭学习功能:

bridge link set dev DEV learning off

可以通过brdige提供的如下命令开启学习功能:

bridge link set dev DEV learning on self

3.3 FDB老化

内核bridge会跳过那些标记了NTF_EXT_LEARNED属性的FDB表项,因为它们需要硬件或者驱动来老化它们。如果端口设备支持老化(这里是不是意味着,设备可以只支持硬件学习,但是可以不支持硬件老化?),则当FDB老化时间到,芯片会通过中断方式通知驱动,驱动又通知bridge模块(使用SWITCHDEV_FDB_DEL)。如果设备不支持老化,驱动程序可以通过定时器机制模拟老化功能。内核的rocker driver就实现了软件的老化机制。

为了保持具有NTF_EXT_LEARNED标志的表项,驱动应该刷新表项,通过调用call_switchdev_notifiers(SWITCHDEV_FDB_ADD, …),该通知会重置FDB表项的最后使用时间。但是驱动需要对刷新机制限速,比如不超过1秒1次。可以使用bridge提供的命令bridge -s fdb查看上一次使用时间信息。这里是不是说,如果表项一直被使用,在老化时间之前到了硬件需要通过上述机制完成表项的刷新。如果不刷新的话,bridge的FDB中会显示该表项上次到当前的使用时间超过了老化时间,也就是没有被老化掉。其实对于功能没有影响,但是老化时间会让人困惑。

3.4 端口STP状态变化

对于二层转发来说,STP状态都是一个需要考虑的问题,比如内部需要或者生成树协议需要,bridge驱动也维护了端口的STP状态。当STP状态变化的时候,bridge会使用switchdev提供的接口switchdev_attr_port_set和事件SWITCHDEV_ATTR_PORT_ID_STP_UPDATE来通知对应的驱动。交换驱动可以使用STP状态来更新报文转发过滤列表,也就是硬件的STP状态。

3.5 L2泛洪

交换芯片基本都会支持L2泛洪功能,即对于组播,广播和未知单播报文会泛洪到它所在的二层广播域的所有接口上。这些报文也可能通过泛洪到CPU口或者特定报文上送CPU机制到bridge驱动上,比如ACL机制。这个时候bridge机制就不应该再次泛洪这些报文到设备的接口上,否则线路上就会出现重复的报文。

为了避免这种情况,交换驱动需要标记上送bridge模块的报文,这通过设置skb->offload_fwd_mark标志位来实现。这样bridge驱动就可以通过该标记来防止报文被再次泛洪到设备的同一个接口上。

当然也可以通过禁止硬件泛洪而将报文上送到bridge模块,通过bridge来泛洪报文。这种通过软件实现泛洪肯定不如硬件效率高,但是在某些特殊场景,比如CPU直接发包,或者特殊报文的处理,还可能有一些用处。

  • 4. L3路由卸载

看完上述L2转发的卸载,其实L3路由卸载就比较容易理解了。L3路由卸载需要内核将FIB表项编程到设备上,以便设备上完成FIB查找和转发的功能。设备会通过LPM(longest prefix match)最长前缀匹配规则来实现查找和转发功能。

为了实现对设备进行编程,驱动需要通过register_fib_notifier注册FIB通知处理函数。Linux支持如下事件:

FIB_EVENT_ENTRY_ADDused for both adding a new FIB entry to the device, or modifying an existing entry on the device. 添加或者更新FIB表项
FIB_EVENT_ENTRY_DELused for removing a FIB entry 删除FIB表项
FIB_EVENT_RULE_ADD,used to propagate FIB rule changes 添加FIB RULE
FIB_EVENT_RULE_DELused to propagate FIB rule changes 删除FIB RULE

FIB_EVENT_ENTRY_ADD and FIB_EVENT_ENTRY_DEL 事件会传递如下结构体信息:

struct fib_entry_notifier_info {
        struct fib_notifier_info info; /* must be first */
        u32 dst;
        int dst_len;
        struct fib_info *fi;
        u8 tos;
        u8 type;
        u32 tb_id;
        u32 nlflags;
};

上面dst和dst_len标识IPv4路由前缀,分别标识地址和地址长度(也就是掩码长度),tb_id标识路由表索引,*fi表示路由和路由下一跳的详细信息,对应数据结构struct fib_info。卸载到硬件的路由表项被标识为“offload”,通过ip route命令可以看到:

$ ip route show
default via 192.168.0.2 dev eth0
11.0.0.0/30 dev sw1p1  proto kernel  scope link  src 11.0.0.2 offload
11.0.0.4/30 via 11.0.0.1 dev sw1p1  proto zebra  metric 20 offload
11.0.0.8/30 dev sw1p2  proto kernel  scope link  src 11.0.0.10 offload
11.0.0.12/30 via 11.0.0.9 dev sw1p2  proto zebra  metric 20 offload
12.0.0.2  proto zebra  metric 30 offload
        nexthop via 11.0.0.1  dev sw1p1 weight 1
        nexthop via 11.0.0.9  dev sw1p2 weight 1
12.0.0.3 via 11.0.0.1 dev sw1p1  proto zebra  metric 20 offload
12.0.0.4 via 11.0.0.9 dev sw1p2  proto zebra  metric 20 offload
192.168.0.0/24 dev eth0  proto kernel  scope link  src 192.168.0.15

内核文档中目前还缺少对于IPv6 FIB表项的卸载内容,具体代码中是否支持还待进一步确认。而且对于IPv4 FIB,主线内核中也只有mellanox的spectrum芯片支持该功能,具体可以看这里。(如果看DENT的文档化,Marvell应该也支持了该功能,不过应该没有合入内核主线)

上述FIB表项中还会关联下一跳列表(nexthop list)信息,一般包含二元组(gateway,dev),也就是下一跳网关地址和出接口设备信息。对于FIB转发来说,最终会将gateway地址解析为下一跳mac地址来指导转发,这可以通过arp和nd功能来实现。所以为了解析路由下一跳网关地址,驱动需要触发内核中的邻居解析处理机制。rocker驱动中的rocker_port_ipv4_resolve展示了如何实现这种功能(但是我并没有找到这个接口对应的代码)。

驱动可以通过注册网络事件通知机制NETEVENT_NEIGH_UPDATE来监测内核arp_tbl的更新变化。所以驱动可以通过解析arp_tbl变化事件来解析下一跳信息,进而将FIB编程到设备上。驱动可以通过实现ndo_neigh_destroy 接口来处理arp_tbl表项被删除事件,具体说就是暂时刷新FIB的下一跳信息为无效或者黑洞,然后上层最终会触发FIB删除动作。

  • 5. 一些设计考虑

除了上面这些功能,内核文档中还规定了支持switchdev模型的驱动必须遵守的一些规则,主要都是关于bridge功能的,这里就不再重点介绍了,如果需要实现switchdev驱动,可以再具体研究。

总结和思考

上面的很多资料都来源于内核目录下Documentation/networking/switchdev.rst的内容,lwn.net上有一篇文章The switchdev driver model也介绍了switchdev驱动模型,该文没有介绍具体技术而是介绍了switchdev的一些设计思想和历史发展过程,文章发表于2016年的netdev会议上,具体见这里

DENT是使用switchdev模型实现的开源网络操作系统open-source network operating system(NOS),但是相对于微软主推的SONiC,使用还是少了很多,目前只有Amazon公开支持。switchdev对于内核的依赖虽然利好一些通用工具net-toos, iproute2, ethtool的使用,但是也不便于厂商支持,因为内核态的代码不容易合入upstream也就是内核主线,同时对系统扩展性也不友好。相反SONiC主推的SAI接口方便各厂商在现有SDK的基础上封装统一的接口,也完全没有对于内核的要求和依赖,所以说SONiC和SAI互相成就了对方也不为过。

相信DENT也意识到这一点,所以在即将发布的DENT 4.0版本上,也要支持SAI接口了,具体可能是一种SwitchDev到SAI适配器的方式,原文是这样的:

Looking ahead, the upcoming “DENT 4.0” release, scheduled for this year, promises a range of new features and improvements. These include DENT Documentation, in-kernel PoE support (in upstream Linux kernel), support for “common” SAI platform, support for edge gateway, a prototype of Yocto-based image for tn48m, debuggability improvements, VXLAN support, migration to Yocto-based build system, dual partitioning, components update, PoE++, update to the latest LTS kernel, PoE LLDP integration, adding support for SAI, SwitchDev to SAI adapter, upgrading to Debian 12, and introducing new HW platforms (Based on OCP Edge Gateway). Stay tuned for the exciting updates and advancements in DENT 4.0, as we continue to push the boundaries of networking innovation.

但是在DENT 4.0 release的Backlog上,关于SAI的项目只有一个Introduce SAI level testing,也就是SAI级别的测试,而没有提及SAI接口是如何集成到DENT上的。在DentOS Future roadmap上,关于SAI的信息有两条,分别是SwitchDev to SAI adapter和Add support for SAI,前者处于In Progress阶段而后者处于Todo阶段。但是在具体讨论页面,也没有太多额外的信息。

所以总体来看,switchdev模型可能不太适合开源交换机方案,而一些商业交换机为了摆脱对于内核机制的依赖,也不太可能使用这种方式,所以switchdev从目前来看不太具有商业价值,但是以后是否可以被再次利用,也要看内核和相关技术的发展,至少它给我们提供了一种利用Linux Kernel实现交换机功能的思路。

参考资料

  1. iproute2, bridge工具实现在这里。
  2. linux source code in bootlin,Linux内核在线代码,可以快速查找符号定义和被应用的地方。

One thought on “Linux 内核 Switchdev 框架学习笔记

Comments are closed.

Comments are closed.