一、路由转发与SNAT实验

iptables实战-SNAT|DNAT|负载均衡_Linux运维


环境说明:

debian机器位于内网,有一个网卡ens38,ip地址172.16.1.2/24,网关为172.16.1.1(router的eth2)

router机器位于内网和外网的边界,有2个网卡eth1和eth2,eth1地址192.168.124.247接外网,网关192.168.124.1;eth2地址172.16.1.1,连接debian

在网卡配置正确的情况下,此时使用debian机器是可以直接ping通172.16.1.1,但是无法ping通任何外网地址。但是debian机器可以ping通router上eth1的ip192.168.124.247。部分同学可能对这一现象不太理解,原因简单陈述一下:在linux系统中,ip地址是属于内核而非网卡的。在这个案例中,debian ping 192.168.124.247时,发现该ip和自己不是同一网段,因此将数据包发往172.16.1.1,此时数据包的dst ip为192.168.124.247,dst mac为eth2的mac。router解开mac发现是自己的mac(eth2的mac),继续解包,再解开ip发现也是自己的(eth1的ip),所以直接回包,此时两者可以通信。

debian机器无法ping通任何外网地址的原因是,数据包到网关router后,router没有打开ip_forword参数,因此会直接丢弃目的地址不属于自己的包。因此第一步需要打开该参数。在router上打开路由转发功能:

echo 1 " data-textnode-index-1671119076312="8" data-index-1671119076312="694" data-textnode-notemoji-index-1671119076312="694" class="" style="margin: 0px; padding: 0px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important;">> /proc/sys/net/ipv4/ip_forward

此时debian的数据包经过router时会被路由转发,但是仍然无法和外界通信。原因是外界没有返回172.16.1.2的路由。此时的解决方案是在router上增加一条snat规则。这样debian机器出网时被snat成router的eth1上的ip,外网回包时先回给router机器,router机器根据原来的snat映射修改回包的目的地址,再发送给debian机器。

iptables -t nat -A POSTROUTING -s 172.16.1.0/24 -o eth0 -j SNAT --to-source 192.168.124.247

此时debian即可和外界通信。

二、DNAT映射实验

环境说明

拓扑如前,现在有一个网络仅能和router(192.168.124.247)通信,无法和debian(172.16.1.2)通信。但是该网络下的用户想要连接位于内网的debian机器,处于安全考虑不能让用户ssh到router后再跳到debian,想在router上开一个端口32200,映射到debian机器的22端口

思路与实现:

数据包到达router机器之后,不能直接流向用户空间。因为用户空间并没有程序监听32200。因此映射的变化应该是在路由决策之前,所以应该配置在PREROUTING链上。在PREROUTING链上增加一条dnat规则,使得ssh 192.168.124.247的32200端口时,把目的地址改为172.16.1.2,端口改为22。

iptables -t nat -A PREROUTING -d 192.168.124.247 -p tcp --destination-port 32200 -i eth0 -j DNAT --to-destination 172.16.1.2:22

三、出向负载均衡和nat实验

环境说明

iptables实战-SNAT|DNAT|负载均衡_SNAT_02


debian机器上配置了4个ip地址,需要实现4个ip轮询访问外部网络。

为了方便验证,我搭建了一个nginx。nginx几行配置即可返回客户端的ip,配置如下:

location / {
      return 200 <span data-raw-text="" "="" data-textnode-index-1671119076312="26" data-index-1671119076312="1621" data-textnode-notemoji-index-1671119076312="1621" class="character">"$remote_addr\n<span data-raw-text="" "="" data-textnode-index-1671119076312="28" data-index-1671119076312="1636" data-textnode-notemoji-index-1671119076312="1636" class="character">";
      }

我们只需要多次请求nginx,只要nginx轮询返回不同的ip,就证明我们实现了这样的效果。

debian机器的网卡配置如下:

auto ens38
iface ens38 inet static
address 172.16.1.2
netmask 255.255.255.0
gateway 172.16.1.1
auto ens38:1
iface ens38:1 inet static
address 172.16.1.3
netmask 255.255.255.0
auto ens38:2
iface ens38:2 inet static
address 172.16.1.4
netmask 255.255.255.0
auto ens38:3
iface ens38:3 inet static
address 172.16.1.5
netmask 255.255.255.0

思路与实现

通常来说,机器和外界通信时,会默认使用第一个网卡主ip,想要修改这一行为,可以在POSTROUTING链中修改源ip。iptables有一个statistic扩展,可以实现负载均衡的效果。

statistic扩展:

statistic
This module matches packets based on some statistic condition. It supports two distinct modes settable with the --mode option.
Supported options:
该模块根据一些统计条件匹配报文。 它支持两个不同的模式,可通过--mode选项设置。  

--mode mode
Set the matching mode of the matching rule, supported modes are random and nth.
设置匹配规则的匹配模式,支持的匹配模式为random和nth。  
[!] --probability p
Set the probability for a packet to be randomly matched. It only works with the random mode. p must be within 0.0 and 1.0. The supported granularity is in 1/2147483648th increments.
设置报文被随机匹配的概率。 它只适用于随机模式。P必须在0.0和1.0之间。 支持的粒度增量为1/2147483648。  
[!] --every n
Match one packet every nth packet. It works only with the nth mode (see also the --packet option).
每n个包匹配一个包。 它只适用于nth个模式(同时请参阅--packet选项)。  
--packet p
Set the initial counter value (0 <= p <= n-1, default 0) for the nth mode.
设置初始计数器。计数器=p的时候,命中规则。0 <= p <= n-1,p=n-1之后,下一次会被重置为0。如果不设置则默认是0。只适用于nth模式

以上翻译自官方文档https://ipset.netfilter.org/iptables-extensions.man.html

所以进行如下设置。

iptables -A POSTROUTING  -t nat -d 192.168.127.247 -m statistic --mode nth --every 4 --packet 0 -j SNAT --to-source 172.16.1.2
iptables -A POSTROUTING  -t nat -d 192.168.127.247 -m statistic --mode nth --every 3 --packet 0 -j SNAT --to-source 172.16.1.3
iptables -A POSTROUTING  -t nat -d 192.168.127.247 -m statistic --mode nth --every 2 --packet 0 -j SNAT --to-source 172.16.1.4
iptables -A POSTROUTING  -t nat -d 192.168.127.247 -m statistic --mode nth --every 1 --packet 0 -j SNAT --to-source 172.16.1.5
计数器变化:
第一个tcp会话:0 ,第一条命中,对应计数器+1,被snat成172.16.1.2
第二个tcp会话:10,第二条命中,对应计数器+1, 被snat成172.16.1.3
第三个tcp会话:210,第三条命中,对应计数器清+1, 被snat成172.16.1.4
第四个tcp会话:3210,第四条命中,对应计数器+1,同时所有计数器都打到every上限,所有计数器清0, 被snat成172.16.1.2
。。。。进入下一个循环,以此类推

nat表规则:

root@debian:~# iptables -nL -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination         
SNAT       all  --  0.0.0.0/0            192.168.124.247      statistic mode nth every 4 to:172.16.1.2
SNAT       all  --  0.0.0.0/0            192.168.124.247      statistic mode nth every 3 to:172.16.1.3
SNAT       all  --  0.0.0.0/0            192.168.124.247      statistic mode nth every 2 to:172.16.1.4
SNAT       all  --  0.0.0.0/0            192.168.124.247      statistic mode nth every 1 to:172.16.1.5

如此即可实现出向负载均衡的效果:

root@debian:~# curl 192.168.124.247
172.16.1.2
root@debian:~# curl 192.168.124.247
172.16.1.3
root@debian:~# curl 192.168.124.247
172.16.1.4
root@debian:~# curl 192.168.124.247
172.16.1.5
root@debian:~# curl 192.168.124.247
172.16.1.2
root@debian:~# curl 192.168.124.247
172.16.1.3
root@debian:~# curl 192.168.124.247
172.16.1.4
root@debian:~# curl 192.168.124.247
172.16.1.5