以太网交换机设备驱动模型 (switchdev)

版权 © 2014 Jiri Pirko <jiri@resnulli.us>

版权 © 2014-2015 Scott Feldman <sfeldma@gmail.com>

以太网交换机设备驱动模型(switchdev)是一种内核驱动模型,用于卸载交换设备的转发(数据)平面,使其脱离内核。

图 1 是一个框图,显示了使用数据中心级交换机 ASIC 芯片的示例设置的 switchdev 模型的组件。其他具有 SR-IOV 或软交换机(例如 OVS)的设置也是可能的。

                       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.

包含文件

#include <linux/netdevice.h>
#include <net/switchdev.h>

配置

在驱动程序的 Kconfig 中使用 “depends NET_SWITCHDEV”,以确保为驱动程序构建 switchdev 模型支持。

交换机端口

在 switchdev 驱动程序初始化时,该驱动程序将为每个枚举的物理交换机端口分配和注册一个 struct net_device(使用 register_netdev()),称为端口 netdev。端口 netdev 是物理端口的软件表示,并为控制器(内核)和网络之间的控制流量提供管道,以及桥接、绑定、VLAN、隧道和 L3 路由器等更高级别构造的锚点。使用标准 netdev 工具(iproute2、ethtool 等),端口 netdev 还可以向用户提供对交换机端口的物理属性(如 PHY 链路状态和 I/O 统计信息)的访问。

除了端口 netdev 外,(目前)没有更高级别的内核对象用于交换机。所有的 switchdev 驱动程序操作都是 netdev 操作或 switchdev 操作。

交换机管理端口不在 switchdev 驱动模型范围之内。通常,管理端口不参与卸载的数据平面,并且在管理端口设备上加载了不同的驱动程序,例如 NIC 驱动程序。

交换机 ID

switchdev 驱动程序必须为每个端口 netdev 实现 net_device 操作 ndo_get_port_parent_id,为同一个交换机的每个端口返回相同的物理 ID。该 ID 在同一系统上的交换机之间必须是唯一的。该 ID 在不同系统上的交换机之间不需要是唯一的。

交换机 ID 用于定位交换机上的端口,以及了解聚合端口是否属于同一交换机。

端口 Netdev 命名

udev 规则应使用端口 netdev 命名,使用端口的某些唯一属性作为键,例如端口 MAC 地址或端口 PHYS 名称。不鼓励在驱动程序中硬编码内核 netdev 名称;让内核选择默认的 netdev 名称,并让 udev 根据端口属性设置最终名称。

将端口 PHYS 名称 (ndo_get_phys_port_name) 用作键对于动态命名的端口特别有用,其中设备基于外部配置命名其端口。例如,如果一个物理 40G 端口在逻辑上分为 4 个 10G 端口,从而产生 4 个端口 netdev,则设备可以使用端口 PHYS 名称为每个端口提供唯一的名称。udev 规则将是

SUBSYSTEM=="net", ACTION=="add", ATTR{phys_switch_id}=="<phys_switch_id>", \
        ATTR{phys_port_name}!="", NAME="swX$attr{phys_port_name}"

建议的命名约定为 “swXpYsZ”,其中 X 是交换机名称或 ID,Y 是端口名称或 ID,Z 是子端口名称或 ID。例如,sw1p1s0 将是交换机 1 上端口 1 上的子端口 0。

端口功能

dev->netns_local

如果 switchdev 驱动程序(和设备)仅支持卸载默认网络命名空间 (netns),则驱动程序应设置此私有标志,以防止端口 netdev 从默认 netns 中移出。netns 感知的驱动程序/设备不会设置此标志,并负责对硬件进行分区以保留 netns 包含。这意味着硬件无法将一个命名空间中端口的流量转发到另一个命名空间中的另一个端口。

端口拓扑

表示物理交换机端口的端口 netdev 可以组织成更高级别的交换结构。默认构造是独立路由器端口,用于卸载 L3 转发。两个或多个端口可以绑定在一起形成 LAG。两个或多个端口(或 LAG)可以桥接以桥接 L2 网络。VLAN 可以应用来细分 L2 网络。可以在端口上构建 L2 over L3 隧道。这些构造是使用标准 Linux 工具(例如桥接驱动程序、绑定/团队驱动程序和基于 netlink 的工具(例如 iproute2))构建的。

switchdev 驱动程序可以通过监视 NETDEV_CHANGEUPPER 通知来了解特定端口在拓扑中的位置。例如,移入绑定的端口将看到其上级主设备发生更改。如果该绑定移入桥接,则该绑定的上级主设备将发生更改。依此类推。该驱动程序将通过注册网络设备事件并对 NETDEV_CHANGEUPPER 执行操作来跟踪此类移动,以了解端口在整个拓扑中的位置。

L2 转发卸载

其思想是将 L2 数据转发(交换)路径从内核卸载到 switchdev 设备,方法是将桥接 FDB 条目镜像到设备。FDB 条目是 {端口、MAC、VLAN} 元组转发目标。

为了卸载 L2 桥接,switchdev 驱动程序/设备应支持

  • 在桥接端口上安装的静态 FDB 条目

  • 来自设备的学习/遗忘的源 MAC/VLAN 的通知

  • 端口上的 STP 状态更改

  • 多播/广播和未知单播数据包的 VLAN 泛洪

静态 FDB 条目

实现了 ndo_fdb_addndo_fdb_delndo_fdb_dump 操作的驱动程序能够支持以下命令,该命令添加静态桥接 FDB 条目

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

(“static” 关键字是必需的:如果未指定,则该条目默认为 “local”,这意味着不应转发该条目)

“self” 关键字(可选,因为它是隐式的)的作用是指示内核通过 DEV 设备本身的 ndo_fdb_add 实现来执行操作。如果 DEV 是桥接端口,则将绕过桥接,因此使软件数据库与硬件数据库不同步。

为避免这种情况,可以使用 “master” 关键字

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

以上命令指示内核搜索 DEV 的主接口,并通过该接口的 ndo_fdb_add 方法执行操作。此时,桥接会生成 SWITCHDEV_FDB_ADD_TO_DEVICE 通知,端口驱动程序可以处理该通知并使用它来编程其硬件表。这样,软件和硬件数据库都将包含此静态 FDB 条目。

注意:对于卸载 Linux 桥接的新 switchdev 驱动程序,强烈不建议实现 ndo_fdb_addndo_fdb_del 桥接绕过方法:所有静态 FDB 条目都应使用 “master” 标志在桥接端口上添加。ndo_fdb_dump 是一个例外,可以实现来可视化硬件表,如果设备没有中断来通知操作系统新学习/遗忘的动态 FDB 地址。在这种情况下,硬件 FDB 可能最终具有软件 FDB 没有的条目,而实现 ndo_fdb_dump 是查看它们的唯一方法。

注意:默认情况下,桥接不筛选 VLAN,仅桥接未标记的流量。要启用 VLAN 支持,请打开 VLAN 筛选

echo 1 >/sys/class/net/<bridge>/bridge/vlan_filtering

已学习/遗忘的源 MAC/VLAN 的通知

交换机设备将在入口数据包上学习/遗忘源 MAC 地址/VLAN,并通知交换机驱动程序 mac/vlan/端口元组。然后,交换机驱动程序将使用 switchdev 通知程序调用通知桥接驱动程序

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

其中 val 是学习时的 SWITCHDEV_FDB_ADD 和遗忘时的 SWITCHDEV_FDB_DEL,info 指向 struct switchdev_notifier_fdb_info。在 SWITCHDEV_FDB_ADD 上,桥接驱动程序会将 FDB 条目安装到桥接的 FDB 中,并将该条目标记为 NTF_EXT_LEARNED。iproute2 桥接命令将这些条目标记为 “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 link set dev DEV learning off

应启用设备端口上的学习,以及 learning_sync

bridge link set dev DEV learning on self
bridge link set dev DEV learning_sync on self

learning_sync 属性启用将学习/遗忘的 FDB 条目同步到桥接的 FDB。可以在设备端口和桥接端口上启用学习,并禁用 learning_sync,但这并非最佳做法。

为了支持学习,驱动程序实现 switchdev 操作 switchdev_port_attr_set 以用于 SWITCHDEV_ATTR_PORT_ID_{PRE}_BRIDGE_FLAGS。

FDB 老化

网桥将跳过标记为 NTF_EXT_LEARNED 的老化 FDB 条目,端口驱动程序/设备负责老化这些条目。如果端口设备支持老化,当 FDB 条目过期时,它将通知驱动程序,驱动程序反过来将通过 SWITCHDEV_FDB_DEL 通知网桥。如果设备不支持老化,驱动程序可以使用垃圾回收计时器模拟老化来监视 FDB 条目。过期的条目将使用 SWITCHDEV_FDB_DEL 通知网桥。有关运行老化计时器的驱动程序示例,请参阅 rocker 驱动程序。

为了保持 NTF_EXT_LEARNED 条目“活跃”,驱动程序应通过调用 call_switchdev_notifiers(SWITCHDEV_FDB_ADD, ...) 来刷新 FDB 条目。通知将 FDB 条目的上次使用时间重置为当前时间。驱动程序应限制刷新通知的速率,例如,不超过每秒一次。(上次使用时间可以使用 bridge -s fdb 选项查看)。

端口上的 STP 状态更改

在内部或使用第三方 STP 协议实现(例如 mstpd),网桥驱动程序维护端口的 STP 状态,并将使用 switchdev 操作 switchdev_attr_port_set(用于 SWITCHDEV_ATTR_PORT_ID_STP_UPDATE)通知交换机驱动程序端口上的 STP 状态更改。

状态是 BR_STATE_* 之一。交换机驱动程序可以使用 STP 状态更新来更新端口的入口数据包过滤器列表。例如,如果端口被禁用 (DISABLED),则不应通过任何数据包,但如果端口变为阻塞 (BLOCKED),则 STP BPDU 和其他 IEEE 01:80:c2:xx:xx:xx 链路本地多播数据包可以通过。

请注意,STP BPDU 是未标记的,并且 STP 状态适用于端口上的所有 VLAN,因此数据包过滤器应在端口上的未标记和标记 VLAN 中一致地应用。

泛洪 L2 域

对于给定的 L2 VLAN 域,如果端口当前 STP 状态允许,交换机设备应将多播/广播和未知单播数据包泛洪到域中的所有端口。交换机驱动程序知道哪些端口在哪个 VLAN L2 域中,可以对交换机设备进行编程以进行泛洪。数据包可以发送到端口 netdev 以便由网桥驱动程序处理。网桥不应将数据包重新泛洪到设备已泛洪的同一端口,否则线路上将出现重复数据包。

为了避免重复数据包,交换机驱动程序应通过设置 skb->offload_fwd_mark 位将数据包标记为已转发。网桥驱动程序将使用入口网桥端口的标记来标记 skb,并阻止其通过任何具有相同标记的网桥端口转发。

交换机设备可能无法处理泛洪,并将数据包推送到网桥驱动程序进行泛洪。这不理想,因为当端口在 L2 域中扩展时,设备在泛洪数据包方面比软件效率更高。

如果设备支持,可以将泛洪控制卸载到设备,从而防止某些 netdev 泛洪没有 FDB 条目的单播流量。

IGMP 侦听

为了支持 IGMP 侦听,端口 netdev 应将所有 IGMP 加入和离开消息捕获到网桥驱动程序。网桥多播模块将通知每个多播组更改上的端口 netdev,无论是静态配置还是动态加入/离开。硬件实现应仅将所有注册的多播流量组转发到配置的端口。

L3 路由卸载

卸载 L3 路由需要使用内核中的 FIB 条目对设备进行编程,设备执行 FIB 查找和转发。设备在与路由前缀匹配的 FIB 条目上执行最长前缀匹配 (LPM),并将数据包转发到匹配的 FIB 条目的下一跳出口端口。

为了对设备进行编程,驱动程序必须使用 register_fib_notifier 注册 FIB 通知程序处理程序。以下事件可用:

FIB_EVENT_ENTRY_ADD

用于将新 FIB 条目添加到设备,或修改设备上的现有条目。

FIB_EVENT_ENTRY_DEL

用于删除 FIB 条目

FIB_EVENT_RULE_ADD,

FIB_EVENT_RULE_DEL

用于传播 FIB 规则更改

FIB_EVENT_ENTRY_ADD 和 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;
};

以在表 tb_id 上添加/修改/删除 IPv4 dst/dest_len 前缀。*fi 结构包含有关路由和路由下一跳的详细信息。*dev 是路由的下一跳列表中提到的端口 netdev 之一。

卸载到设备的路由在 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

如果至少有一个设备卸载了 FIB 条目,则设置“卸载”标志。

XXX:添加/修改/删除 IPv6 FIB API

下一跳解析

FIB 条目的下一跳列表包含下一跳元组(网关,dev),但是为了使交换机设备使用正确的 dst mac 地址转发数据包,必须将下一跳网关解析为邻居的 mac 地址。邻居 mac 地址发现通过 ARP(或 ND)过程进行,并且可通过 arp_tbl 邻居表获得。为了解析路由下一跳网关,驱动程序应触发内核的邻居解析过程。有关示例,请参阅 rocker 驱动程序的 rocker_port_ipv4_resolve()。

驱动程序可以使用 netevent 通知程序 NETEVENT_NEIGH_UPDATE 监视 arp_tbl 的更新。可以根据 arp_tbl 更新为路由编程已解析的下一跳。驱动程序实现 ndo_neigh_destroy 以了解何时从端口清除 arp_tbl 邻居条目。

设备驱动程序的预期行为

以下是启用 switchdev 的网络设备必须遵守的一组定义的行为。

无配置状态

在驱动程序启动时,网络设备必须完全可操作,并且后备驱动程序必须配置网络设备,以便可以向该网络设备发送和接收流量,并且它与其他网络设备/端口正确分离(例如:在交换机 ASIC 中经常出现)。如何实现这一点在很大程度上取决于硬件,但是一个简单的解决方案是使用每个端口的 VLAN 标识符,除非有更好的机制可用(例如,每个网络端口的专有元数据)。

网络设备必须能够运行完整的 IP 协议栈,包括多播、DHCP、IPv4/6 等。如有必要,它应为 VLAN、多播、单播等编程适当的过滤器。底层设备驱动程序必须以类似于为在这些 switchdev 网络设备上启用 IP 多播的 IGMP 侦听时的方式配置,并且必须在硬件中尽早过滤掉未经请求的多播。

在网络设备之上配置 VLAN 时,所有 VLAN 都必须工作,而不管其他网络设备的状态如何(例如:作为进行入口 VID 检查的 VLAN 感知网桥一部分的其他端口)。有关详细信息,请参见下文。

如果设备实现 VLAN 过滤等,将接口置于混杂模式应允许接收所有 VLAN 标签(包括那些未在过滤器中出现的标签)。

桥接交换机端口

当将启用了 switchdev 的网络设备添加为网桥成员时,它不应中断任何非桥接网络设备的功能,并且它们应继续像正常的网络设备一样运行。根据下面的网桥配置旋钮,记录了预期的行为。

网桥 VLAN 过滤

Linux 网桥允许配置 VLAN 过滤模式(静态地,在设备创建时,以及动态地,在运行时),底层 switchdev 网络设备/硬件必须遵守该模式。

  • 禁用 VLAN 过滤时:网桥严格来说是不感知 VLAN 的,并且其数据路径将处理所有以太网帧,就好像它们是未标记 VLAN 一样。网桥 VLAN 数据库仍然可以修改,但是当禁用 VLAN 过滤时,修改应该不起作用。具有未编程到网桥/交换机 VLAN 表中的 VID 的帧进入设备时,必须转发,并且可以使用 VLAN 设备进行处理(请参见下文)。

  • 启用 VLAN 过滤时:网桥是 VLAN 感知的,并且具有未编程到网桥/交换机 VLAN 表中的 VID 的帧进入设备时,必须被丢弃(严格的 VID 检查)。

当在作为网桥端口成员的 switchdev 网络设备之上配置 VLAN 设备(例如:sw0p1.100)时,必须保留软件网络堆栈的行为,或者如果不可能,则必须拒绝配置。

  • 禁用 VLAN 过滤时,网桥将处理端口的所有入口流量,除了目标为 VLAN 上层的带有 VLAN ID 标记的流量。VLAN 上层接口(它使用 VLAN 标签)甚至可以添加到第二个网桥,该网桥包括其他交换机端口或软件接口。一些确保正确管理属于 VLAN 上层接口的流量的转发域的方法。

    • 如果可以按 VLAN 管理转发目标,则可以将硬件配置为将所有流量(除了标记有属于 VLAN 上层接口的 VID 的数据包)映射到对应于未标记数据包的内部 VID。此内部 VID 跨越不感知 VLAN 的网桥的所有端口。对应于 VLAN 上层接口的 VID 跨越该 VLAN 接口的物理端口,以及可能与该接口桥接的其他端口。

    • 将具有 VLAN 上层接口的网桥端口视为独立的,并在软件数据路径中处理转发。

  • 启用 VLAN 过滤时,只要网桥在任何网桥端口上都没有具有相同 VID 的现有 VLAN 条目,就可以创建这些 VLAN 设备。这些 VLAN 设备不能被从属于网桥,因为它们会复制网桥 VLAN 数据路径处理的功能/用例。

在网桥设备上启用 VLAN 过滤绝不应干扰同一交换机结构的非桥接网络端口。如果 VLAN 过滤设置对于整个芯片是全局的,则独立端口应通过在 ethtool 功能中设置“rx-vlan-filter: on [fixed]”来向网络堆栈指示需要 VLAN 过滤。

由于 VLAN 过滤可以在运行时开启/关闭,switchdev 驱动程序必须能够动态地重新配置底层硬件,以响应该选项的切换并做出适当的行为。如果无法做到这一点,switchdev 驱动程序也可以拒绝支持在运行时动态切换 VLAN 过滤旋钮,而要求销毁桥接设备并创建具有不同 VLAN 过滤值的新桥接设备,以确保将 VLAN 感知推送到硬件中。

即使桥接中的 VLAN 过滤被关闭,只要遵守上述行为,底层交换机硬件和驱动程序仍然可以将其自身配置为 VLAN 感知模式。

桥接的 VLAN 协议在决定是否将数据包视为已标记方面起作用:使用 802.1ad 协议的桥接必须将未标记 VLAN 的数据包以及标记有 802.1Q 标头的数据包都视为未标记。

802.1p (VID 0) 标记的数据包必须与未标记的数据包一样被设备处理,因为桥接设备不允许在其数据库中操作 VID 0。

当桥接启用了 VLAN 过滤并且入口端口未配置 PVID 时,必须丢弃未标记和 802.1p 标记的数据包。当桥接启用了 VLAN 过滤并且入口端口存在 PVID 时,必须接受未标记和优先级标记的数据包,并根据 PVID VLAN 的桥接端口成员关系进行转发。当桥接禁用了 VLAN 过滤时,PVID 的存在/缺失不应影响数据包的转发决策。

桥接 IGMP 窥探

Linux 桥接允许配置 IGMP 窥探(静态地,在接口创建时,或动态地,在运行时),底层 switchdev 网络设备/硬件必须以下列方式观察它:

  • 当 IGMP 窥探关闭时,必须将多播流量泛洪到同一桥接内所有具有 mcast_flood=true 的端口。CPU/管理端口理想情况下不应被泛洪(除非入口接口具有 IFF_ALLMULTI 或 IFF_PROMISC),并继续通过网络堆栈通知来学习多播流量。如果硬件无法做到这一点,则还必须泛洪 CPU/管理端口,并且多播过滤在软件中进行。

  • 当 IGMP 窥探开启时,多播流量必须选择性地流向相应的网络端口(包括 CPU/管理端口)。未知多播的泛洪应仅指向连接到多播路由器的端口(本地设备也可以充当多播路由器)。

由于 Linux 桥接实现是这样做的,因此交换机必须遵守 RFC 4541 并相应地泛洪多播流量。

由于 IGMP 窥探可以在运行时开启/关闭,switchdev 驱动程序必须能够动态地重新配置底层硬件,以响应该选项的切换并做出适当的行为。

switchdev 驱动程序也可以拒绝支持在运行时动态切换多播窥探旋钮,而要求销毁桥接设备并创建具有不同多播窥探值的新桥接设备。