情境:有一台服务器,其外网IP为10.103.25.248,作为宿主机其上运行着许多虚拟机,网络方式为NAT,宿主机的内网IP为192.168.83.1,其中一台虚拟机的ip为192.168.83.131,其8000端口运行着WEB服务,现在需要使10.0.0.0/24的外网计算机能够访问虚拟机上的8000服务。
因为192.168.83.131是服务器的内部虚拟网络,对外是不可见的,所以需要将虚拟机8000服务映射到宿主机外网IP 10.103.25.248的某端口上上,这就是端口映射,亦即PNAT。
选定端口
查看宿主机端口占用情况:
1 2 3
| root@dell:~# sudo lsof -i:8000 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME java 2466 confluence 37u IPv6 23593 0t0 TCP localhost:8000 (LISTEN)
|
8000已被使用,选择使用空闲的8880端口,即实现10.103.25.248:8880 <===> 192.168.83.131:8000的映射。
开启IP转发功能
在配置iptables实现NAT之前,enable ip forwarding是前提。
1
| sysctl net.ipv4.ip_forward=1
|
DNAT
DNAT为目的地址翻译缩写,在IP包进行内核路由之前,将其目的地址改写,再送入路由表进行匹配。
1
| iptables -t nat -A PREROUTING -d 10.103.25.248 -p tcp --dport 8880 -j DNAT --to-destination 192.168.83.131:8000
|
-t指定nat表,-A添加到PREROUTING链末, -d指定目的地址,-p 指定TCP协议,—dport目的端口,-j选择DNAT操作,—to-destination指定改写后的IP和端口。
注意这里同时隐含了反向操作,对源自IP和端口为192.168.83.131:8000的TCP数据包进行源IP:端口改写。
此时已经能够成功连接了。
再来查看iptable的nat表:
1 2 3 4
| root@dell:~# iptables -L PREROUTING -nvx --line-numbers -t nat Chain PREROUTING (policy ACCEPT 1626 packets, 278608 bytes) num pkts bytes target prot opt in out source destination 1 9 540 DNAT tcp -- * * 0.0.0.0/0 10.103.25.248 tcp dpt:8880 to:192.168.83.131:8000
|
测试与分析
相应的规则已经添加进来了,现在从另一台10.210.106.20的机器访问10.103.25.248:8880试试wget 10.103.25.248:8880
:
1 2 3
| chen@vaio:~$ wget 10.103.25.248:8880 --2015-05-11 19:21:23-- http://10.103.25.248:8880/ Connecting to 10.103.25.248:8880...
|
并未能连接上,是DNAT失败吗?在运行8000服务的虚拟机上用tcpdump来诊断一下:
1 2 3 4
| 03:38:53.575588 IP 10.210.106.20.45581 > 192.168.83.131.irdmi: Flags [S], seq 404138411, win 29200, options [mss 1460,sackOK,TS val 26590957 ecr 0,nop,wscale 7], length 0 03:38:53.575683 IP 192.168.83.131.irdmi > 10.210.106.20.45581: Flags [S.], seq 683540265, ack 404138412, win 14480, options [mss 1460,sackOK,TS val 345476707 ecr 26590957,nop,wscale 7], length 0 03:38:53.575974 IP 10.210.106.20.45581 > 192.168.83.131.irdmi: Flags [R], seq 404138412, win 32767, length 0 03:38:55.579521 IP 10.210.106.20.45581 > 192.168.83.131.irdmi: Flags [S], seq 404138411, win 29200, options [mss 1460,sackOK,TS val 26591458 ecr 0,nop,wscale 7], length 0
|
可以8000端口是收到了来自10.210.106.20的访问请求的(SYN),并且也有应答(SYN,ACK),但是为什么没有连接上呢?因为虚拟机并没有到达10.210.106.20的路由,即使网络能到达,但是返回数据包的src_ip是192.168.83.131,而client请求的是10.103.25.248,会被client当作错误的数据包丢弃。
SNAT
所以还需要进行一次SNAT,将连接请求的源IP改为宿主机的内网IP:192.168.83.1。
1
| iptables -t nat -A POSTROUTING -d 192.168.83.131 -p tcp --dport 8000 -j SNAT --to-source 192.168.83.1
|
以上命令执行的操作是:目的IP和端口为192.168.83.131:8000的TCP数据包从路由表出来之后,改写其源IP的192.168.83.1。同时隐含了反向操作,对源自IP和端口为192.168.83.131:8000的TCP数据包进行目的IP还原。
此时已经能够成功连接了。
1 2 3 4 5 6 7 8 9 10
| chen@vaio:~$ wget 10.103.25.248:8880 --2015-05-11 19:17:42-- http://10.103.25.248:8880/ Connecting to 10.103.25.248:8880... connected. HTTP request sent, awaiting response... 200 OK Length: 5659 (5.5K) [text/html] Saving to: ‘index.html.6’ index.html.6 100%[=====================>] 5.53K --.-KB/s in 0.002s 2015-05-11 19:17:42 (3.00 MB/s) - ‘index.html.6’ saved [5659/5659]
|
此时的nat表为
1 2 3 4 5 6 7 8
| root@dell:~# iptables -L -nvx --line-numbers -t nat Chain PREROUTING (policy ACCEPT 999 packets, 260994 bytes) num pkts bytes target prot opt in out source destination 1 28 1680 DNAT tcp -- * * 0.0.0.0/0 10.103.25.248 tcp dpt:8880 to:192.168.83.131:8000 …… Chain POSTROUTING (policy ACCEPT 55 packets, 3630 bytes) num pkts bytes target prot opt in out source destination 1 1 60 SNAT tcp -- * * 0.0.0.0/0 192.168.83.131 tcp dpt:8000 to:192.168.83.1
|
总结
如果不计较原理,实质操作只有三步:
1 2 3 4 5 6
| #开启ip转发 sysctl net.ipv4.ip_forward=1 #DNAT iptables -t nat -A PREROUTING -d 10.103.25.248 -p tcp --dport 8880 -j DNAT --to-destination 192.168.83.131:8000 #SNAT iptables -t nat -A POSTROUTING -d 192.168.83.131 -p tcp --dport 8000 -j SNAT --to-source 192.168.83.1
|
其它注意事项有:
- centos开启新服务时需要添加iptables规则开放相应端口(默认规则为拒绝)。
- 如果想对本机同一IP使用端口映射,以上方法依次适用,但是要注意lo的特殊性:访问127.0.0.1和本机IP不经过PREROUTING链,那么需要在OUTPUT链上作转换
1 2 3
| iptables -t nat -A OUTPUT -d 127.0.0.1,10.210.106.20 -p tcp --dport 2222 -j DNAT --to 127.0.0.1:22 # 或者 iptables -t nat -A OUTPUT -d 127.0.0.1,10.210.106.20 -p tcp --dport 2222 -j REDIRECT --to-port 22
|