以太网交换设备驱动程序模型 (switchdev)¶
版权所有 © 2014 Jiri Pirko <jiri@resnulli.us>
版权所有 © 2014-2015 Scott Feldman <sfeldma@gmail.com>
以太网交换设备驱动程序模型 (switchdev) 是一种内核驱动程序模型,用于将转发(数据)平面从内核中分流的交换设备。
图 1 是一个框图,显示了 switchdev 模型在使用数据中心级交换 ASIC 芯片的示例设置中的组件。其他使用 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 命名¶
端口 netdev 命名应使用 Udev 规则,以端口的某个唯一属性作为键,例如端口 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_immutable
如果 switchdev 驱动程序(和设备)仅支持卸载默认网络命名空间 (netns),则驱动程序应设置此私有标志,以防止端口 netdev 移出默认 netns。一个感知 netns 的驱动程序/设备不会设置此标志,并将负责划分硬件以保持 netns 隔离。这意味着硬件不能将流量从一个命名空间中的端口转发到另一个命名空间中的另一个端口。
端口拓扑¶
表示物理交换机端口的端口 netdev 可以组织成更高级别的交换构造。默认构造是独立的路由器端口,用于卸载 L3 转发。两个或更多端口可以绑定在一起形成一个 LAG。两个或更多端口(或 LAG)可以桥接以桥接 L2 网络。VLAN 可用于细分 L2 网络。L2-over-L3 隧道可以在端口上构建。这些构造是使用标准 Linux 工具构建的,例如网桥驱动程序、绑定/团队驱动程序和基于 netlink 的工具(如 iproute2)。
switchdev 驱动程序可以通过监视 NETDEV_CHANGEUPPER 通知来了解特定端口在拓扑中的位置。例如,移动到绑定中的端口将看到其上层主控发生变化。如果该绑定被移动到网桥中,则绑定的上层主控将发生变化。依此类推。驱动程序将通过注册 netdevice 事件并响应 NETDEV_CHANGEUPPER 来跟踪此类移动,以了解端口在整体拓扑中的位置。
L2 转发卸载¶
核心思想是通过将网桥 FDB 条目镜像到 switchdev 设备来将 L2 数据转发(交换)路径从内核卸载到 switchdev 设备。FDB 条目是 {port, MAC, VLAN} 元组转发目的地。
要卸载 L2 桥接,switchdev 驱动程序/设备应支持
安装在网桥端口上的静态 FDB 条目
设备学习/遗忘源 MAC/VLAN 的通知
端口上的 STP 状态更改
组播/广播和未知单播数据包的 VLAN 泛洪
静态 FDB 条目¶
实现 ndo_fdb_add
、ndo_fdb_del
和 ndo_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_add
和 ndo_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/port 元组。交换机驱动程序反过来将使用 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 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
应启用设备端口上的学习功能,以及 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_ATTR_PORT_ID_{PRE}_BRIDGE_FLAGS 实现了 switchdev 操作 switchdev_port_attr_set。
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 列表中标记为“offload”
$ 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 条目,则设置“offload”标志。
XXX: 添加/修改/删除 IPv6 FIB API
下一跳解析¶
FIB 条目的下一跳列表包含下一跳元组(网关,设备),但为了交换设备能够使用正确的目的 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 都必须正常工作,无论其他网络设备的状态如何(例如:其他端口作为 VLAN 感知网桥的一部分执行入口 VID 检查)。详细信息请参见下文。
如果设备实现例如 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 ID 且目标是 VLAN 上层的流量。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/管理端口)。未知组播的泛洪应仅流向连接到组播路由器的端口(本地设备也可以充当组播路由器)。
交换机必须遵守 RFC 4541 并相应地泛洪组播流量,因为 Linux 网桥实现就是这样做的。
由于 IGMP 侦听可以在运行时开启/关闭,因此 switchdev 驱动程序必须能够即时重新配置底层硬件,以遵循该选项的切换并采取适当的行为。
switchdev 驱动程序也可以拒绝在运行时支持多播侦听旋钮的动态切换,并要求销毁现有网桥设备并创建具有不同多播侦听值的新网桥设备。