[OpenBSD]

[上一小节: 网络地址转换] [总目录] [下一小节: 定义规则的捷径]

PF: 重定向(端口转发)

目录



介绍

当你在办公室中运行NAT时, 你可以让内部所有的机器连接到Internet上, 但是如果外部需要访问NAT网关后的一台机器怎么办呢?这就需要设置重定向了。重定向允许将进站通讯发至NAT网关后面的机器。

我们看一个例子:

pass in on tl0 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 这部分很有用, 如果你知道需要访问web服务器的地址或子网, 你可以在这里进行限制:

pass in on tl0 proto tcp from 27.146.49.0/24 to any port 80 \
   rdr-to 192.168.1.20

这条规则限制了仅能从指定的子网访问。注意, 这意味着你可以将来自不同主机(的通讯)重定向到不同的机器上, 这很有用。例如, 你可以让内部用户从远程站点通过网关上相同的IP地址和端口访问他们自己的计算机,只要您知道他们的IP地址:

pass in on tl0 proto tcp from 27.146.49.14 to any port 80 \
   rdr-to 192.168.1.20
pass in on tl0 proto tcp from 16.114.4.89 to any port 80 \
   rdr-to 192.168.1.22
pass in on tl0 proto tcp from 24.2.74.178 to any port 80 \
   rdr-to 192.168.1.23

重定向规则内同时可以使用端口范围:

pass in on tl0 proto tcp from any to any port 5000:5500 \
   rdr-to 192.168.1.20
pass in on tl0 proto tcp from any to any port 5000:5500 \
   rdr-to 192.168.1.20 port 6000
pass in on tl0 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, 而规则e #3 里, 端口5000被重定向到7000, 5001到7001 ........

安全隐患

重定向肯定存在安全隐患, 这等于在防火墙上穿了一个洞允许外部通讯进入内部, 被保护的网络存在潜在的安全威胁。如果通讯被重定向到一个内部的web服务器, 并且服务器程序或上面的CGI脚本的一个弱点被发现, 那么这台机器可能遭受来自Internet的入侵威胁。入侵者可以从它进入内部网络, 因为防火墙允许以这种方式访问内部网络。

通过将把外部访问的机器严格地限制在隔离的网络上可以最小化这种风险。这个隔离的网络经常被称为非军事化区(DMZ)或者私有服务网络(PSN), 仔细地审查过滤内部网络与隔离区域间的来往通讯, 这样即使这台web服务器被攻陷, 受影响的区域也仅限于DMZ/PSN网络。

重定向与映射

通常, 重定向规则被用于将来自Internet的进站连接转发到有私有地址的内部网络或LAN的一台服务器上, 像这样:
server = 192.168.1.40
pass in on $ext_if proto tcp from any to $ext_if port 80 \    rdr-to $server port 80

但当你从LAN上客户端测试这个重定向规则时, 你会发现它不能工作。原因是重定向规则仅应用在通过指定接口的数据包(在上例中是$ext_if, 外部接口), 然而, 位于LAN的主机要连接到防火墙的外部地址并不意味着数据包真的通过它的外部接口, 防火墙上的TCP/IP堆栈将进站数据包的目的地址与自己的地址及别名进行比较并察觉到要连接到它自己, 所以它马上将这些数据包改道至内部接口。 这类数据包不会在物理意义上真正地通过外部接口, 并且堆栈任何情况下也不会这样处理内部至内部数据包。因此,PF永远不会在外部接口上看见这些数据包, 重定向规则指定的外部接口也不会起作用。

增加第二条针对内部网卡的重定向规则也不能达到期望的效果。当本地的客户端连接防火墙的外部地址时, 完成TCP握手的初始化数据包通过内部接口到达防火墙。重定向规则确实起作用了, 并且目的地址被替换为内部服务器的地址, 这些数据包被转发回来经过内部接口抵达内部服务器。但是源地址并没有被转换, 也就是说还是本地客户端的地址, 所以服务器将回复直接发送到内部客户端, 防火墙永远看不到这些回复和没有机会进行反向转换, 而这个客户端从一个并非自己期望的源地址收到了一个回复, 所以它就直接将这个回复丢弃了, 最终TCP握手失败了, 并且不能建立任何连接。

很明显, 我们还是希望内部的客户端可以像外部的客户端一样连接到内部的服务器。这有几种解决的方法:

水平分割DNS

配置DNS服务器对来自本地和外部的客户端区别应询是有可能的, 这样在域名解析过程中本地客户端将得到内部服务器的地址, 它们就会直接连接到本地服务器上, 而防火墙一点也不会干涉。因为数据包不必通过防火墙, 所以这样减少了本地的通讯量。

将服务器移至单独的本地网络

在防火墙里另外再增加一个网络接口, 并且将本地服务器从客户端所在网络迁移到一个专门的网络(DMZ), 就像对外部客户端一样允许对内部客户端的连接进行重定向。使用单独的网络有几个优点, 包括将服务器和本地其余主机隔离开可以提高安全性, 即便服务器总是面临威胁甚至被攻陷 (本例中可以从Internet直接接触它), 它也不能直接访问本地的其余主机, 因为所有的连接必须经过防火墙。

TCP代理

可以在服务器上建立一个普通的TCP代理, 监听被转发到的端口或者将连到内部接口的连接重定向到它监听的端口, 当一个内部客户端连接到防火墙时, 代理承担这个连接, 再建立一个到内部防火墙的连接, 并且在这两个连接间转发数据。

简单的代理可以使用 inetd(8)nc(1)建立。下面 /etc/inetd.conf 的项目在产生一个监听套接字并绑定在loopback地址(127.0.0.1) 和端口5000上。连接被转发到192.168.1.10的80端口上。下面是通过用户"proxy"完成的。

127.0.0.1:5000 stream tcp nowait proxy /usr/bin/nc nc -w \
   20 192.168.1.10 80

下列的重定向规则转发内部接口上端口80的通讯到这个代理上:

pass in on $int_if proto tcp from $int_net to $ext_if port 80 \
   rdr-to 127.0.0.1 port 5000

高性能的代理也可以用 relayd(8)来创建。

RDR-TO 和 NAT-TO 的组合

在内部接口上附加一条NAT规则, 上面描述的源地址转换中的缺陷将得到修正。

pass in on $int_if proto tcp from $int_net to $ext_if 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堆栈看到那些到达内部接口、以一个内部网络的地址为目标地址的数据包。(原文此处太绕了,读者这样理解吧,也就是TCP/IP堆栈可以看见从内部发起的对内部服务器的请求。)

通常情况下, 应该用前面所提方法替代。

[上一小节: 网络地址转换] [总目录] [下一小节: 定义规则的捷径]


[back] www@openbsd.org
$OpenBSD: rdr.html, v 1.27 2007/05/06 18:59:54 nick Exp $