# CARP 简介

* 原文地址：[Introduction to CARP](https://freebsdfoundation.org/wp-content/uploads/2022/11/zaborski_CARP.pdf)
* 作者：**MARIUSZ ZABORSKI**

在大型网络中，高可用性问题可能会变得充满挑战与复杂性，因此我们应当寻求那些简单、易于维护和理解的解决方案。毫无疑问，CARP 协议正是其中之一。CARP 是 Common Address Redundancy Protocol，共用地址冗余协议的缩写，其基本功能是让多台主机共享一组 IP 地址。CARP 协议并非新鲜事物——它最初作为 CISCO 协议 VRRP 的替代方案，于 2003 年出现在 OpenBSD。由于专利问题（超出本文讨论范围），CARP 被用来取代 VRRP 协议。在 OpenBSD 推出 CARP 后，它随后被集成到了 FreeBSD 和 NetBSD 中。最终，ucarP 被引入——这是一种 CARP 协议的用户态实现，为内核实现提供了另一种选择，并使其在 Linux 上也得以使用。

## CARP 背景

CARP 可构建一个冗余组——一组共享 IP 地址的主机。然而，从物理上讲，只有一个网络接口拥有这些 IP 地址（该接口称为活跃主机）。当活跃主机消失（例如被关闭或网络出现问题）时，冗余组中的其他主机会检测到这一情况，并选举出新的活跃主机。该情况如图 1 所示。

在这个冗余组中，有两台机器——蓝色和绿色，但只有蓝色的是活跃节点，因此网络中的其他机器能够顺利地选择连接目标。

**图 1. 活跃与被动的 CARP 节点**

![](https://github.com/user-attachments/assets/90eacfa5-5025-46b1-b584-f0b94af3c996)

在 CARP 中，活跃节点会广播其状态，如图 2 所示。由于蓝色主机是活跃节点，因此它正在广播 CARP 数据包，而绿色和/或橙色节点则处于待命状态，不发送任何数据包。CARP 数据包体积很小，仅包含一些基本信息，例如：

* **vhid（虚拟服务器 ID）**：用于标识冗余组；冗余组中的所有机器必须共享相同的 vhid
* 关于 CARP 版本以及 CARP 数据包类型的信息

CARP 中的所有数据包均经过加密签名，这意味着冗余组中的每个节点都必须共享同一密钥。CARP 永远不会以明文形式将密码发送到网络上。非常重要的一点是，冗余组中的每台机器都必须配置有完全相同的一组 IP 地址。不过，这些 IP 地址并不会在网络上传输——它们仅用于计算加密签名。在图 2 中所示的情形下，只要蓝色服务器使用指定的 vhid 正确地广播经过加密签名的数据包，其余节点便不执行任何操作，只是处于监听状态。

**图 2. CARP 数据包广播**

![](https://github.com/user-attachments/assets/31f6e10e-b01f-4efe-b2d6-152e714538c2)

当某个节点一段时间内未能接收到 CARP 数据包时，另一节点便会介入并成为活跃节点。图 3 展示了这一情况。由于某种原因，蓝色节点停止了数据包广播，绿色节点发现这一情况后便开始广播 CARP 数据包。当蓝色节点恢复时，它会注意到绿色节点已成为活跃节点，从而保持被动状态。

**图 3. 新的活跃节点**

![](https://github.com/user-attachments/assets/bf2da29e-2f53-434d-88c8-199c26f9c952)

以上所有示例均展示了冗余组中只有一个 IP 地址的情况。但实际上，冗余组可以包含多个 IP 地址，而且主机也可以属于多个冗余组——这可以通过不同的 vhid 实现。得益于这一特性，我们还可以在网络中的服务之间实现某种程度的负载均衡。例如，绿色节点可以在提供 Web 服务器服务的冗余组中担任活跃节点，而蓝色节点则在提供时间服务的冗余组中担任活跃节点。如果其中某一节点消失，另一节点则会在相应冗余组中成为活跃节点。

## CARP 与脑裂

在某种情况下，两个节点同时发现某个节点消失，便可能同时尝试成为活跃节点。这种情况被称为脑裂，即出现多个活跃节点的情况。当节点之间的链路断开，彼此无法接收对方的数据包，经过一段时间后问题恢复时也可能出现这种情况，如图 4 所示。

CARP 同样能解决这一问题。当两个主机均处于活跃状态时，它们都在广播 CARP 数据包。广播数据包频率更高、在更短时间内发送更多数据包的节点会被优先选为新的主控节点。这一机制在 CARP 中通过优先级来控制，优先级越低表示数据包发送频率越高。当另一节点发现对方的数据包发送频率比自己高时，就会自动切换回被动模式。在两个节点以相同优先级发送数据包的情况下，将随机选择其中一节点作为活跃节点。

**图 4. 脑裂情况**

![](https://github.com/user-attachments/assets/dfccd1fc-fa41-4af0-a593-e80295450110)

## FreeBSD 内核模块 CARP 配置

CARP 模块包含在 FreeBSD 的默认安装中。从 FreeBSD 10.0 开始，CARP 不再是伪接口，而是直接在网络接口上进行配置。列表 1 展示了 CARP 的基本配置。首先，我们需要加载 FreeBSD 的 CARP 模块，这可以通过 **kldload(8)** 命令完成。然后，使用 **ifconfig(8)** 命令，定义 CARP 应在哪个接口上工作（在我们的例子中是 **em0**）。接下来，我们定义冗余组的 ID（将 **vhid** 设为 1）。另一个重要的配置项是用于计算校验和的密码短语；这个密码短语必须在冗余组内的所有主机之间共享。在命令中，我们还定义了优先级（或称为广告间隔）。这由两个参数控制：

* **advbase**（广告基准），以秒为单位指定；
* **advskew**（广告偏移——此参数在列表中未显示），以 1/256 秒为单位测量。

需要提醒的是——较低的优先级意味着主机广告发送得更频繁，也就表示它是优选节点。最后，我们定义了浮动地址。在同一列表中，有两次 **ifconfig(8)** 的运行；与 CARP 无关的部分已被省略。在第一次运行中，我们可以看到冗余组处于 **BACKUP** 状态，这意味着该接口处于待命模式，正监听 CARP 数据包。由于网络中没有 CARP 数据包，它便切换为 **MASTER**（活跃）状态，并开始广播其状态。在图 5 中，我们可以看到捕获到的 CARP 数据包，其使用第二个静态 IP 地址向多播 IP 地址发送 CARP 数据包。因此，除了共享的 IP 地址外，还必须配置一个额外的 IP 地址。

**清单 1. FreeBSD 中 CARP 的配置**

```sh
# kldload carp
# ifconfig em0 vhid 1 pass randompass advbase 1 alias 192.168.1.50/32
# ifconfig
em0:
    inet 192.168.1.50 netmask 0xffffffff broadcast 192.168.1.50 vhid 1
    carp: BACKUP vhid 1 advbase 1 advskew 0
# ifconfig
em0:
    inet 192.168.1.50 netmask 0xffffffff broadcast 192.168.1.50 vhid 1
    carp: MASTER vhid 1 advbase 1 advskew 0
    status: active
```

**图 5. 使用 Wireshark 捕获的 CARP 流量**

![](https://github.com/user-attachments/assets/808433d6-02fe-4f66-a2cd-80557fcb97d9)

或许有用的一点是，FreeBSD 的 devd(8) 守护进程允许在状态发生变化时运行额外的脚本。列表 2 展示了来自 FreeBSD 手册页的一个此类配置示例。当冗余组状态改变时，将执行 `/root/carpcontrol.sh` 脚本，第一个参数为 `vhid@inet`，第二个参数为当前组状态。

列表 2. 针对 CARP 的 devd(8) 配置

```sh
notify 0 {
       match "system" "CARP";
       match "subsystem" "[0-9]+@[0-9a-z.]+";
       match "type" "(MASTER|BACKUP)";
       action "/root/carpcontrol.sh $subsystem $type";
};
```

## ucarp

另外，一个非常有前景的项目是 **ucarp**，即 CARP 协议的用户态实现。它减少了内核空间中的代码量。而内核空间的实现可能略有不同，在那种情况下，代码库是为多个平台共享的。然而，该项目似乎已经被放弃——GitHub 上的项目已关闭，且 ucarp 域名已过期。不过，你仍然可以在不同操作系统上找到 ucarp 的分发版本，因此如果你在寻找跨平台的实现，我们仍然推荐你关注该项目。它的配置选项与内核实现非常相似。

列表 1 展示了如何在 FreeBSD 系统上安装 ucarp，紧接着的命令显示了其基本用法。此时，大部分选项都不言自明。下面我们详细介绍一下 **upscript** 和 **downscript** 选项。由于 ucarp 被设计为一个多平台工具，它本身并不知道如何将 IP 地址添加到网络接口上——这部分工作由管理员负责。用户必须自行定义脚本，以便将 IP 地址添加到正确的接口上。

**清单 3. ucarp 的基本用法**

```sh
# pkg install carp
# ucarp --interface=eth0 --srcip=192.168.1.157 --vhid=1 --pass=randompass
--addr=192.168.1.50 --upscript=up.sh --downscript=down.sh
```

另一个关于 ucarp 的小问题是：我们只能为该协议定义一个单独的浮动 IP 地址。虽然我们可以在 upscript 和 downscript 中添加许多 IP 地址，但只有一个（来自参数 addr）会被加入到加密校验中。这意味着如果我们希望混合使用内核和用户态的 CARP 实现，则在单个冗余组中使用多个浮动地址将无法正常工作，因为校验和不匹配。

## 总结

CARP 是一款简单但功能强大的工具，能够为我们的网络提供高可用性。主要有两种 CARP 实现：一种是在内核空间中实现的（每个 BSD 操作系统均自带此实现），另一种是跨平台的用户态实现 ucarp（亦支持 Linux）。遗憾的是，用户态实现似乎已经被放弃。不过，如果你正在寻找一个简单易用的解决方案来提供浮动地址，那么你仍然可以考虑使用它。

## 参考文献

* **CARP on Wikipedia**\
  <https://en.wikipedia.org/wiki/Common_Address_Redundancy_Protocol>
* **UCARP GitHub Project**\
  <https://github.com/jedisct1/UCarp>
* **CARP in FreeBSD Handbook**\
  <https://docs.freebsd.org/en/books/handbook/advanced-networking/#carp>
* **CARP FreeBSD man page**\
  <https://www.freebsd.org/cgi/man.cgi?query=carp&sektion=4>

## 致谢

本文中的图片资源来自 [flaticon.com](https://www.flaticon.com)。

***

**MARIUSZ ZABORSKI** 目前在 4Prime 担任安全专家。他自 2015 年起就自豪地拥有 FreeBSD 提交权限。他的主要兴趣领域是操作系统安全和底层编程。Mariusz 曾在 Fudo Security 工作，领导团队开发 IT 基础设施中最先进的 PAM 解决方案。2018 年，他组织了波兰 BSD 用户组。在业余时间，Mariusz 喜欢在 <https://oshogbo.vexillium.org> 上撰写博客。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://freebsd-journal-cn.bsdcn.org/20220910-an-quan-xing/carp.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
