关于connmark负载均衡(About connmark load-balancing)
本文介绍了在linux-2.6的路由器上,使用netfilter及iptables的connmark模块进行网络负载均衡。
目的:演示如何使用多个互联网连接为局域网中的所有主机提供更快的互联网访问速度。我们希望所有的传出连接都分布在所有可用的网络链接上。所有的路由必须在一台路由器上实现,不需要改变本地网络的机器配置。
它应该适用于所有linux-2.6内核及以上的linux,同时还需要安装iproute2软件包及基本的网络工具。
网络环境示例(Example of networking environment)
为了解释如何使用connmark模块进行负载均衡,我们将使用以下网络环境:
- 本地网络(192.168.157.0/24)由运行各种操作系统(如Windows和Linux)的3台桌面机组成。这些PC被命名为Saturn,Desktop2,Desktop3。所有的例子将基于Saturn,其他PC将以相同的方式行事。所有这些机器都使用Jupiter(192.168.157.253)作为默认网关。在这些PC上没有高级路由工作要做。
- 网络的路由器是Jupiter。它有三个以太网接口。第一个是eth0(192.168.157.253),它连接到本地网络。其他接口是eth1(10.37.1.253)和eth2(10.37.2.253)。他们连接到两个以太网调制解调器,分别连接到两个ADSL链接。所有的防火墙和路由都是在Jupiter上完成的。
- 别的地方有一台名叫Neptune的服务器连接到互联网。本地网络和Neptune之间没有直接的联系。Neptune只是作为一个普通的服务器,没有特定的路由配置。它只是用作本文中的一个远程服务器。例如,它可以是运行Apache的Web服务器,响应在端口tcp/80上发出的请求。它也可以在任何端口上监听任何其他服务。
- 本文的目的是展示如何均衡Jupiter上的连接,以便利用两条ADSL链路提供的全部可用带宽。所有的连接都是由连接到本地网络的PC发起的,所有的连接都是通过Neptune进行的。当然,它可以访问任何远程服务器,但是我们只需要一台机器作为测试的远程服务器。
- 在图中,我们使用内部IP地址(192.168.157.a,10.37.b.c,172.16.1.d)。在真实环境中,所有连接到互联网的接口都可能具有公共IP地址。如果调制解调器工作在NAT模式下,它们也可以是专用地址。在这种情况下,每个调制解调器都有两个IP地址:以太网接口上的专用内部地址和WAN接口上的公共IP地址。
- 本文介绍了如何在两个链接之间执行负载均衡,但显然您可以根据需要使用尽可能多的链接执行相同的操作。
- 测试已经在运行gentoo linux的路由器上运行,内核为2.6.24,iptables-1.4.0
环境要求(Requirements)
需要最近的linux-2.6内核和最近的iptables版本,因为我们需要使用高级netfilter模块,它是在最近的linux-2.6版本中引入或更改的。以下是我们需要的功能:
- netfilter的CONNMARK目标模块用于标记属于连接的数据包。它是在linux-2.6.12中引入的。
- netfilter的statistic匹配模块用于决定使用哪个链接。这个特性已经在linux-2.6.18中合并了。
- 还需要一个支持statistic匹配的iptables版本,它在iptables-1.4.0中可用,也可能在旧版本中。要知道你的iptables是否支持它,你可以看看man iptables,或者检查系统中/lib/iptables或/lib64/iptables中是否有一个名为libxt_statistic.so的文件。
- 旧的iptables版本似乎支持相同功能,在一个名为libxt_nth.so的模块中。在最近的iptables中,nth和random匹配被合并到一个名为statistic的模块中。
该负载均衡的原理及局限性(How this load balancing works)
Netfilter是一个有状态的防火墙。这意味着它能够知道IP数据包属于哪个连接。连接由连接跟踪模块(connmark)管理。在路由数据包时遵守连接是非常重要的。一个连接的所有IP数据包必须通过相同的链路路由。如果我们通过两个不同的链路来路由一个连接的数据包,将会中断连接。这就是为什么netfilter在内存中有一个状态表。它使用它来记住一个连接的状态,并以这种方式可以识别属于该连接的所有数据包。
每次创建一个新的连接,我们将选择使用哪个链接。在我们的例子中,可以使用两条链路来路由Jupiter上的数据包。所以我们必须在link1和link2之间进行选择。一旦做出选择,我们将数据包路由到eth0(连接到link1的接口)或eth1(连接到link2的接口)。一旦对连接的第一个IP数据包进行了路由决策,我们就必须将这个决定写在防火墙状态表中,以便我们可以对这个连接的随后的数据包进行相同的路由。
我们必须注意到,netfilter连接跟踪系统能够处理UDP数据包。UDP传输通常被认为是“非连接模式”,因为数据包中没有序列号。但是,连接跟踪认为具有相同源/目标的IP地址/端口的UDP数据包是连接的一部分。所以负载均衡将能够在UDP流量和TCP上工作。
负载均衡在连接级别工作。无论IP地址或端口是什么,路由决策(使用哪个链路)都将被采用。这意味着它不会考虑源IP/目的IP/源端口/目的端口,因此所有客户端计算机的负载均衡方式相同,并且适用于所有服务。这一点很重要,因为这意味着即使只有一个本地网络的桌面正在使用网络,负载均衡也是有效的。无论用户是谁,Jupiter将只传播所有从本地网络(eth0)传入的连接到可用链接(eth1和eth2)。
不幸的是,在单个连接需要大量带宽的情况下,负载均衡器将无法高效地分发传入的数据包。这是不可能的,因为将一个连接的数据包路由到多个链路将会破坏TCP连接。接收者将看到具有不同源IP地址的IP分组,并且将丢弃分组。
负载均衡概述(Overview of the load balancing)
现在,让我们看看我们如何做路由来均衡数据包。旧的route和最近的iproute2工具都在IP级别工作。问题是连接取决于源端口和目标端口,这些端口是在传输级别(TCP和UDP)决定的。
为了解决这个问题,我们将使用netfilter连接跟踪(conntrack)和连接标记(connmark)模块。他们将识别哪些IP包属于哪个tcp/udp连接,并且这些模块会在这些包上打上标记。这个标记是fwmark属性。在本文中,我们可以使用fwmark或者只是mark来引用这个属性。请记住,这是相同的属性。这个属性不是IP头的一部分,它只是在路由器内存中。在负载均衡的第二阶段,路由将使用该标记来知道分组必须路由到哪个以太网设备。iproute2是我们将用来进行高级路由的软件包。ip rule命令用于提供策略路由,并且可以基于fwmark属性执行IP路由。
显然,数据包必须在达到路由代码之前必须由netfilter标记。所以记住“何时”netfilter工作在数据包上是非常重要的。Netfilter在内核网络堆栈中有五个钩子。这意味着有五个地方可以让netfilter函数在数据包上工作。以下是五个钩子可以看到的数据包种类:
- PREROUTING:所有传入的数据包,不管目的地址是什么;
- POSTROUTING:无论源地址是什么,都是传出的数据包;
- FORWARD:所有路由的数据包;
- INPUT:发送到本地机器的所有数据包;
- OUTPUT:本地机器发送的所有数据包
因此,如果我们在POSTROUTING标记数据包,路由代码将不会看到标记,高级路由将不起作用。这就是为什么我们必须在传入路由数据包的PREROUTING钩子中工作,如果我们要路由由路由器本身发送的数据包,则在OUTPUT钩子中。
使用connmark标记数据包(Marking the packets with connmark)
关于netfilter中的表(About the netfilter tables)
Netfilter和iptables使用三个表:
- filter:最受常用的表,主要用于防火墙、接受、拒绝数据包;
- nat:用于NAT;
- mangle:它主要用于修改网络数据包;
我们将使用mangle表,因为我们要更改数据包的属性(fwmark)。
连接状态(The connection states)
netfilter是检查数据包并决定它们是否与特定条件匹配的模块。只有当匹配返回true时,netfilter才会执行与匹配相关的操作。这就是连接跟踪模块如何访问连接的状态。这是通过-m state完成的。以下是各种状态:
- NEW:数据包正在启动一个新的连接(它可能是TCP中的一个SYN数据包);
- ESTABLISHED:数据包与连接相关联,在连接的两个方向上能看到数据包;
- RELATED:数据包启动了一个新连接,并与现有连接关联的(如果是FTP,则可能是数据连接);
- INVALID:数据包未与有效的连接关联;
这些状态是很重要的,因为我们必须仅对新数据包进行路由决策,并且该决策要是数据包是已经存在的连接的一部分。
统计匹配(The statistic match)
统计netfilter模块提供了两种模式:nth和random:
- nth:这个模式允许你知道在接收到的数据包列表中数据包的索引是什么。换句话说,您知道是否是第一个,第二个,第三个,第四个,。。。当它达到一个值,可以使用名为every的选项来重置计数器。这样,与数据包相关的索引可以是如下的数据:1,2,3,1,2,3,1,2,3 …当每个数字是3时。
- random:这个模式允许你选择随机数据包。如果要抓取33%的数据包,则可以提供0.33的概率。
在旧的linux/iptables版本中,这两种模式在两个不同的模块中实现。所以如果你使用2.6.18之前的Linux内核,你可能需要更改iptables指令。
connmark的target(The connmark target)
一个ipfilter target是一个模块,该模块运行一个动作。我们将需要MARK目标在数据包上标记,CONNMARK管理netfilter状态表:
- -j MARK –set-mark:此操作用于在IP数据包上写入fwmark。标记的值是作为这个动作的一个参数给出的;
- -j CONNMARK –save-mark:此操作用于在状态表中写入数据包的fwmark(从数据包到状态表);
- -j CONNMARK –restore-mark:这个动作用于在ip包中写入状态表的fwmark值(从状态表到包);
初始化一个新连接(Initialization of a new connection)
每当我们收到一个发起新连接的数据包时,我们希望在数据包上设置fwmark属性。要做到这一点,我们将使用-m state –state NEW匹配,相关的操作将是在IP数据包中设置标记,并将标记保存在状态表中,以便稍后将其用于这个连接的其他数据包。
CONNMARK目标提供了一个名为–set-mark X的选项,它应该在一个数据包上设置fwmark。它在测试过程中没有工作,标记仍然是0。这就是为什么我们将使用下面的目标代替:-j MARK –set-mark 1。
- 首先,我们用-m state –state NEW来选择出启动一个新连接的数据包。下面是conntrack模块在执行比如-m state –state NEW时可以做什么的一个例子:
- conntrack代码看到当前的tcp数据包是从192.168.157.3:45238到172.16.1.100:80;
- 该模块在netfilter状态表中找不到从192.168.157.3:45238到172.16.1.100:80的现有tcp连接,因此它认为状态为NEW,它会在状态表中创建一个新条目并返回true;
- netfilter发现匹配返回true,所以与匹配相关的操作将被执行;
- 一旦前一次匹配选择了一个数据包,我们希望使用MARK目标来设置数据包中的fwmark属性,并且我们也希望使用CONNMARK目标来保存与新连接相关联的fwmark。我们创建两个名为CONNMARK1和CONNMARK2的新链(每个链接一个),因为有两个目标与一个匹配相关联。以下是connmark目标在执行比如-j CONNMARK –save-mark时可以做什么的一个例子:
- connmark模块发现当前的tcp数据包是从192.168.157.3:45238到172.16.1.100:80;
- 该模块查找刚刚为新连接创建的状态表条目,从192.168.157.3:45238到172.16.1.100:80;
- 它将包中看到的fwmark属性保存到状态表项中该属性的副本中;
下面是用于标记新包的iptables指令:
#!/bin/bash # initialise two chains that will put the mark on the packet and keep it in memory iptables -t mangle -N CONNMARK1 iptables -t mangle -A CONNMARK1 -j MARK --set-mark 1 iptables -t mangle -A CONNMARK1 -j CONNMARK --save-mark iptables -t mangle -N CONNMARK2 iptables -t mangle -A CONNMARK2 -j MARK --set-mark 2 iptables -t mangle -A CONNMARK2 -j CONNMARK --save-mark # if the mark is zero if means the packet does not belong to any existing connection iptables -t mangle -A PREROUTING -p tcp -m state --state NEW \ -m statistic --mode nth --every 2 --packet 0 -j CONNMARK1 iptables -t mangle -A PREROUTING -p tcp -m state --state NEW \ -m statistic --mode nth --every 2 --packet 1 -j CONNMARK2
已存在连接的数据包(Packets of an existing connection)
现在,我们还必须处理那些属于已有连接的数据包。我们必须保持与为此连接的第一个数据包所做的路由决策相同的路由决策,否则会中断连接。所以我们首先必须使用-m state –state ESTABLISHED,RELATED来选择相关连接的数据包,然后我们必须在状态表中找到与这个连接相关联的fwmark属性,并将这个fwmark写入IP包。
- 首先,我们使用-m state –state ESTABLISHED,RELATED选择属于现有连接的数据包。重要的是不要在两个方向上标记数据包,否则会干扰路由。该标记只能在从本地网络(从接口eth0)路由到ADSL链路的数据包上设置,所以我们使用-i eth0来确保我们不会选择在另一个方向的数据包。下面是一个conntrack模块在执行比如-m stat –state ESTABLISHED,RELATED时做了什么的例子:
- conntrack代码看到当前的tcp数据包是从192.168.157.3:45238到172.16.1.100:80;
- 该模块在netfilter状态表中找到一个从192.168.157.3:45238到172.16.1.100:80的现有tcp连接,所以这个包的状态是ESTABLISHED或RELATED,所以匹配返回true;
- netfilter发现匹配返回true,所以与匹配相关的操作将被执行;
- 下面是connmark执行-j CONNMARK –restore-mark命令时的一个例子:
- connmark模块发现当前的tcp数据包是从192.168.157.3:45238到172.16.1.100:80;
- 该模块在netfilter状态表中找到一个从192.168.157.3:45238到172.16.1.100:80的现有tcp连接;
- 它读取已存储在netfilter状态表中的fwmark属性,并将其写入数据包的fwmark属性中。该属性将被路由代码使用。
下面就是上述对已存在连接处理的命令:
#!/bin/bash iptables -t mangle -A PREROUTING -i eth0 -p tcp \ -m state --state ESTABLISHED,RELATED -j CONNMARK --restore-mark
完整的iptables标记命令(The whole iptables marking code)
#!/bin/bash # initialise two chains that will put the mark on the packet and keep it in memory iptables -t mangle -N CONNMARK1 iptables -t mangle -A CONNMARK1 -j MARK --set-mark 1 iptables -t mangle -A CONNMARK1 -j CONNMARK --save-mark iptables -t mangle -N CONNMARK2 iptables -t mangle -A CONNMARK2 -j MARK --set-mark 2 iptables -t mangle -A CONNMARK2 -j CONNMARK --save-mark # get the mark on the packet that belongs to an existing connection iptables -t mangle -A PREROUTING -i eth0 -p tcp \ -m state --state ESTABLISHED,RELATED -j CONNMARK --restore-mark # if the mark is zero it means that the packet does not belong to any existing connection iptables -t mangle -A PREROUTING -p tcp -m state --state NEW \ -m statistic --mode nth --every 2 --packet 0 -j CONNMARK1 iptables -t mangle -A PREROUTING -p tcp -m state --state NEW \ -m statistic --mode nth --every 2 --packet 1 -j CONNMARK2
对标记的数据包进行路由(Routing the marked packets)
要使用mark属性路由数据包,我们必须使用ip rule命令。它被命名为“policy routing”。我们必须创建辅助路由表,当数据包的fwmark属性与规则匹配时,使用该路由表。
创建新的路由表(Create new routing tables)
首先,我们必须通过编辑/etc/iproute2/rt_tables来创建这两个路由表。下面是自动创建两个名为rt_link1和rt_link2的表的命令:
#!/bin/bash if ! cat/etc/iproute2/rt_tables | grep -q '^251' then echo '251 rt_link1' >>/etc/iproute2/rt_tables fi if ! cat/etc/iproute2/rt_tables | grep -q '^252' then echo '252 rt_link2' >>/etc/iproute2/rt_tables fi
下面是Jupiter上的路由表的内容:
# reserved values
255 local
254 main
253 default
0 unspec
# custom routes
252 rt_link2
251 rt_link1
现在我们必须填充这两个路由表。最好的做法是在每个表中添加一个默认路由。每个默认路由将数据包驱动到要使用的链接所在的以太网卡。这样,当标记为fwmark=1的包遵循在rt_link1中写入的默认路由时,将通过设备eth1发送给Neptune。我们同时使用ip route flush来确保表是空的。
#!/bin/bash ip route flush table rt_link1 ip route add table rt_link1 default dev eth1 ip route flush table rt_link2 ip route add table rt_link2 default dev eth2
使用新策略路由表(Use the new tables with policy routing)
现在我们必须使用ip rule命令来说明如何处理标记的数据包。下面几行表示标记fwmark=1的数据包必须遵循名为rt_link1的路由表的路由指令,而具有第二个标记的数据包必须使用rt_link2。最后,我们刷新路由缓存,以确保新规则被执行。
#!/bin/bash ip rule del from all fwmark 2 2>/dev/null ip rule del from all fwmark 1 2>/dev/null ip rule add fwmark 1 table rt_link1 ip rule add fwmark 2 table rt_link2 ip route flush cache
执行上述命令之后的所有规则:
0: from all lookup local
32764: from all fwmark 0x2 lookup rt_link2
32765: from all fwmark 0x1 lookup rt_link1
32766: from all lookup main
32767: from all lookup default
内核网络参数(linux network parameters)
如果你想你的路由器行为如预期的那样,有两个网络参数必须检查。首先,我们要确保在Jupiter上运行的内核被配置为路由数据包。要在IPv4上启用路由,必须将ip_forward设置为1(1表示启用,0表示禁用):
还必须禁用反向路径过滤(Reverse Path Filtering)。这是默认启用的一个选项,通过检查传入数据包的源地址是否与本地计算机上的路由表匹配来增加安全性并防止ip欺骗。由于我们正在做一个复杂的设置,这个选项会导致我们的数据包丢失,所以它必须被禁用:
如果您重新启动服务器,这些更改将会丢失。可以在启动时由脚本自动执行的,也可以编辑网络配置文件,以确保重新启动后保留这些更改。也可以编辑/etc/sysctl.conf:
#
# Enables packet forwarding
net.ipv4.ip_forward = 1
net.ipv4.conf.all.rp_filter = 0
Source Network-Address-Translation (SNAT)
现在从Saturn到Neptune的数据包应该按照预期路由。但是还有一个问题需要解决。Neptune向Saturn发送的回复将忽略高级路由,并将始终通过相同的链接发送,该链接与在Neptune上配置的路由192.168.157.3相匹配。当Neptune从Saturn接收数据包时,源地址是192.168.157.3。由于在Neptune上没有配置高级路由,所以到Saturn的数据包只能遵循正常的路由。
这是非对称路由的情况。由于Jupiter上的高级路由,从Saturn到Neptune的一半数据包通过第二个链路进行路由。而这些数据包的回复只是因为它是正常的路由而通过第一个链接发送的。我们要做的就是在Jupiter上配置SNAT(源网络地址转换),这样所有通过link1或link2发送的数据包都会被写入一个重写的源地址。我们希望link1的报文的源地址是10.37.1.253,link2的报文的源地址是10.37.2.253。这样Neptune将收到一个源地址与他们来的链接匹配的数据包。当Neptune回复来自link1或link2的请求时,它只会使用这些包中的源地址作为新的目的地址。
您还可以注意到,SNAT涉及隐式DNAT(目标网络地址转换)。当Jupiter在eth2(第二条链路连接的接口)上收到一个数据包时,它会工作,因为目的地址是10.37.2.253。这是来自Saturn(192.168.157.3)的一个数据包的回复,所以我们希望Jupiter改变目的地址,并把它转发给Saturn。这是由隐式DNAT完成的。
注意SNAT在POSTROUTING中执行是很重要的。这样它就在路由之后执行,这是我们将每个数据包驱动到正确的设备(Jupiter上的eth1或eth2)的地方。SNAT iptable规则使用“传出设备”匹配来确定在报头中必须写入什么源地址。
如果您在Jupiter和Neptune之间使用ADSL链接,则您将被迫使用本地网络之外的公共IP地址。大多数调制解调器可以为你做NAT。在这种情况下,您不必担心这一点。
在Jupiter上的SNAT配置命令如下:
#!/bin/bash iptables -t nat -F iptables -t nat -X iptables -t nat -A POSTROUTING -o eth1 -j SNAT --to-source 10.37.1.253 iptables -t nat -A POSTROUTING -o eth2 -j SNAT --to-source 10.37.2.253
问题排查(Troubleshooting)
Here is what you can do in case it does not work:
Check your firewall
In this article we assumed that the packet filtering is not enabled on your
router and on your network. In case you are using iptables already, you will
have to check that it’s consistent with the new iptables rules involved in the
destination port routing. It does not mean that you have to keep your firewall
wide open to get the load balancing to work, it just means that the filtering
may disturb the load balancing in the case that it’s not correctly configured.
列出当前的连接状态(List the current connections details)
The connection tracking module exports very useful information about the connections state table. You can read the file /proc/net/ip_conntrack that contains all the important attributes about the current connections, including the fwmark attribute:
% cat /proc/net/ip_conntrack
tcp 6 96 TIME_WAIT src=192.168.157.3 dst=172.16.1.100 sport=52037 dport=80 packets=6 bytes=414 src=172.16.1.100 dst=10.37.1.253 sport=80 dport=52037 packets=4 bytes=938 [ASSURED] mark=1 use=1
tcp 6 96 TIME_WAIT src=192.168.157.3 dst=172.16.1.100 sport=52035 dport=80 packets=6 bytes=414 src=172.16.1.100 dst=10.37.1.253 sport=80 dport=52035 packets=4 bytes=938 [ASSURED] mark=1 use=1
tcp 6 96 TIME_WAIT src=192.168.157.3 dst=172.16.1.100 sport=52039 dport=80 packets=6 bytes=414 src=172.16.1.100 dst=10.37.1.253 sport=80 dport=52039 packets=4 bytes=938 [ASSURED] mark=1 use=1
tcp 6 96 TIME_WAIT src=192.168.157.3 dst=172.16.1.100 sport=52036 dport=80 packets=6 bytes=414 src=172.16.1.100 dst=10.37.2.253 sport=80 dport=52036 packets=4 bytes=938 [ASSURED] mark=2 use=1
tcp 6 96 TIME_WAIT src=192.168.157.3 dst=172.16.1.100 sport=52038 dport=80 packets=6 bytes=414 src=172.16.1.100 dst=10.37.2.253 sport=80 dport=52038 packets=4 bytes=938 [ASSURED] mark=2 use=1
对于较新的发行版及内核使用cat /proc/net/nf_conntrack命令。或者使用conntrack包中的conntrack命令来查看连接状态。
Logging packets with iptables
In case you have problems, you may want to enable the logging so that you can
see all the packets that go through a specific netfilter instruction. To enable
logging, you can replace a simple iptables action (such as CONNMARK) with a
customized chain (such as LOG_FWMARK). Everytime a packet is marked you will
also have a message written in your logs. For instance, you can replace this
simple iptables command:
#!/bin/bash iptables -t mangle -A PREROUTING -i eth0 -p tcp \ -m state --state ESTABLISHED,RELATED -j CONNMARK --restore-mark
With the following chain:
#!/bin/bash # create a new chain to both restore the mark and log the packet iptables -t mangle -N RESTOREMARK iptables -t mangle -A RESTOREMARK -j CONNMARK --restore-mark iptables -t mangle -A RESTOREMARK -j LOG --log-prefix 'restore-mark: ' --log-level info # restore the fwmark to packet that belongs to an existing connection iptables -t mangle -A PREROUTING -i eth0 -p tcp \ -m state --state ESTABLISHED,RELATED -j RESTOREMARK
It’s important that you execute -j CONNMARK –restore-mark before -j LOG –log-
prefix ‘restore-mark: ‘ because you want to have the details of the packet
attributes when it has already been changed. Unfortunately the default LOG
match does not display the fwmark attribute. So it may be difficult to know
whether or not the mark in the packet is what you expect. What you can do is
you can edit the source code of the LOG target in the kernel sources, but it
means you have to recompile it. Here is what you need to change:
— net/ipv4/netfilter/ipt_LOG.old 2008-05-31 19:40:38.000000000 +0100
+++ net/ipv4/netfilter/ipt_LOG.new 2008-06-01 16:29:25.000000000 +0100
@@ -56,6 +56,8 @@
+
printk(“LEN=%u TOS=0x%02X PREC=0x%02X TTL=%u ID=%u “,
You can also disable logging of attributes that you consider useless since the
messages are very detailed and hard to read. Here is what you can get with the
patched LOG target:
restore-mark: IN=eth0 OUT= SRC=192.168.157.3 DST=172.16.1.100 FWMARK=1 ID=53071 SPT=44426 DPT=80
restore-mark: IN=eth0 OUT= SRC=192.168.157.3 DST=172.16.1.100 FWMARK=1 ID=53072 SPT=44426 DPT=80
restore-mark: IN=eth0 OUT= SRC=192.168.157.3 DST=172.16.1.100 FWMARK=1 ID=53073 SPT=44426 DPT=80
restore-mark: IN=eth0 OUT= SRC=192.168.157.3 DST=172.16.1.100 FWMARK=1 ID=53074 SPT=44426 DPT=80
restore-mark: IN=eth0 OUT= SRC=192.168.157.3 DST=172.16.1.100 FWMARK=1 ID=53075 SPT=44426 DPT=80
Use a network sniffer
You can use a sniffer such as tcpdump (console) or wireshark (graphical mode) to check what packets are transmitted and with which attributes.
Routing configuration on Saturn
Even if 95% of the networking configuration has to be done on the router
(Jupiter) don’t forget to set a route to Neptune on Saturn. It may be necessary
if Jupiter is not the default gateway on Saturn. Here is what to do on Saturn:
ip route add 176.16.1.100 via 192.168.157.253
完整的负载均衡脚本
#!/bin/bash echo 1 >| /proc/sys/net/ipv4/ip_forward echo 0 >| /proc/sys/net/ipv4/conf/all/rp_filter # flush all iptables entries iptables -t filter -F iptables -t filter -X iptables -t nat -F iptables -t nat -X iptables -t mangle -F iptables -t mangle -X iptables -t filter -P INPUT ACCEPT iptables -t filter -P OUTPUT ACCEPT iptables -t filter -P FORWARD ACCEPT # initialise chains that will do the work and log the packets iptables -t mangle -N CONNMARK1 iptables -t mangle -A CONNMARK1 -j MARK --set-mark 1 iptables -t mangle -A CONNMARK1 -j CONNMARK --save-mark iptables -t mangle -A CONNMARK1 -j LOG --log-prefix 'iptables-mark1: ' --log-level info iptables -t mangle -N CONNMARK2 iptables -t mangle -A CONNMARK2 -j MARK --set-mark 2 iptables -t mangle -A CONNMARK2 -j CONNMARK --save-mark iptables -t mangle -A CONNMARK2 -j LOG --log-prefix 'iptables-mark2: ' --log-level info iptables -t mangle -N RESTOREMARK iptables -t mangle -A RESTOREMARK -j CONNMARK --restore-mark iptables -t mangle -A RESTOREMARK -j LOG --log-prefix 'restore-mark: ' --log-level info iptables -t nat -N SNAT1 iptables -t nat -A SNAT1 -j LOG --log-prefix 'snat-to-10.37.1.253: ' --log-level info iptables -t nat -A SNAT1 -j SNAT --to-source 10.37.1.253 iptables -t nat -N SNAT2 iptables -t nat -A SNAT2 -j LOG --log-prefix 'snat-to-10.37.2.253: ' --log-level info iptables -t nat -A SNAT2 -j SNAT --to-source 10.37.2.253 # restore the fwmark on packets that belong to an existing connection iptables -t mangle -A PREROUTING -i eth0 -p tcp \ -m state --state ESTABLISHED,RELATED -j RESTOREMARK # if the mark is zero it means the packet does not belong to an existing connection iptables -t mangle -A PREROUTING -p tcp -m state --state NEW \ -m statistic --mode nth --every 2 --packet 0 -j CONNMARK1 iptables -t mangle -A PREROUTING -p tcp -m state --state NEW \ -m statistic --mode nth --every 2 --packet 1 -j CONNMARK2 iptables -t nat -A POSTROUTING -o eth1 -j SNAT1 iptables -t nat -A POSTROUTING -o eth2 -j SNAT2 if ! cat /etc/iproute2/rt_tables | grep -q '^251' then echo '251 rt_link1' >> /etc/iproute2/rt_tables fi if ! cat /etc/iproute2/rt_tables | grep -q '^252' then echo '252 rt_link2' >> /etc/iproute2/rt_tables fi ip route flush table rt_link1 2>/dev/null ip route add table rt_link1 default dev eth1 ip route flush table rt_link2 2>/dev/null ip route add table rt_link2 default dev eth2 ip rule del from all fwmark 0x1 lookup rt_link1 2>/dev/null ip rule del from all fwmark 0x2 lookup rt_link2 2>/dev/null ip rule del from all fwmark 0x2 2>/dev/null ip rule del from all fwmark 0x1 2>/dev/null ip rule add fwmark 1 table rt_link1 ip rule add fwmark 2 table rt_link2 ip route flush cache
调试脚本
#!/bin/bash INFINCOMING=wlp4s0 INFOUTGOING1=enp0s25 INFOUTGOING2=wlp0s20u9u4u1 echo 1 >| /proc/sys/net/ipv4/ip_forward echo 0 >| /proc/sys/net/ipv4/conf/all/rp_filter # flush all iptables entries iptables -t filter -F iptables -t filter -X iptables -t nat -F iptables -t nat -X iptables -t mangle -F iptables -t mangle -X iptables -t filter -P INPUT ACCEPT iptables -t filter -P OUTPUT ACCEPT iptables -t filter -P FORWARD ACCEPT if [ "$1" == "stop" ]; then # iptables -t nat -A POSTROUTING -o echo "iptables stoped." exit fi # initialise chains that will do the work and log the packets iptables -t mangle -N CONNMARK1 iptables -t mangle -A CONNMARK1 -j MARK --set-mark 1 iptables -t mangle -A CONNMARK1 -j CONNMARK --save-mark iptables -t mangle -A CONNMARK1 -j LOG --log-prefix 'iptables-mark1: ' --log-level info iptables -t mangle -N CONNMARK2 iptables -t mangle -A CONNMARK2 -j MARK --set-mark 2 iptables -t mangle -A CONNMARK2 -j CONNMARK --save-mark iptables -t mangle -A CONNMARK2 -j LOG --log-prefix 'iptables-mark2: ' --log-level info iptables -t mangle -N RESTOREMARK iptables -t mangle -A RESTOREMARK -j CONNMARK --restore-mark iptables -t mangle -A RESTOREMARK -j LOG --log-prefix 'restore-mark: ' --log-level info # restore the fwmark on packets that belong to an existing connection iptables -t mangle -A PREROUTING -i ${INFINCOMING} -p tcp \ -m state --state ESTABLISHED,RELATED -j RESTOREMARK # if the mark is zero it means the packet does not belong to an existing connection iptables -t mangle -A PREROUTING -p tcp -m state --state NEW \ -m statistic --mode nth --every 2 --packet 0 -j CONNMARK1 iptables -t mangle -A PREROUTING -p tcp -m state --state NEW \ -m statistic --mode nth --every 2 --packet 1 -j CONNMARK2 iptables -t nat -N SNAT1 iptables -t nat -A SNAT1 -j LOG --log-prefix 'snat-to-10.37.1.253: ' --log-level info iptables -t nat -A SNAT1 -j SNAT --to-source 10.37.1.253 iptables -t nat -N SNAT2 iptables -t nat -A SNAT2 -j LOG --log-prefix 'snat-to-10.37.2.253: ' --log-level info iptables -t nat -A SNAT2 -j SNAT --to-source 10.37.2.253 iptables -t nat -A POSTROUTING -o ${INFOUTGOING1} -j SNAT1 iptables -t nat -A POSTROUTING -o ${INFOUTGOING2} -j SNAT2 if ! cat /etc/iproute2/rt_tables | grep -q '^251' then echo '251 rt_link1' >> /etc/iproute2/rt_tables fi if ! cat /etc/iproute2/rt_tables | grep -q '^252' then echo '252 rt_link2' >> /etc/iproute2/rt_tables fi ip route flush table rt_link1 2>/dev/null ip route add table rt_link1 default dev ${INFOUTGOING1} ip route flush table rt_link2 2>/dev/null ip route add table rt_link2 default dev ${INFOUTGOING2} ip rule del from all fwmark 0x1 lookup rt_link1 2>/dev/null ip rule del from all fwmark 0x2 lookup rt_link2 2>/dev/null ip rule del from all fwmark 0x2 2>/dev/null ip rule del from all fwmark 0x1 2>/dev/null ip rule add fwmark 1 table rt_link1 ip rule add fwmark 2 table rt_link2 ip route flush cache
参考文献
Load balancing using iptables with connmark.
OpenWRT/linux多WAN带宽叠加使用iptables标记策略路由负载均衡。