14.11. 在 IPsec 上的 VPN

撰写者 Nik Clayton.

使用 FreeBSD 网关在两个被 Internet 分开的网络之间架设 VPN。

14.11.1. 理解 IPsec

撰写者 Hiten M. Pandya.

这一节将指导您完成架设 IPsec, 并在一个包含了 FreeBSD 和 Microsoft® Windows® 2000/XP 机器的网络中使用它来进行安全的通讯的全过程。 为了配置 IPsec, 您应当熟悉如何编译一个定制的内核的一些概念 (参见 Chapter 8)。

IPsec 是一种建立在 Internet 协议 (IP) 层之上的协议。 它能够让两个或更多主机以安全的方式来通讯 (并因此而得名)。 FreeBSD IPsec “网络协议栈” 基于 KAME 的实现, 它支持两种协议族, IPv4 和 IPv6。

Note: FreeBSD 5.X 包括了采用 “硬件加速的” IPsec 协议栈, 也称作 “Fast IPsec”, 它来自 OpenBSD。 它能够通过 crypto(4) 子系统来利用加密硬件 (只要可能) 优化 IPSec 的性能。 这个子系统是新的, 暂时还不支持 KAME 版本的 IPsec 的全部功能。 此外, 为了启用硬件加速的 IPsec, 必须把下面的选项加入到内核配置中:

options      FAST_IPSEC  # new IPsec (cannot define w/ IPSEC)
       

请注意, 目前还不能把 “Fast IPsec” 子系统同 KAME 的 IPsec 实现同时混用。 请参考 fast_ipsec(4) 联机手册以了解进一步的信息。

IPsec 包括了两个子协议:

ESPAH 根据环境的不同, 和以分别或一同使用。

IPsec 既可以用来直接加密主机之间的网络通讯 (也就是 传输模式); 也可以用来在两个子网之间建造 “虚拟隧道” 用于两个网络之间的安全通讯 (也就是 隧道模式)。 后一种更多的被称为是 虚拟专用网 (VPN)ipsec(4) 联机手册提供了关于 FreeBSD 中 IPsec 子系统的详细信息。

为了把 IPsec 支持放进内核, 需要在配置文件中加入下面的选项:

options   IPSEC        #IP security
options   IPSEC_ESP    #IP security (crypto; define w/ IPSEC)
     

如果需要 IPsec 的调试支持, 还需要增加:

options   IPSEC_DEBUG  #debug for IP security
     

14.11.2. 问题

由于对如何建立 VPN 并不存在标准, 因此 VPN 可以采用许多种不同的技术来实现, 每种技术都有其长处和弱点。 这篇文章讲展现一个具体的应用情景, 并为它设计了适合的 VPN。

14.11.3. 情况: 两个网络都接入了 Internet, 希望像一个网络那样工作

前提如下:

如果您发现您正打算连接两个内网使用同样私有 IP 地址范围的网络 (例如它们都使用 192.168.1.x), 则建议改掉其中一个网络的地址。

网络的拓扑结构如下:

请注意两个公网 IP 地址。 在这篇文章的其余部分我将用这些字母来表示它们。 在文章中看到这些字母的时候, 请把它们换成自己的公网 IP 地址。 另外, 在内部, 两个网关都是使用的 .1 的 IP地址, 而两个网络使用了不同的私有 IP 地址 (相应地, 192.168.1.x192.168.2.x)。 所有私有网络上的机器都被配置为使用 .1 这台机器作为它们的网关。

我们希望, 从网络的观点看, 每一个网络上的机器都应该能够像在直接连接到同一路由器上一样看到对方网络上的机器 -- 尽管可能比路由器略慢一些, 并且有时会有丢包的现象。

这意味着 (举例来说), 主机 192.168.1.20 应该能够运行

ping 192.168.2.34

并且这能够透明地工作。 Windows 机器应该能够看到其他网络上的机器, 浏览文件共享, 等等, 就像在本地网络上一样。

而且这些事情必须是安全的, 也就是说两个网络之间的通讯必须加密。

在两个网络之间建立 VPN 可以分为几步。 这些步骤包括:

  1. 在两个网络之间, 通过 Internet 建立一个 “虚拟的” 网络连接。 使用类似 ping(8) 这样的工作来验证它是否正常工作。

  2. 在两个网络之间应用安全策略以保证它们之间的通讯被透明地加密盒解密。 可以使用 tcpdump(1) 或类似的工具来验证这一点。

  3. 在 FreeBSD 网关上配置其他软件, 让 Windows 机器能够通过 VPN 看到另一个网络中的机器。

14.11.3.1. 步骤 1: 建立并测试 “虚拟的” 网络连接

假设您目前已经登录到了网络 #1 的网关机上 (其公网 IP 地址是 A.B.C.D, 私网 IP 地址是 192.168.1.1), 则您可以执行 ping 192.168.2.1, 这是公网 IP 为 W.X.Y.Z 的私网地址。 需要做什么实现上述功能呢?

  1. 作为网关的机器需要知道如何能够到达 192.168.2.1。 换言之, 它需要一条通往 192.168.2.1 的路由。

  2. 私网 IP 地址, 例如 192.168.x 这样的地址是不应在 Internet 上面大量出现的。 于此相反, 发送到 192.168.2.1 的数据包将会封装到另外的包中。 这样的包对外展现的应该是来自 A.B.C.D, 并被发到 W.X.Y.Z 去。 这个过程称为 封装

  3. 一旦包到达了 W.X.Y.Z 就需要对其 “拆封”, 并传递给 192.168.2.1

可以把上述过程理解为在两个网络间建立了一个 “隧道”。 两个 “隧道口” 是 IP 地址 A.B.C.DW.X.Y.Z, 而隧道必须被告知哪些私有地址可以自由地在其中通过。 隧道被用来在公共的 Internet 上传递私有的 IP 数据。

在 FreeBSD 上, 隧道可以通过一般的网络接口, 或 gif 来建立。 您也许已经猜到了, 每一台网关机的 gif 接口需要配置四个 IP 地址; 两个是公网 IP 地址, 另两个则是私网 IP 地址。

对于 gif 设备的支持必须在两台网关机上编译进内核。 可以通过添加下面的设置:

pseudo-device gif

到两边的内核配置文件中, 并重新编译、 安装和启动它们。

配置隧道可以分为两步来完成。 首先隧道必须被告知外部的 (或公网的) IP 地址, 可以通过 gifconfig(8) 来完成这步。 私网 IP 地址则必须使用 ifconfig(8) 来配置。

Note: 在 FreeBSD 5.X 中, gifconfig(8) 的功能已经并入 ifconfig(8)

在网络 #1 的网关机上可以通过下面的两个命令来配置隧道。

gifconfig gif0 A.B.C.D W.X.Y.Z
ifconfig gif0 inet 192.168.1.1 192.168.2.1 netmask 0xffffffff
     

在另一边也需要做类似的设置, 只是 IP 地址的顺序是反过来的。

gifconfig gif0 W.X.Y.Z A.B.C.D
ifconfig gif0 inet 192.168.2.1 192.168.1.1 netmask 0xffffffff
     

随后执行:

gifconfig gif0

可以查看当前的配置情况。 例如, 在网络 #1 的网关上您应该能够看到:

# gifconfig gif0
gif0: flags=8011<UP,POINTTOPOINT,MULTICAST> mtu 1280
inet 192.168.1.1 --> 192.168.2.1 netmask 0xffffffff
physical address inet A.B.C.D --> W.X.Y.Z
     

如您所见, 虽然到已经在物理地址 A.B.C.DW.X.Y.Z 之间建立起来, 而允许通过隧道的地址则是 192.168.1.1192.168.2.1 这个范围。

这同时会在两边机器的路由表中加入一项, 可以通过 netstat -rn 来观察。 来自网络 #1的网关机的输出如下。

# netstat -rn
Routing tables

Internet:
Destination      Gateway       Flags    Refs    Use    Netif  Expire
...
192.168.2.1      192.168.1.1   UH        0        0    gif0
...
     

正如 “Flags” 的值所显示的那样, 这是一个主机路由, 这意味着每一个网关都知道如何到达另一端的网关, 但它们现在还不知道如何到达对方的网络。 我们接下来立刻解决这个问题。

您很可能在两台机器上都在运行防火墙。 这需要作一些变动, 以便适应 VPN 的需要。 一般来说会希望两个网络相互传递数据包, 或者通过防火墙来隔离两边的危险。

如果您将防火墙配置为允许两边的网络传输通过, 则测试工作会简单不少。 随后您可以随时将限制变得更严格一些。 假如您在网关上使用 ipfw(8) 则下面的命令

ipfw add 1 allow ip from any to any via gif0

将允许两端点的 VPN 数据通过, 而不影响其他防火墙策略。 很显然, 您需要在两个网关上都执行上述命令。

现在已经可以让两台机器相互 ping 了。 在 192.168.1.1 您应该能够正常执行

ping 192.168.2.1

并得到回应。 对于另一台网关来说也是一样。

然而, 到目前为止仍然还无法连上另一网络上的内部主机。 原因是路由 -- 尽管网关机知道如何到达对方那里, 但它们都不知道如何到达对方后面的网络。

要解决这个问题, 就必须在两边都添加一条静态路由。 可以在第一台网关上执行:

route add 192.168.2.0 192.168.2.1 netmask 0xffffff00
     

这相当于是说 “为了到达 192.168.2.0 子网的机器, 需要把包发给 192.168.2.1”。 您需要在另一个网关上也执行类似的命令, 但使用 192.168.1.x 的地址。

来自一个网络上的 IP 访问现在能够抵达对面的网络了。

在两个网络之间建立 VPN 的过程已经完成了三分之二, 它现在已经是 “虚拟的” “网络”, 然而它还不够专用。 您可以使用 ping(8)tcpdump(1) 来进行测试, 并记录两边收发的数据包

tcpdump dst host 192.168.2.1

接下来登录到本机的另一个会话

ping 192.168.2.1

您将在输出中发现

16:10:24.018080 192.168.1.1 > 192.168.2.1: icmp: echo request
16:10:24.018109 192.168.1.1 > 192.168.2.1: icmp: echo reply
16:10:25.018814 192.168.1.1 > 192.168.2.1: icmp: echo request
16:10:25.018847 192.168.1.1 > 192.168.2.1: icmp: echo reply
16:10:26.028896 192.168.1.1 > 192.168.2.1: icmp: echo request
16:10:26.029112 192.168.1.1 > 192.168.2.1: icmp: echo reply
     

如您所见, ICMP 消息在收发的过程中都没有加密。 如果使用了 -s 参数来运行 tcpdump(1), 甚至可以得到包中的更多信息以及其中的数据。

很明显这是不能接受的。 下一节将讨论如何让两个网络之间的连接更安全, 这件事是通过对通讯实施加密来完成的。

小结:

  • 使用 “pseudo-device gif” 配置两边的内核。

  • 编辑网关 #1 上的 /etc/rc.conf 并将下面的行添加进去 (根据需要改 IP )。

    gifconfig_gif0="A.B.C.D W.X.Y.Z"
    ifconfig_gif0="inet 192.168.1.1 192.168.2.1 netmask 0xffffffff"
    static_routes="vpn"
    route_vpn="192.168.2.0 192.168.2.1 netmask 0xffffff00"
             
    
  • 在两台机器上编辑防火墙脚本 (/etc/rc.firewall, 或类似的名字) 在其中加入

    ipfw add 1 allow ip from any to any via gif0
    
  • 在网络 #2 的网关机上也对 /etc/rc.conf 做同样的修改, 注意把 IP 地址倒过来。

14.11.3.2. 步骤 2: 对连接实施安全加固

为了加密连接通讯将用到 IPsec。 IPsec 提供了一种机制, 使得两台主机协商一个加密密钥, 并使用它加密之间的通讯。

在配置时有两个地方需要考虑。

  1. 必须有能够让两台主机协商所采用的加密方式的机制。 一旦双方确认了这机制, 则称他们之间建立了 “安全关联”。

  2. 必须采用某种机制来指定哪些通讯需要加密。 很明显地, 通常并不需要所有的发出数据都被加密 -- 一般只需要加密在 VPN 上传输的那些数据。 这类决定哪些数据被加密的规则被称为 “安全策略”。

安全关联和安全策略都是由内核来维护的, 并可以通过用户态的程序来修改。 在能够这样做之前, 首先需要配置内核来让它支持 IPsec 和安全载荷封装 (ESP) 协议。 配置下面的内核选项

options IPSEC
options IPSEC_ESP
      

然后重新编译、 安装最后重新启动新的内核。 在继续进行设置之前, 您需要在两台网关上都进行同样的设置。

在建立安全关联时有两种选择。 一种方法是完全手工地在两台主机之间选择加密算法、 密钥等等, 另一种方法是使用实现了 Internet 密钥交换协议 (IKE) 的服务程序来帮您完成这些任务。

我们推荐后者。 不说别的, 它配置起来要容易得多。

setkey(8) 可以编辑和显示安全策略。 打个比方, setkey 之于内核的安全策略表, 就相当于 route(8) 之于内核中的路由表。 setkey 还可以显示当前的安全关联, 这一点和 netstat -r 类似。

FreeBSD 上用于管理安全关联的可供选择的服务程序有很多。 这篇文章将介绍如何使用其中的一种, racoon。 racoon 可以从 FreeBSD ports collection 安装, 它位于 security/ 分类下。 安装方法与其他软件无异。

racoon 需要在两台网关机上都运行。 需要配置 VPN 另一端的 IP, 以及一个密钥 (这个密钥可以任意选择, 但两个网关上的密钥必须一致)。

两端的服务程序将相互通讯, 并确认它们各自的身份 (使用刚刚配置的密钥) 然后服务程序将生成一个新的密钥, 并用它来加密 VPN 上的数据通讯。 它们定期地改变密钥, 因此即使供给者破解了一个密钥 (虽然这在理论上并不十分可行) 他也得不到什么 -- 破解密钥的时候, 已经产生一组新的密钥了。

racoon 的配置保存在 ${PREFIX}/etc/racoon 中。 在那里应该能够找到一个配置文件, 不需要修改太多的设置。 raccon 配置的另一部分, 也就是需要修改的内容, 是 “预先配置的共享密钥”。

默认的 racoon 配置文件应该可以在 ${PREFIX}/etc/racoon/psk.txt 这个文件中找到。 需要强调的是, 这个密钥 并非 用于加密 VPN 连接的密钥, 他们只是密钥管理服务程序用以信任对方的一种凭据。

psk.txt 包含了需要打交道的每一个远程站点。 在本例中一共有两个站点, 每一个 psk.txt 都只有一行 (因为每个 VPN 接入点都只和一个端点连接)。

在网关机 #1 上应该是:

W.X.Y.Z            secret

这包括了远程站点的 公网 IP 地址, 空格, 以及提供秘密的字符串。 很明显不应使用 “secret” 作为实际的密钥 -- 通常的口令选择策略应该应用到这里。

在网关 #2 上对应的配置是

A.B.C.D            secret

也就是说, 对面端的公网 IP 地址, 以及同样的密钥。 psk.txt 的权限必须是 0600 (也就是说, 只有 root 能够读写) 否则 racoon 将不能运行。

两边的机器上都必须执行 racoon。 另外, 还需要增加一些防火墙规则来允许 IKE 通讯通过, 它是通过 UDP 在 ISAKMP (Internet 安全关联密钥管理协议) 端口上运行的协议。 再次强调, 这个规则应该在规则集尽可能早的位置出现。

ipfw add 1 allow udp from A.B.C.D to W.X.Y.Z isakmp
ipfw add 1 allow udp from W.X.Y.Z to A.B.C.D isakmp
      

一旦 racoon 开始运行, 就可以开始测试让网关进行相互的 ping 了。 此时连接还没有进行加密, 但 racoon 将在两个主机之间建立安全关联 -- 这可能需要一段时间, 对您来说, 具体的现象则是在 ping 命令开始响应之前会有短暂的延迟。

一旦安全关联建立之后, 就可以使用 setkey(8); 来查看它了。 在两边的网关上执行

setkey -D

就可以看到安全关联的相关信息了。

现在只完成了一半的工作。 另一半是设置安全策略。

想要完成一个有判断力的安全策略, 首先要看我们已经完成的步骤。 接下来的讨论针对连接的两端。

您所发出的每一个 IP 包都包括一个包头, 其内容是和这个包有关的描述性数据。 包头包括了包的来源和目的的 IP 地址。 正如我们所了解的那样, 私有 IP 地址, 例如 192.168.x.y 这样的地址范围, 不应该出现在 Internet 的公网上。 于此相反, 他们必须首先封装到别的包中。 包的来源或目的如果是私有 IP 地址, 则必须替换成公网 IP 地址。

因此如果发出的包类似下面这样:

随后它将被封装进另一个包中, 像下面这样:

封装过程是在 gif 设备上完成的。 如上图所示, 包现在有了外部的实际 IP 地址, 而原始的包则被封装到里面作为数据。 这个包将通过 Internet 传递。

很明显地, 我们希望 VPN 之间的通讯是加密的。 用语言来描述大致是:

“如果包从 A.B.C.D 发出且其目的地是 W.X.Y.Z, 则通过必要的安全关联进行加密。”

“如果包来自 W.X.Y.Z 且其目的地是 A.B.C.D, 则通过必要的安全关联进行解密。”

这已经很接近了, 但还不够正确。 如果这么做的话, 所有来自和发到 W.X.Y.Z 的包, 无论是否属于 VPN 通讯都会被加密。 这可能并不是您所希望的, 因此正确的安全策略应该是

“如果包从 A.B.C.D 发出, 且封装了其他的包, 其目的地是 W.X.Y.Z, 则通过必要的安全关联进行加密。”

“如果包来自 W.X.Y.Z, 且封装了其他的包, 其目的地是 A.B.C.D, 则通过必要的安全关联进行解密。”

一个很小, 但却必要的改动。

安全策略也是通过 setkey(8) 设置的。 setkey(8) 提供了一种用于配置策略的语言。 可以直接在 stdin 上输入策略, 或通过 -f 选项来指定一个包含配置命令的文件。

网关 #1 上的配置 (其 IP 地址是 A.B.C.D) 强制将所有到 W.X.Y.Z 的通讯进行加密的配置是:

spdadd A.B.C.D/32 W.X.Y.Z/32 ipencap -P out ipsec esp/tunnel/A.B.C.D-W.X.Y.Z/require;
      

把这些命令放到一个文件 (例如 /etc/ipsec.conf) 然后执行

# setkey -f /etc/ipsec.conf

spdadd 会告诉 setkey(8) 我们希望把规则加入到安全策略数据库中。 命令的其它部分指定了什么样的包能够匹配这规则。 A.B.C.D/32W.X.Y.Z/32 是用于指定规则能够匹配的网络或主机的 IP 地址和掩码。 本例中, 希望应用到两个主机之间的通讯上。 ipencap 则告诉内核这规则只应被用于封装其他包的那些数据包。 -P out 表示策略是针对发出的包的, 而 ipsec 则表示需要对数据包进行加密。

第二行指定了如何加密。 esp 是将要使用的协议, 而 tunnel 则表示包应该进一步封装进一个 IPsec 包里面。 反复使用 A.B.C.DW.X.Y.Z 用来选择所用的安全关联 而最后的 require 则强制所有匹配这规则的包都被加密。

上面的规则只匹配了发出的包。 接下来需要配置类似的匹配进入包的规则。

spdadd W.X.Y.Z/32 A.B.C.D/32 ipencap -P in ipsec esp/tunnel/W.X.Y.Z-A.B.C.D/require;

请注意本例中 in 代替了 out 并且 IP 地址的顺序也相反。

在另一个网关上 (其公网 IP 地址是 W.X.Y.Z) 也需要类似的规则。

spdadd W.X.Y.Z/32 A.B.C.D/32 ipencap -P out ipsec esp/tunnel/W.X.Y.Z-A.B.C.D/require;
spdadd A.B.C.D/32 W.X.Y.Z/32 ipencap -P in ipsec esp/tunnel/A.B.C.D-W.X.Y.Z/require;

最后是添加允许 ESP 和 IPENCAP 包进出的防火墙规则。 这些规则需要在两边分别设置。

ipfw add 1 allow esp from A.B.C.D to W.X.Y.Z
ipfw add 1 allow esp from W.X.Y.Z to A.B.C.D
ipfw add 1 allow ipencap from A.B.C.D to W.X.Y.Z
ipfw add 1 allow ipencap from W.X.Y.Z to A.B.C.D
      

由于规则的对称性, 因此可以在两台网关上使用同样的规则。

发出的包如下图所示:

当 VPN 数据被远端接到时, 它将首先被解密 (使用 racoon 协商得到的安全关联)。 然后它们将进入 gif 接口, 并在那里展开第二层, 直到只剩下最里层的包, 并将其转发到内网上。

可以通过与之前同样的 ping(8) 命令来测试安全性。 首先登录到 A.B.C.D 网关上并执行:

tcpdump dst host 192.168.2.1

在同一主机上登录另一会话, 执行

ping 192.168.2.1

此时的输出应该是:

XXX tcpdump output

如您看到的, tcpdump(1) 给出的将是 ESP 包。 假如您想查看它们的内容可以使用 -s option 选项, 您将 (显然地) 看到一些乱码, 因为传输过程实施了加密。

祝贺您。 您已经完成了两个远程站点之间的 VPN 的架设工作。

小结

  • 将两边的内核配置加入:

    options IPSEC
    options IPSEC_ESP
             
    
  • 安装 security/racoon。 编辑两台网关上的 ${PREFIX}/etc/racoon/psk.txt 并添加远程主机的 IP 和共享的密钥。 文件的权限应该是 0600。

  • 将下面的设置加入两台主机的 /etc/rc.conf 中:

    ipsec_enable="YES"
    ipsec_file="/etc/ipsec.conf"
             
    
  • 在两个网关上都建立 /etc/ipsec.conf 并添加必要的 spdadd。 在网关 #1 上是:

    spdadd A.B.C.D/32 W.X.Y.Z/32 ipencap -P out ipsec
      esp/tunnel/A.B.C.D-W.X.Y.Z/require;
    spdadd W.X.Y.Z/32 A.B.C.D/32 ipencap -P in ipsec
      esp/tunnel/W.X.Y.Z-A.B.C.D/require;
    

    在网关 #2 上则是:

    spdadd W.X.Y.Z/32 A.B.C.D/32 ipencap -P out ipsec
      esp/tunnel/W.X.Y.Z-A.B.C.D/require;
    spdadd A.B.C.D/32 W.X.Y.Z/32 ipencap -P in ipsec
      esp/tunnel/A.B.C.D-W.X.Y.Z/require;
    
  • 添加防火墙规则以允许 IKE, ESP, 和 IPENCAP 通讯能够到达各自的主机:

    ipfw add 1 allow udp from A.B.C.D to W.X.Y.Z isakmp
    ipfw add 1 allow udp from W.X.Y.Z to A.B.C.D isakmp
    ipfw add 1 allow esp from A.B.C.D to W.X.Y.Z
    ipfw add 1 allow esp from W.X.Y.Z to A.B.C.D
    ipfw add 1 allow ipencap from A.B.C.D to W.X.Y.Z
    ipfw add 1 allow ipencap from W.X.Y.Z to A.B.C.D
             
    

前面的两部应该足以让 VPN 运转起来了。 两个网络上的机器都应该能通过 IP 来访问对方, 而所有的通讯都被自动地进行加密。