如何有效管理XDP/eBPF以获得更好的DDoS保护

扩展的 Berkeley 数据包过滤器(eBPF)能够快速、不间断地进行更新,非常适合处理频繁的安全配置更改。

译自 How to Manage XDP/eBPF Effectively for Better DDoS Protection,作者 Ivan Koveshnikov。

扩展 Berkeley 数据包过滤器 (eBPF) 映射用作共享内存段的原子更新的高级接口,这些段用作共享内存并为 eBPF 程序提供强大的配置接口。读-复制-更新机制最大程度地减少了热路径中的性能占用。此外,eBPF 映射允许独占访问共享内存片段。它们可以处理混合映射类型(数组、哈希表、布隆过滤器、队列和环形缓冲区),这使得它们非常适合复杂配置,例如安全性

随着配置复杂性的增加,不同映射条目之间的连接需求也在增加。如果映射条目之间的连接过多,进行原子配置更新的能力就会开始下降。仅更新一个映射条目可能意味着必须同时更新其他条目,这可能会导致更新期间出现不一致。

应用 XDP 进行高级流量管理

考虑一个简单的 eXpress 数据路径 (XDP) 程序,它根据优先级五元组规则集对流量进行分类和过滤。该程序根据规则的优先级和数据包的源 IP 地址、目标 IP 地址、协议以及源端口和目标端口的组合处理下一个数据包。

分类导致处理的流程图

分类导致处理的流程图。

以下是网络配置规则的示例:

  1. 始终允许来自子网 A 的任何流量。
  2. 限制子网 C 的客户端访问子网 B 中的 Web 服务器。
  3. 限制访问子网 B 中的 Web 服务器。
  4. 拒绝所有其他访问。

这些规则要求在配置中存储流量分类规则和限制,这可以通过使用 eBPF 映射来实现。

将 eBPF 程序配置理解为树结构

你可以将配置可视化为一个分层树,其基础上的“配置根”作为基础。此根(可能是虚拟的)组织各种配置实体以形成活动配置。实体要么直接连接到根以进行立即全局访问,要么嵌套在其他实体中以进行结构化组织。

访问特定实体从根开始,按顺序(逐级“解除引用”)进行,直到所需的实体。例如,要从集合中的“选项”结构中检索布尔标志,你需要导航到集合,找到结构,然后检索标志。

Gcore 应对 eBPF 复杂性挑战的方法

这种树状结构在配置管理中提供了灵活性,包括任何子树的原子交换,确保平稳过渡而不会中断。但是,复杂性的增加带来了挑战。随着配置变得更加复杂,条目之间的相互联系也变得更加紧密。几个父条目指向单个子条目或一个条目扮演双重角色的情况很常见,既充当一个实体的属性,又成为集合的一部分。

现代编程语言已经开发出管理复杂配置的机制。开发人员使用引用计数器、可变和不可变引用以及垃圾回收器来确保安全更新。但是,管理这些配置的安全性并不能保证在配置版本之间切换时的原子性。

在线流量不断变化的格局意味着 安全运营 团队必须对安全策略进行频繁更改。因此,Gcore 对 Gcore DDoS 保护 进行了快速且频繁的更新,并纳入了正则表达式引擎等重要功能。我们超越了自托管解决方案每天一到两次的标准更新,转而采用服务提供商所需的近乎持续的更新。这种需求在 Linux 应用程序中常常被忽视,它促使我们采用 eBPF 技术,该技术可以实现快速、不间断的更新。

在探索 eBPF 解决方案时,我们必须彻底探索策略,以确保以最佳方式处理我们的 eBPF 配置。具体来说,eBPF 映射的限制导致我们的团队重新考虑我们的配置存储策略。

由于内核安全验证,eBPF 映射条目无法存储指向任意内存段的直接指针,这需要使用搜索键来访问映射条目,从而减慢查找过程。但此缺点提供了一个好处:它允许我们将复杂的配置树划分为更小、更易于管理的段,直接链接到配置根。结果是什么?一致性,即使在非原子更新期间。

我们的发现和策略强调了对 eBPF 程序进行优化效率所需的小心规划和执行的重要性。因此,现在让我们转向 eBPF 环境的特定配置更新策略及其对系统独特要求和限制的适用性。

安全配置更新策略

我们发现有三种更新策略在增强程序更新的同时确保高性能和灵活性方面特别有效。

更新策略 1:逐步过渡

逐步更新策略意味着在多个映射中进行增量配置更新。当在一个映射中处理数据为另一个映射提供查找键时,这是一个有用的选项。在这种情况下,需要更新多个映射条目,原子转换是不可行的。但精确且顺序的更新操作可以对配置进行有条不紊的更新。如果按正确的顺序执行,对引用的配置子树的一些操作将变得安全。

例如,在分类和处理的上下文中,分类层为匹配的安全策略提供查找键,这意味着更新操作应遵循特定顺序:

  • 插入新的安全策略是安全的,因为新策略尚未被引用。
  • 更新现有安全策略也是安全的,因为单独更新它们通常不会出现问题。虽然原子更新是可取的,但它没有提供显著的优势。
  • 更新分类层映射以引用新的安全策略并删除对过时策略的引用是安全的。
  • 从配置中清除未使用的安全策略一旦不再引用是安全的。

即使没有原子更新,也可以通过正确排序更新过程来执行安全更新。此方法最适合与其他映射没有紧密关联的独立映射。

我们建议执行增量更新,而不是一次更新整个映射。例如,对哈希映射和数组进行增量更新是完全安全的。然而,对最长前缀匹配 (LPM) 映射进行增量更新并非如此,因为查找取决于映射中已存在的元素。当为另一个表创建查找键需要你操作来自多个映射的元素时,也会出现同样的问题。

分类层通常使用多个 LPM 和哈希表实现,提供了此复杂性的一个示例:

从分类到 LPM 和哈希的查找流,以及从分类到处理再到哈希的查找流,并附有映射更新问题说明。

更新策略 2:映射替换

对于无法在不出现不一致的情况下进行增量更新的映射(例如 LPM 映射),替换整个映射是最佳解决方案。要替换 eBPF 程序的映射,你需要一个映射的映射。用户空间应用程序可以创建一个新映射,用必要的条目填充它,然后以原子方式替换旧映射。

映射的映射导致两个具有资源隔离和替换功能的节点。

将配置划分为单独的映射,每个映射描述单个实体的设置,提供了资源隔离的附加好处,并且无需在较小的更新期间重新创建完整配置。每个多个实体的配置都可以存储在可替换的映射中。

此方法有一些缺点。用户空间需要取消固定以前的映射以维护以前的固定路径,因为替换映射无法固定到与以前映射相同的位置。对于频繁更新配置并依赖映射固定以实现稳定性的长期程序,这一点尤其重要。

更新策略 3:程序替换

在将多个映射链接在一起时,映射替换方法可能会失败。单独更新映射可能会导致不一致或无效的状态,既不反映旧配置也不反映预期的新配置。

为了解决这个问题,原子更新应该在更高的级别发生。尽管 eBPF 缺乏原子替换一组映射的机制,但映射通常链接到特定的 eBPF 程序。将相互连接的映射和相应代码划分为由尾调用链接的单独 eBPF 程序可以解决这个问题。

数据包管道到程序映射流程图,导致 eBPF 程序的可替换代码和映射包。

实现此操作需要加载一个新的 eBPF 程序,为其创建和填充映射,固定两者,然后从用户空间更新程序映射。此过程比简单的映射替换更费力,但它允许同时更新映射和关联代码,从而促进运行时代码调整。但是,使用此方法并不总是特别有效,尤其是在使用多个映射和子程序更新复杂程序中的单个映射条目时。

错误处理

在管理 eBPF 时处理错误可能很棘手。更新配置以防止不一致性非常重要。如果在更新期间出现错误,可能会造成混乱,因此拥有自动备份有助于减少手动修复的需要。

你可以将错误分为两类:可恢复错误和不可恢复错误。对于可恢复错误,如果在更新期间出现问题,你可以简单地停止,并且不会进行任何更改。你可以修复任何错误,而不会有风险。

不可恢复错误有点棘手。你需要小心处理它们,因为它们会影响特定的配置实体,这可能会破坏整个系统。

最好按配置实体而不是更新类型组织更新。这样,如果发生错误,它只会影响特定的配置实体,而不会一次影响所有内容。例如,如果不同的网络段定义了分类规则和安全策略,那么根据网络段而不是按更新类型在单独的周期中更新它们会更有效。这使得处理自动备份变得更容易,如果发生不可恢复错误,你将确切地知道影响是什么。网络只有一部分配置不一致,而其余部分不受影响或可以快速切换到新配置。

管理 eBPF 程序生命周期以进行更新

跟踪 eBPF 程序的生命周期对于需要持久性、频繁更新和跨不同代码实例保留状态的程序至关重要。例如,如果 XDP 程序需要频繁的代码更新,同时还要维护现有的客户端会话,那么有效管理其生命周期至关重要。

对于希望最大限度地提高灵活性和避免约束的开发人员来说,目标应该是仅在重新加载之间保留重要信息——无法从非易失性存储中获取的数据。这样,你可以使用 eBPF 映射进行动态配置调整。

为了使热代码重新加载过程更直接,你需要能够区分状态映射和配置映射,在重新加载期间重用状态映射,并从非易失性存储中重新填充配置映射。将处理从旧程序过渡到新程序并通知所有 eBPF 映射用户有关更改的信息可能会有点麻烦。

有两种常用的方法来实现过渡:

  • 原子程序替换:此方法涉及将 XDP 程序直接附加到网络接口并在更新期间以原子方式将其交换出去。对于与大量用户空间程序和映射交互的大型复杂 eBPF 程序,这可能不是最合适的。
  • 类似 libxdp 的方法:调度程序程序链接到网络接口,并使用尾调用在程序映射中进行实际处理的下一个程序中进行处理。除了管理映射使用和固定之外,它还协调多个处理程序,从而实现它们之间的快速转换。

图表显示网络接口卡 (NIC) 连接到调度程序、程序映射和状态映射,从而导致实际程序配置。

网络接口卡 (NIC) 连接到调度程序、程序映射和状态映射,从而导致实际程序配置。

热重新加载过程能够快速检测和纠正配置问题,并在需要时快速恢复到以前的稳定版本。对于 A/B 测试等复杂场景,调度程序可以使用分类表将特定流量引导到 XDP 程序的新版本。

结论

通过 eBPF/XDP 编程,Gcore 已经突破了网络安全和性能优化的界限。我们的旅程展示了我们致力于通过高级 eBPF/XDP 功能对抗新兴威胁。随着我们不断改进我们的数据包处理核心,我们致力于提供尖端的解决方案,以帮助保持我们客户网络的稳健性和敏捷性。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注