> For the complete documentation index, see [llms.txt](https://book.bsdcn.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://book.bsdcn.org/openbsd/networking-and-daemons/pf/forwarding.md).

# PF 转发

## 简介

办公室里运行 NAT 后，所有机器都可访问整个互联网。但如果 NAT 网关后有一台机器需要被外部访问呢？这就需要重定向。重定向允许将入站流量发送到 NAT 网关后的机器。

来看一个例子：

```pf
pass in on egress proto tcp from any to any port 80 rdr-to 192.168.1.20
```

这行将 TCP 端口 80（Web 服务器）的流量重定向到网络内部 **192.168.1.20** 的机器。如此一来，即便 **192.168.1.20** 位于网关之后、身处你的网络内部，外部世界也能访问它。

上面 `rdr` 行中的 `from any to any` 部分相当有用。若你知道哪些地址或子网应被允许访问端口 80 上的 Web 服务器，可在此处加以限制：

```pf
pass in on egress proto tcp from 203.0.113.0/24 to any port 80 rdr-to 192.168.1.20
```

这只会重定向指定子网。注意，这意味着你可以将不同的入站主机重定向到网关后不同的机器。这同样很有用。例如，只要你知道远程用户从哪个 IP 地址连接，就可让远程站点的用户使用网关上相同的端口与 IP 地址访问他们自己的桌面计算机。

```pf
pass in on egress proto tcp from 203.0.113.14 to any port 80 rdr-to 192.168.1.20
pass in on egress proto tcp from 198.51.100.89 to any port 80 rdr-to 192.168.1.22
pass in on egress proto tcp from 198.51.100.178 to any port 80 rdr-to 192.168.1.23
```

同一规则内也可重定向一段端口范围：

```pf
pass in on egress proto tcp from any to any port 5000:5500 \
   rdr-to 192.168.1.20
pass in on egress proto tcp from any to any port 5000:5500 \
   rdr-to 192.168.1.20 port 6000
pass in on egress proto tcp from any to any port 5000:5500 \
   rdr-to 192.168.1.20 port 7000:*
```

这些示例将 5000 到 5500（含）的端口重定向到 **192.168.1.20**。

* 规则 1 中，端口 5000 重定向到 5000，5001 重定向到 5001，依此类推。
* 规则 2 中，整个端口范围都重定向到端口 6000。
* 规则 3 中，端口 5000 重定向到 7000，5001 重定向到 7001，依此类推。

## 安全影响

重定向确有安全影响。在防火墙上开洞让流量进入受保护的内部网络，可能让内部机器遭受入侵。若流量被转发到内部 Web 服务器，而 Web 服务器守护进程又被发现存在漏洞，互联网上的入侵者便可借此攻陷该机器。入侵者由此获得通向内部网络的入口，且此入口被允许径直穿过防火墙。

通过将外部可访问的系统严格隔离在单独网络上，可将这些风险降至最低。此网络通常称为非军事区（DMZ）或专有服务网络（PSN）。这样，即便 Web 服务器被攻陷，也可通过对进出 DMZ/PSN 的流量仔细过滤，将影响限制在 DMZ/PSN 网络内。

## 重定向与反射

重定向规则常用于将互联网入站连接转发到内部网络或 LAN 中具有专有地址的本地服务器，例如：

```pf
server = 192.168.1.40
pass in on egress proto tcp from any to egress port 80 rdr-to $server port 80
```

但当从 LAN 上的客户端测试此重定向规则时，它并不工作。原因在于重定向规则仅对穿越指定接口（示例中为 egress，即外部接口）的数据包生效。然而，从 LAN 上的主机连接到防火墙的外部地址，并不意味着数据包真的会穿越防火墙的外部接口。防火墙上的 TCP/IP 协议栈会将入站数据包的目的地址与自身地址及别名比较，一旦数据包通过内部接口便立即识别出指向自身的连接。此类数据包物理上并未穿越外部接口，协议栈也不会以任何方式模拟这种穿越。因此，PF 在外部接口上根本看不到这些数据包，指定外部接口的重定向规则也不适用。

为内部接口添加第二条重定向规则也达不到预期效果。本地客户端连接防火墙的外部地址时，TCP 握手的首个数据包通过内部接口到达防火墙。重定向规则确实适用，目的地址被替换为内部服务器的地址。数据包经内部接口转发回内部服务器。但源地址未被转换，仍为本地客户端的地址，因此服务器将回复直接发给客户端。防火墙看不到回复，也就无法正确地反向转换。客户端从意料之外的源收到回复并将其丢弃。TCP 握手随之失败，连接无法建立。

尽管如此，让 LAN 上的客户端像外部客户端那样连接到同一台内部服务器，且对客户端透明，常常仍是所期望的。此问题有几种解决方案：

### 视域分离 DNS

可配置 DNS 服务器，使其对本地主机的查询响应不同于对外部查询的响应，从而让本地客户端在名称解析时获得内部服务器的地址。它们随后直接连接到本地服务器，防火墙完全不介入。由于数据包不必经过防火墙，这还能减少本地流量。

### 将服务器移入独立的本地网络

为防火墙增加一块额外的网络接口，并将本地服务器从客户端所在的网络移入专用网络（DMZ），即可像重定向外部连接那样重定向本地客户端的连接。使用独立网络有若干优点，包括通过将服务器与其余本地主机隔离来提升安全性。若服务器（在本例中可从互联网访问）一旦被攻陷，它也无法直接访问其他本地主机，因为所有连接都必须经过防火墙。

### TCP 代理

可在防火墙上设置通用 TCP 代理，要么监听待转发端口，要么让内部接口上的连接重定向到它监听的端口。本地客户端连接防火墙时，代理接受连接，再向内部服务器建立第二条连接，并在两条连接之间转发数据。

### RDR-TO 与 NAT-TO 组合

在内部接口上添加一条额外的 NAT 规则，即可实现上述缺失的源地址转换。

```pf
pass in on $int_if proto tcp from $int_net to egress port 80 rdr-to $server
pass out on $int_if proto tcp to $server port 80 received-on $int_if nat-to $int_if
```

这会让客户端的首个数据包经内部接口转发回时再次被转换，将客户端的源地址替换为防火墙的内部地址。内部服务器会将回复发回防火墙，防火墙在转发给本地客户端时可反向执行 NAT 与 RDR 转换。这种结构相当复杂，因为每条反射连接会创建两条独立状态。须小心防止 NAT 规则应用到其他流量，例如来自外部主机（通过其他重定向）或防火墙自身的连接。注意，上面的 `rdr-to` 规则会让 TCP/IP 协议栈看到在内部接口上到达、目的地址位于内部网络内的数据包。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://book.bsdcn.org/openbsd/networking-and-daemons/pf/forwarding.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
