
下图 是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之前,历任数字媒体娱乐系统工程 师、宽带业务架构师、云解决方案架构师,负责数字媒体娱乐系统、云计算解决方案等服务的咨询和架构设计工作。
更多内容推荐
设计千万级学生管理系统的考试试卷存储方案
设计千万级学生管理系统的考试试卷存储方案
2022-06-09
Wally-DR6000/IPQ6000/802.11ax/ 2x2 2.4GHz&5GHz /1.7Gbps
IPQ6000 802.11ax 2x2 2.4GHz&5GHz 802.11ax 2x2 2.4G 2x2 5G 1.7Gbps
2022-05-10
5000 页?一份字节跳动 Java 面试全解手册发布!瞬间登顶各大搜索栏
5000页?一份字节跳动Java面试全解手册发布!瞬间登顶各大搜索栏
2022-09-09
Linux 之 ss 命令
【Linux常用命令速查手册】关注【入门小站】,后台回复 「1001」 自取。
2022-03-10
5. 容器网络
2023-09-26
数组目标值 target 两个整数,并返回它们的数组下标
给定一个整数数组nums和一个整数目标值target,请你在该数组中找出和为目标值target的那两个整数,并返回它们的数组下标。
2022-06-10
Redis 中的数据类型以及常用方法
Redis五种数据类型分别是string(字符串),hash(哈希),list(列表),set(集合)及sort set (有序集合)
2022-10-03
3. 网络编程:客户端详解、简单 TCP 服务器
2023-09-27
使用 goofys 挂载 S3 bucket 为文件系统
goofys挂载s3 bucket为文件系统
2022-02-23
Talent Plan TinyKV Project1 StandaloneKV
复制代码
2022-05-13
DevStream 成为 CNCF Sandbox 项目啦!- 锣鼓喧天、鞭炮齐鸣、红旗招展、忘词了。
开局两张图,内容全靠“编”
2022-06-16
3、使用 Dubbo 发布分布式服务
2023-09-26
第一模块作业
模块一作业
2022-09-27
12. 集群:gRPC 中如何实现广播
2023-09-27
4、NIO 案例:网络聊天室 v1.0
2023-09-27
5. DNS 原理和实践
2023-09-26
API 安全设计 5A 原则
5A原则是由5个首字母为A的单词构成的,分别是Authentication(身份认证)、Authorization(授权)、Access Control(访问控制)、Auditable(可审计性)、Asset Protection(资产保护),其含义是当安全设计人员在做安全设计时,需要从这5个方面考量安全设计
2022-11-21
Amazon Braket 与量子计算
KY1,Yankuan Pan2,Bertran Shao3,Zoey Deng41.Amazon HERO;2.项目架构师;3.开发者生态负责人;4.开发者关系
2022-11-24
使用 QuTrunk+Amazon ParallelCluster3 进行并行计算
1.丘秉宜;2.邵伟;3.黄文;4.郭梦杰;5.刘利;6.刘波
2022-12-27
推荐阅读
软件测试 / 测试开发丨函数式编程学习笔记
2023-07-04
新功能 – Cloud WAN:托管 WAN 服务
2023-07-27
1、Nginx 概述及 web server 技术选型
2023-09-28
SQL 数据操作技巧:SELECT INTO、INSERT INTO SELECT 和 CASE 语句详解
2023-11-30
4、一主多从,互为主从
2023-09-27
16|云原生部署任务实践:让你成为更好的 DevOps 工程师
2023-10-25
Amazon EC2 Hpc7g 实例现已在更多区域推出
2023-11-06
电子书

大厂实战PPT下载
换一换 
张柳青 | 百度 资深研发工程师
陈旸 | 清华大学计算机博士、阿里云MVP 《21日天 AIGC 行动营》专栏作者
王斯杰 | Cocos 引擎 Cocos Creator 前端负责人
评论