NAT探珠
概要
NAT的全称是 Network Address Translation, 其实看英文名,就知道它的大概意思:网络地址转换。
严格来说,
- 这个Address,包括了IP地址和端口。
- 而这个Translation,则包括来来源地址和目的地址,以及来源端口和目的端口这4者之间的转换
那么NAT规则其实定义的就是这4者的映射关系,告诉路由器应该怎么去转换(Translation)
那么什么时候会用到NAT技术呢,答案是,从一个网络到另外一个网络,有一些文章在写NAT的时候,常讲的是外网(互联网)到私网(私域子网)的通信的时候,其实这只是其中一种场景,实际上,只要是两个网络间需要通信,都可以用NAT来做。
下面这个图讲的就是互联网和私网这个场景的NAT
\ | / . /
+---------------+ WAN . +-----------------+/
|Regional Router|----------------------|Stub Router w/NAT|---
+---------------+ . +-----------------+\
. | \
. | LAN
. ---------------
Stub border
Figure 1: A typical NAT operation scenario
这个图是从NAT的规范rfc.2663 复制过来的,其中WAN就是广域网,LAN是一个局域网,LAN通过路由器连接到WAN,那么要在WAN和LAN之间通信,那就通过的在路由器里面做数据包的NAT转换。
NAT的地址转换技术有很多种类,但这些技术一般都具备下面这3个特征:
- 透明地址分配,就是定义外网和内网的地址的映射关系
- 通过地址转换进行透明路由(这里的路由指的是数据包的转发,而不是指交换路由信息)
- ICMP的错误包的payload转换
那么透明地址分配的规则是这个这些技术里面的关键点,这种地址分配又分为:
- 静态地址分配:这是一种one-to-one的映射关系,这种映射关系明确、简单,不需要在地址转换翻译的过程中管理会话,发出去和回复回来的数据包都有明确的关系。
- 动态地址分配:非one-to-one的映射关系,一个或者少量的外网地址分配个一个私网的一堆主机,这种情况,则需要通过会话来明确这个映射关系,那么就需要管理会话,这样才知道回复的数据包对应的是哪台主机发出去的(通过会话来查找映射关系,映射关系会话开始的时候就确立了)
NAT的种类
NAT根据他的通信种类,又分为4类:
- 传统NAT或出站NAT: 会话从私有网络发起,是单向的,出站方向的这种会话
- 双向NAT:会话可以从无论是公有网络还是私有网络发起都行,是双向的
- 两次NAT:两次NAT是NAT的一个变种,它在转换过程中,把数据包里面源地址和目标地址都给改了,这种情况是因为两个网络的地址有冲突,比如两个网络都是同一个网段,而且主机的地址都一样
- 多宿主NAT:解决NAT路由的单点故障问题而衍生出来的一种NAT技术
这么多NAT,对我们最经常需要用到的是传统NAT(出站NAT),下面我们重点讲这种NAT技术
传统NAT(出站NAT)
而传统的NAT又分为:
- Basic NAT,需要一个外部的IP池拿来和做转换,不需要端口参与,要求对每一个当前连接都要对应一个公网IP地址,这种NAT在转换的阶段,指修改IP、ip头的checksum以及一切更高级别和IP相关的checksum(如TCP、UDP、ICMP的header的checksum)的值(使数据包看起来合法),一般来讲你有足够多的外网ip可以用来对应你的内网主机,那就用这种,
- NAPT, NAPT是Net Address Port Translation, 需要端口参与到映射关系里,为什么需要端口参数呢,因为你的外网ip数量不足以对应到你内网的主机数量,所以需要共享外网IP.
NAPT的技术有根据地址转换过程阶段分为
- DNAT,数据包发出去的时候,修改的是目标IP地址,所以叫做Destination NAT,简称DNAT
- SNAT, 数据包发出去的时候,修改的是源IP地址,所以叫做Source NAT,简称SNAT(但是只看缩写的话,SNAT会有很多其他的定义,有一些厂商,比如WatchGuard,SNAT代表的是Static NAT)
BASIC NAT
下面这个图分别演示了路由器在转换过程中的SNAT和DNAT
imageSNAT阶段:由于私网对外只有一个IP(一般是拨号拿来的或者运营商给静态分配的),私网的主机A在给公网发出去请求的给Web Server的时候,它的这个请求的数据包先经过路由器,路由器对会把它的地址改为私网对外的唯一的对外的公网地址202.20.65.5,然后在发给Web Server
DNAT阶段: Web Server收到请求后,开始响应,这个时候它响应的数据包的目标地址就变成了它私网的唯一的对外的公网地址202.20.65.5,但是,路由器收到这个数据包后,要把他转给这个包发起者192.168.1.2,就必须包目的地址改为192.168.1.2然后再发出去,这样主机A才会接受这个包(这里涉及同一个子网到数据链路层包广播的问题)
那么,这里又有一个问题,在DNAT阶段,路由器是怎么知道,这个报应该把他的目的地址转换位192.168.1.2呢?这里就涉及到上面所说会话管理的问题,路由器(或者说NAT网关)在会话开始的阶段,在第一个个包发出去,它就确认了会话的方向(第一个包的方向就是会话的方向)和对应关系(这里讲的会话的定义是,一小撮用来表达怎么用来完成一个转换的流量集合,这个集合定义为一个会话)
到这里,又涉及到一个概念是Session flow和packet low的对比(NAT用的Session flow),这里不细讲了,可以直接看rfc规范的这部分
在传输层里面:
- 如果是TCP协议,每个TCP会话的第一个数据包尝试建立一个会话 并包含连接启动信息。(这里涉及到3次握手的过程。TCP Session的确立,是在第一个包含这SYN标志位,而有没有ACK标志位出现后就确认,在TCP的协议里,除了第一个SYNC包,除了其他的包都必须包含ACK标志位)
2. 如果是UDP又是另外一码事,这里不细讲,看rfc规范吧
还有关于TCP和UDP详细的会话结束的定义和生命周期,看rfc规范这里
总的来讲,就是NAT会建立一个session或者connection 跟踪的机制,来跟踪哪个报应该转换到内部的哪个个主机IP
image这个track table 如果,是外网IP足够多的话,那就用静态分配技术,使内外网IP一对一,如果外网IP不够多,那就用动态分配,如何根据这个对照表来动态转换
NAPT
大家看上面这个图很快就会发现另外一个问题,如果内网里面有两个主机同时请求Web Server,那怎么转换呢,这个时候,可以把端口作为额外信息加入这个映射表,这个就是NAPT了
image看上图,即使主机A和主机B请求的源端口一样,路由器也可以将他们转换成不一样的的端口,如何在发出去,如何收到Server回复的数据包的时候,再按照转换的规则,原路转换回去就好
docker和wireshark抓包实例演示
下面我们用docker建一个有子网络位192.168.5.0的子网的容器,如何在容器它里面去访问一个网站,并通过wireshark来抓这个包看具体是怎么转换的
# 新建一个桥接子网
docker network create -d bridge --subnet 192.168.5.0/24 --gateway 192.168.5.1 test_bridge1
# 新建一个名位box 1的容器,并加入这个网络
docker run --name box1 -p 8082:80 -it --rm --network=test_bridge1 busybox sh
如何我们在宿主机,看看这个私有网络的网卡是哪个
route -n
内核 IP 路由表
目标 网关 子网掩码 标志 跃点 引用 使用 接口
192.168.5.0 0.0.0.0 255.255.255.0 U 0 0 0 br-6edc31410314
接着,用wireshark监控这个网卡
并在box 1这个容器里面区去访问外网
wget www.baidu.com
然后下面这个截图是wireshark的抓包信息
image可以看到,前面是访问dns,如何接下来就是建立TCP连接,最后是发起http的Get请求
可以看到info里面是49982->80,这个就是NAPT,主机box1用的端口是49982去访问百度的80
可以展开这个握手包的传输层看看:
image然后服务器发回握手的第二个包是确认包,我们再看看他的传输层:
imageserver返回来的包的目的端口是49982,这个就是NAT的映射关系
实际上,上面这个例子,有几层的NAT,有容器到子网,如何子网到物理机的无线网卡,那么即使你在分别抓这3个地方的包,他们的端口映射是一样的。
上面这个端口的情况,下面我们来看一下ping 百度的其中一个ip
ping 182.61.200.6
抓容器的包和子网网桥的包和以及宿主机的包对比一下
image image image容器本身和网桥里面抓包的source ip 是一样的(这个是容器的veth pair实现的机制所致),IP是192.168.5.2(容器IP)
而宿主机发出去的source ip以及被NAT网关转换成192.168.3.3,同时也可以看到,回复的ICMP包的地址也同样被按原路转换回去了
查看宿主机的NAT规则
上面我们创建容器的时候,绑定了容器box1的80端口到宿主机的8082端口
docker run --name box1 -p 8082:80 -it --rm --network=test_bridge1 busybox sh
那这个绑定是怎么回事呢?为什么这样绑定,发给宿主机的8082端口的数据包就会发到容器呢?答案就是NAT,严格来说是宿主机在它的iptables里面增加了DNAT规则,告诉宿主机的iptable,碰到8082的包就发给宿主机的80吧,下面就是这条规则
> sudo iptables -S -t nat | grep 8082
-A DOCKER ! -i br-6edc31410314 -p tcp -m tcp --dport 8082 -j DNAT --to-destination 192.168.5.2:80
上面是规则,下面把路由表格列出来
> sudo iptables -L -t nat
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
KUBE-SERVICES all -- anywhere anywhere /* kubernetes service portals */
DOCKER all -- anywhere anywhere ADDRTYPE match dst-type LOCAL
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
KUBE-SERVICES all -- anywhere anywhere /* kubernetes service portals */
DOCKER all -- anywhere !localhost/8 ADDRTYPE match dst-type LOCAL
...
Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:27017 to:172.18.0.2:27017
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8081 to:172.17.0.2:80
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8082 to:192.168.5.2:80
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8083 to:192.168.5.3:80
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8085 to:192.168.6.3:80
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8084 to:192.168.5.4:80
用conntrack看当前链接的映射关系
sudo conntrack -L
tcp 6 32841 ESTABLISHED src=192.168.3.3 dst=49.51.200.100 sport=58710 dport=443 src=49.51.200.100 dst=192.168.3.3 sport=443 dport=58710 [ASSURED] mark=0 use=1
tcp 6 74 TIME_WAIT src=10.1.1.1 dst=10.1.1.32 sport=33634 dport=8082 src=10.1.1.32 dst=10.1.1.1 sport=8082 dport=33634 [ASSURED] mark=0 use=1
tcp 6 31706 ESTABLISHED src=192.168.3.3 dst=49.51.200.100 sport=55282 dport=443 src=49.51.200.100 dst=192.168.3.3 sport=443 dport=55282 [ASSURED] mark=0 use=1
...