VPC 中 NAT 的那点事

阅读数:58 2019 年 11 月 20 日 08:00

VPC中NAT的那点事
### NAT 就在那里
复制代码
下图 是 EC2 实例通过 IGW(Internet 网关) 接入到 Internet 的示意图。熟悉 AWS 的读者会知道,这里 EC2 实例和 Internet 通信的两个方向上,实际上发生了如下的转换:
* 从 EC2 实例发出的前往 Internet 的 IP 包,其源地址 10.0.0.10 在经过 IGW 时,会被转换为与实例关联的公网地址 54.232.0.1
* 从 Internet 发给 54.232.0.1 的 IP 包,经过 IGW 时其目的地址会转换为 ENI 对应的内网地址 10.0.0.10 并被送到 EC2 实例;
可以看到,这里 Internet 网关就是起到实例的内网地址和公网地址一对一的基本 NAT(网络地址转换)的功能。
!
相比于没有 NAT 的场景,大部分的应用、软件不需要任何改变就能在基本 NAT 的场景下继续工作,例如基于 HTTP 协议的 Web 应用等;但是对于某些应用,例如 FTP、VoIP 等,网络地址转换会给应用带来一些意想不到的挑战。今天我们以历史悠久的 FTP 协议为例,来和大家探讨一下 NAT 给 FTP 这样的应用带来什么样的挑战,以及 FTP 应用和协议又是如何演进去适应它。
### 被动模式下的 FTP
我们重温一下 FTP 的工作过程。客户端连接服务端 TCP 21 端口建立命令通道后,输入用户名密码完成登录;随后的每一次数据传输都需要另外建立数据通道进行; 如果数据通道由客户端发起,服务端接受,我们称之为被动模式;反之,如果数据通道由服务端发起,客户端接受,则称之为主动模式。
为简化讨论,我们以被动模式为例。
** 同一个私网内 **
我们在 EC2 实例 10.0.0.10 上搭建了一台 FTP 服务器,它监听在 21 端口。现在我们从同一个 VPC 里的另外一台 EC2 上运行 FTP 客户端;那么整个过程会很顺利。
!
** 从 Internet 访问 **
现在我们从位于 Internet 某处的一台 PC 上运行 FTP 客户端。
!
这里连接超时的原因显而易见,FTP 服务端发送给客户端的 IP 地址是服务端的私有地址。位于 Internet 上的客户端无法建立与位于 VPC 内部的私有地址 10.0.0.10 直接通讯。
解决这个问题有多种方法,我们由简单到复杂分别叙述。
### 增强协议适配 NAT
FTP 协议针对 NAT 和其他因素,对协议进行了增强,提供了增强版的被动模式 EPSV 命令 [1]。
下面的例子里,服务端不再显式指定 IP 地址,只提供数据通道的端口号。客户端默认与控制通道相同的 IP 地址建立数据通道。
!
可以看到,解决方案很优雅。实际上如果你在阅读本文的时候,绝大部分 FTP 服务端和客户端都已经实现了 EPSV,而且优先使用它,所以 FTP 应用目前在 EC2 上是可以称之为开箱即用的。当然这需要客户端和服务端都支持增强的协议才能达成;如果我们不修改协议,能否解决这个问题呢。
### 放开那协议!我来!
有些时候,修改协议和实现需要多方协调和很长的时间才能完成。在 RFC2428 标准化之前,一些 FTP 实现就已经通过修改实现来适配基本 NAT,而非修改协议。
以 vsftpd 为例,它允许通过配置文件 vsftpd.conf 中的配置项 pasv_address=54.232.0.1 告知服务端,在 PASV 被动模式下,应当指示客户端连接到配置项指定的 IP(而不是服务端的私有 IP)来适配基本 NAT。
其他的一些常见应用例如 VoIP 类应用,也有类似的机制去适配基本 NAT;例如引入 STUN/TURN/ICE 等方式[2]适配各种更加复杂的 NAT 穿越场景;但是针对部署在 EC2 上的基本 NAT 环境,也有通过实现上的简单调整,例如开源 VoIP 应用 Asterisk 就支持通过在配置文件 /etc/asterisk/sip.conf 里指定本机的公网地址和本地网段的方式来适配基本 NAT。
nat=yes
externaddr=54.223.0.1
localnet=10.0.0.0/16
### 协议和实现我都不想动!
作为一枚任性的读者,如果您既不想动协议也不想动实现,这里笔者给读者介绍一种剑走偏锋的方式,读者若有兴趣,可以轻松愉快的在 AWS 上试一试,看看能否解决你的应用适配基本 NAT。下面是一段 shell 脚本,当然是运行在 Linux 操作系统上的。
_\#从 EC2 实例元数据服务获取本实例的公网 IP(如有)、私网 IP_
_public_ipv4=`curl -s http://169.254.169.254/latest/meta-data/public-ipv4`_
_local_ipv4=`curl -s http://169.254.169.254/latest/meta-data/local-ipv4`_
_\#配置私网地址段,这里应为 EC2 实例所在 VPC 的地址范围 _
_local_net=10.0.0.0/16_
_if [ “x${public_ipv4}” == “x” ]_
_then_
_echo “No public IPv4 address available for this instance, abort.”_
_exit 1_
_else_
_\#如果 EC2 实例的公网 IP 不为空, 则将该公网地址添加到 eth0 上 _
_ip address add ${public_ipv4}/32 dev eth0_
_\#本地接受的连接,如果来源不是本 VPC,那么将 IP 包的目的地址改写为公网 IP_
_iptables -t nat -A PREROUTING ! -s ${local_net} -d ${local_ipv4} -i eth0 -j DNAT –to ${public_ipv4}_
_\#本地发起的连接,如果出方向流量的源 IP 地址是公网地址,那么需要改写为私网 IP_
_iptables -t nat -A POSTROUTING -s ${public_ipv4} -o eth0 -j SNAT –to ${local_ipv4}_
_fi_
正常情况下,脚本执行完毕后,可以通过如下方式验证效果。
### 首先检查本实例的公网 IP 是否已经正确配置到 eth0 上。
~ # ip addr show dev eth0
eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc pfifo_fast state UP group default qlen 1000
link/ether 02:c7:6b:9b:d2:b6 brd ff:ff:ff:ff:ff:ff
inet 10.0.0.10/24 brd 10.0.0.255 scope global eth0
valid_lft forever preferred_lft forever
inet 54.232.0.1/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::c7:6bff:fe9b:d2b6/64 scope link
valid_lft forever preferred_lft forever
可以看到,公有 IP 地址 (54.232.0.1/32) 已经成功添加到 eth0 接口。
### 然后检查 iptables 的 NAT 规则是否正确配置
~ # iptables -t nat -nvL
Chain PREROUTING (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 DNAT all — eth0 * ! 10.0.0.0/16 10.0.0.10 to:54.223.74.106
Chain INPUT (policy ACCEPT 1 packets, 64 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 3 packets, 222 bytes)
pkts bytes target prot opt in out source destination
Chain POSTROUTING (policy ACCEPT 3 packets, 222 bytes)
pkts bytes target prot opt in out source destination
0 0 SNAT all — * eth0 54.223.74.106 0.0.0.0/0 to:10.0.0.1
从上面可以看到传入、传出数据包的数量以及 IP 地址在传入前,传出后的地址改写情况。
### 最后分别从 VPC 内部和 Internet 连接到服务,验证结果
~ $ ss -nt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 72 54.223.74.106:21 119.xx.x.xx:52425
ESTAB 0 1272 54.223.74.106:12081 119.xx.x.xx:23710
ESTAB 0 72 10.0.0.10:21 10.xx.x.xx:48361
ESTAB 0 1272 10.0.0.10:12090 10.xx.x.xx:32115
笔者在没有修改任何 vsftpd 的配置文件的前提下,通过上述脚本的运行和配置,同一个 VPC 内部的客户端和 Internet 客户端都能完成 FTP 被动模式的文件传输全流程。
值得一提的是,本方法仅供参考,不建议在生产环境中大规模使用,推荐的解决方案请参考前文关于协议适配和实现适配。
[1] FTP Extensions for IPv6 and NATs https://tools.ietf.org/html/rfc2428
[2] NAT Traversal Practices for Client-Server https://tools.ietf.org/html/rfc6314
** 作者介绍:**
!
丁成银
AWS 解决方案架构师,获得 AWS 解决方案架构师专业级认证和 DevOps 工程师专业级认证。负责基于 AWS 的云计算方案架构的咨询和设计,同时致力于 AWS 云 服务在国内的应用和推广,在数字媒体、电信、互联网和游戏、企业混合 IT 等方面有着丰富的实践和设计经验。在加入 AWS 之前,历任数字媒体娱乐系统工程 师、宽带业务架构师、云解决方案架构师,负责数字媒体娱乐系统、云计算解决方案等服务的咨询和架构设计工作。

欲了解 AWS 的更多信息,请访问【AWS 技术专区】

评论

发布