> For the complete documentation index, see [llms.txt](https://man.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://man.bsdcn.org/man4/divert.4.md).

# divert(4)

`divert` — 内核数据包转向机制

## 名称

`divert`

## 概要

`#include <sys/types.h>`

`#include <sys/socket.h>`

`#include <netinet/in.h>`

`Ft int Fn socket PF_DIVERT SOCK_RAW 0`

`若要启用对 divert 套接字的支持，请在内核配置文件中加入以下行：`

> options IPDIVERT

`或者，若要在引导时以模块方式加载此驱动，请在 loader.conf(5) 文件中加入以下行：`

```sh
ipdivert_load="YES"
```

## 描述

Divert 套接字允许拦截并重新注入流经 ipfw(4) 和 [pf(4)](/man4/pf.4.md) 防火墙的数据包。可通过 bind(2) 系统调用将 divert 套接字绑定到特定的 `divert` 端口。sockaddr 参数应为 sockaddr\_in，sin\_port 设置为所需值。注意，`divert` 端口与 TCP/UDP 端口无关。它只是一个 cookie，其值取决于所使用的防火墙。对于 ipfw(4)，这是转向数据包的规则编号；对于 [pf(4)](/man4/pf.4.md)，这是一个指示被转向数据包通过防火墙的原始方向的值。绑定到某个 divert 端口的 divert 套接字将接收防火墙转向到该端口的所有数据包。也可将数据包写入 divert 端口，在这种情况下，它们在下一条规则处重新进入防火墙处理。

通过读取和写入 divert 套接字，匹配的数据包在通过主机时可通过任意 "过滤器" 进行处理，可执行特殊的路由技巧等。

如果所有套接字上都设置了 `SO_REUSEPORT_LB` 套接字选项，则多个 divert 套接字可绑定到同一端口。在这种情况下，内核将尝试在套接字之间进行负载均衡。实现确保来自同一流的数据包被传递到同一套接字。为此，它依赖于防火墙为每个被转向的数据包提供流标识符。使用 [pf(4)](/man4/pf.4.md) 防火墙时，这是关联的状态 ID（如果存在），否则所有数据包都被转向到组中的第一个套接字。目前 ipfw(4) 防火墙不提供流标识符，因此所有数据包都被转向到组中的第一个套接字。最多可将 32 个套接字绑定到同一端口。

## 读取数据包

数据包在 "传入" 或 "传出" 时被转向。传入数据包在 IP 接口上接收后被转向，而传出数据包在下一跳转发之前被转向。

可通过 read(2)、recv(2) 或 recvfrom(2) 读取未修改的已转向数据包。在后一种情况下，返回的地址会将其端口设置为数据包转向器提供的某个标记（通常是上述 cookie），IP 地址设置为接收数据包的接口的（第一个）地址（如果数据包是传入的）或 `INADDR_ANY`（如果数据包是传出的）。接口名称（如果为数据包定义）将放在地址之后的 8 个字节中（如果适合）。

## 写入数据包

写入 divert 套接字类似于写入原始 IP 套接字；数据包使用 sendto(2) "按原样" 注入到正常内核 IP 数据包处理中，并进行最少的错误检查。数据包区分为传入或传出。如果 sendto(2) 使用 `INADDR_ANY` 的目标 IP 地址，则数据包被视为传出，即目的地为非本地地址。否则，数据包被假定为传入，并进行完整的数据包路由。

在后一种情况下，指定的 IP 地址必须匹配某个本地接口的地址，或者在 IP 地址之后找到接口名称。如果找到接口名称，将使用该接口并忽略 IP 地址的值（除了它不是 `INADDR_ANY ）`这一事实）。这是为了指示数据包 "到达" 的接口。

通常，作为传入读取的数据包应作为传入写入；传出数据包同理。在读取然后写回数据包时，将 recvfrom(2) 提供的相同套接字地址不修改地传递给 sendto(2) 可简化操作（见下文）。

传递给 sendto(2) 的套接字地址的端口部分包含一个标记，该标记应对转向模块有意义。对于 [ipfw(8)](/man8/ipfw.8.md)，该标记被解释为规则处理应重新启动的规则编号 *之后*。

## 循环避免

写入 divert 套接字（使用 sendto(2)）的数据包在套接字地址端口部分中给定标记之后的规则编号处重新进入数据包过滤器，通常已设置为导致转向的规则编号（如果有多个相同编号的规则，则不是下一条规则）。如果更改 'tag' 以指示替代的重新进入点，应注意避免循环，即同一数据包在同一规则处被多次转向。

## 细节

如果数据包被转向但没有套接字绑定到该端口，或者内核中未启用或未加载 `IPDIVERT`，则数据包被丢弃。

被转向的传入数据包分片在交付前完全重新组装；任何一个分片的转向都会导致整个数据包被转向。如果不同的分片转向到不同的端口，则最终选择哪个端口是不可预测的。

注意，通过 [ipfw(8)](/man8/ipfw.8.md) `tee` 动作到达 divert 套接字的数据包按原样交付，并且在这种情况下数据包分片不会重新组装。

数据包接收和发送时保持不变，但作为传出读取的数据包具有无效的 IP 头校验和，而作为传出写入的数据包其 IP 头校验和会被正确值覆盖。作为传入写入且校验和不正确的数据包将被丢弃。否则，所有头字段保持不变（因此为网络字节序）。

创建 `divert` 套接字需要超级用户权限。

## 错误

写入 divert 套接字可能返回这些错误，以及写入原始数据包时可能出现的常见错误：

**\[Er** EINVAL] 数据包头无效，或者数据包中的 IP 选项与设置的套接字选项不兼容。

**\[Er** EADDRNOTAVAIL] 目标地址包含的 IP 地址不等于 `INADDR_ANY` 且未与任何接口关联。

## 参见

bind(2), recvfrom(2), sendto(2), socket(2), ipfw(4), [pf(4)](/man4/pf.4.md), [ipfw(8)](/man8/ipfw.8.md)

## 作者

Archie Cobbs <archie@FreeBSD.org>，Whistle Communications Corp.

## 缺陷

这是为用户态进程提供一种干净的方式来实现各种 IP 技巧（如地址转换）的尝试，但它可以更干净。

传入分片是否应在转向前重新组装是值得商榷的。例如，如果发往另一台机器的数据包只有部分分片未通过本地机器路由，则该数据包会丢失。在任何情况下，这可能应该是一个可设置的套接字选项。
