This article describes the configuration of a Linux gateway for balancing traffic between channels of different providers.


The result achieved in this manual differs from the result of
such manuals : the same external IP address is used for each client, which eliminates problems with Internet services that are not ready to change the client's IP address in one session.
Problems
For an ordinary consumer who does not have any of his own block of addresses and does not participate in the exchange of routes at the level of telecom operators, access to the Internet is possible only from those addresses that each Internet provider provides for each channel. This means that any connection of several channels from “domestic” Internet providers to a single node requires not only a choice between different upstream gateways, but also a choice of the correct outgoing address for communicating with them.
This state of affairs runs counter to how the standard routing mechanism works. In general, the route in the routing table depends only on the destination address and does not change the sender's address. Therefore, the first task that needs to be solved is to organize the correspondence of the sending interface, the gateway and the outgoing address when forwarding packets from clients.
')
The second problem to be solved is how to intelligently distribute the load from clients.
Balancing using multipath routes (as in the link at the beginning of the article) is carried out at the destination address and does not always look like an attractive solution. Where it would be worthwhile to have the same external address, the client may have a different session in one session and vice versa - when accessing popular resources, all clients will use one common channel.
Balancing at the sender address can be a compromise between compatibility and granularity of distribution. Each computer in the local network will always have the same external address and with a considerable number of computers in the network we will get a tolerable load distribution across the channels. Such a division can be achieved if we form hashes from local addresses, divide the entire space of hashes in proportion to the weights of the channels and use the value of this hash to select the channel.
Instruments
In the Linux kernel, it is possible to use several different tables at the same time, depending on what criteria the package meets. Often this mechanism is called policy based routing (PBR). This mechanism is controlled through the iproute2 toolkit.
Somewhat simplifying the formulation of the problem, one can say: all that is required of us to distinguish between routes, interfaces and outgoing addresses is to create for each provider an additional routing table that looks as if this channel of this provider were the only one. Then you need to add rules (the same routing policies) that trigger the appropriate routing table for the corresponding traffic.
As for balancing by sender, for this you can use the kernel network filter - netfilter (iptables). With the HMARK action, we will mark the packet with a hash of the address. According to the fwmark criterion in the routing rules (ip rule command), we will forward the packet to the desired routing table. iproute2 and iptables play well in pairs here.
Example
As an example, I’ll take a Debian based gateway that provides Internet access through three channels from two different providers. Such an example was chosen specifically, as it considers a delicate situation when the same upstream gateway of one provider is accessible through 2 different wires. The described configuration will have a similar appearance in all other debian-like operating systems, including Ubuntu.
The gateway has 4 working interfaces:
Int. | Description | Capacity | Address | Gateway |
---|
eth0 | the local network | | 10.0.0.1/16 | - |
eth1 | first channel of the first provider | 100 Mbps | 100.1.1.92/24 | 100.1.1.1 |
eth2 | the only channel from the second provider | 80 Mbps | 200.2.2.22/24 | 200.2.2.1 |
eth3 | second channel of the first provider | 100 Mbps | 100.1.1.93/24 | 100.1.1.1 |
Each channel is connected via a normal ethernet with a static address.
Initial state
Suppose we have already configured access through one channel. The / etc / network / interfaces file is:
/etc/network.interfaces, 3 interfaces, 1 main# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
source /etc/network/interfaces.d/*
# The loopback network interface
auto lo
iface lo inet loopback
auto eth0
iface eth0 inet static
address 10.0.0.1
netmask 255.255.0.0
auto eth1
iface eth1 inet static
address 100.1.1.92
gateway 100.1.1.1
netmask 255.255.255.0
auto eth2
iface eth2 inet static
address 200.2.2.22
# gateway 200.2.2.1 #correct gateway value, but commented out to avoid routing conflicts
netmask 255.255.255.0
auto eth3
iface eth3 inet dhcp
address 100.1.1.93
# gateway 100.1.1.1 # correct gateway value, but commented out to avoid routing conflicts
netmask 255.255.255.0
As you can see, only one of the interfaces has a default route.
In /etc/sysctl.conf, the value of net.ipv4.ip_forward is set to 1. The iptables-persistent package is installed and the contents of the /etc/iptables/rules.v4 file are:
/etc/iptables/rules.v4, NAT rules and nothing more# Generated by iptables-save v1.4.21 on Tue Feb 30 13:14:06 2016
*mangle
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
COMMIT
# Completed on Tue Feb 30 13:14:06 2016
# Generated by iptables-save v1.4.21 on Tue Feb 30 13:14:06 2016
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A FORWARD -s 10.0.0.0/16 -i eth0 -j ACCEPT # Proposed by ValdikSS due to security clues
-A FORWARD -d 10.0.0.0/16 -o eth0 -j ACCEPT # --//--
-A FORWARD -j DROP # --//--
COMMIT
# Completed on Tue Feb 30 13:14:06 2016
# Generated by iptables-save v1.4.21 on Tue Feb 30 13:14:06 2016
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 10.0.0.0/8 -o eth1 -j MASQUERADE
-A POSTROUTING -s 10.0.0.0/8 -o eth2 -j MASQUERADE
-A POSTROUTING -s 10.0.0.0/8 -o eth3 -j MASQUERADE
COMMIT
# Completed on Tue Feb 30 13:14:06 2016
Only three rules for address translation from packets originating through three external interfaces. It does not matter to use a masquerade, but I stopped at it. Since the gateway is currently configured on only one interface, only the first rule actually works.
Routing rules
Now the most difficult: we will solve the first problem of matching addresses, interfaces and gateways. We will prepare three additional routing tables that we will use for different providers.
Let's start with the optional step - in order not to mention the routing tables everywhere by their number, let's get them in the file of correspondences of the numbers and names of the tables / etc / iproute2 / rt_tables:
/ etc / iproute2 / rt_tables#
# reserved values
#
255 local
254 main
253 default
0 unspec
#
# local
#
#1 inr.ruhep
10 Provider1_Cable1
20 Provider2
30 Provider1_Cable2
So far, quite simple. Now fill these tables with routes. Let each table for each external channel contain all routes of all interfaces, except routes through other external interfaces. When picking up each external interface, add a rule that instructs to use the specified additional routing table if the sender address is equal to the address on this interface.
/ etc / network / interfaces: routes in additional tables and routing rules to the source address# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
source /etc/network/interfaces.d/*
# The loopback network interface
auto lo
iface lo inet loopback
up ip route add 127.0.0.0/8 dev lo table Provider1_Cable1
up ip route add 127.0.0.0/8 dev lo table Provider2
up ip route add 127.0.0.0/8 dev lo table Provider1_Cable2
auto eth0
iface eth0 inet static
address 10.0.0.1
netmask 255.255.0.0
up ip route add 10.0.0.0/16 dev eth0 table Provider1_Cable1
up ip route add 10.0.0.0/16 dev eth0 table Provider2
up ip route add 10.0.0.0/16 dev eth0 table Provider1_Cable2
auto eth1
iface eth1 inet static
address 100.1.1.92
gateway 100.1.1.1
netmask 255.255.255.0
up ip route add 100.1.1.0/24 dev eth1 table Provider1_Cable1
up ip route add default dev eth1 via 100.1.1.1 table Provider1_Cable1
up ip rule add from 100.1.1.92 table Provider1_Cable1
auto eth2
iface eth2 inet static
address 200.2.2.22
# gateway 200.2.2.1 #correct gateway value, but commented out to avoid routing conflicts
netmask 255.255.255.0
up ip route add 200.2.2.22/24 dev eth2 table Provider2
up ip route add default dev eth2 via 200.2.2.1 table Provider2
up ip rule add from 200.2.2.22 table Provider2
auto eth3
iface eth3 inet dhcp
address 100.1.1.93
# gateway 100.1.1.1 # correct gateway value, but commented out to avoid routing conflicts
netmask 255.255.255.0
up ip route add 100.1.1.0/24 dev eth3 table Provider1_Cable2
up ip route add default dev eth3 via 100.1.1.1 table Provider1_Cable2
up ip rule add from 100.1.1.93 table Provider1_Cable2
Note: the main routing table still gets only one gateway - all the gateway directives, except for one, are commented out.
Here, additional routes and rules are decorated with up directives that simply execute the command when raising the interface. Commands for adding routes and rules are grouped under the interfaces through which they can be implemented - this is most clearly seen in the example of the lo interface.
After restarting the network, we can notice how the output of the list of rules in the ip rule list command has changed:
ip rule list# ip ru li
0: from all lookup local
32763: from 100.1.1.93 lookup Provider1_Cable2
32764: from 200.2.2.22 lookup Provider2
32765: from 100.1.1.92 lookup Provider1_Cable1
32766: from all lookup main
32767: from all lookup default
The corresponding table can be viewed with the command “ip ro li table XXX”.
Already in this position, the server is ready to use all interfaces at once. We will verify this by trying to use all the interfaces one by one and checking the address observed by the outside:
checking external addresses through different interfaces# curl --interface 100.1.1.92 http://canihazip.com/s ; echo
100.1.1.92
# curl --interface 100.1.1.93 http://canihazip.com/s ; echo
100.1.1.93
# curl --interface 200.2.2.22 http://canihazip.com/s ; echo
200.2.2.22
If the external interfaces have real “white” addresses, then you can try to join the server using any address — the server will communicate through each of them through the correct channel and the correct provider. If so, then the most difficult thing behind.
Balancing
In my example there are two channels of 100 Mb / s and one 80 Mb / s. In order to divide the load equally between them, it is enough for me to divide it into 14 parts and send 5 parts to two 100-megabit channels and 4 parts to an 80-megabit channel. Add a rule to the firewall, marking the packet with numbers from 10,000 to 10,013, depending on the outgoing IP address:
# iptables -t mangle -A PREROUTING -s 10.0.0.0/16 -j HMARK --hmark-tuple src --hmark-offset 10000 --hmark-mod 14 --hmark-rnd 0xfeedcafe
# /etc/init.d/netfilter-persistent save
Simple enough. Parameters and values ​​speak for themselves. It now remains to send traffic, marked by different numbers, through different interfaces. As a result, / etc / network / interfaces takes the form:
/ etc / network / interfaces final# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
source /etc/network/interfaces.d/*
# The loopback network interface
auto lo
iface lo inet loopback
up ip route add 127.0.0.0/8 dev lo table Provider1_Cable1
up ip route add 127.0.0.0/8 dev lo table Provider2
up ip route add 127.0.0.0/8 dev lo table Provider1_Cable2
auto eth0
iface eth0 inet static
address 10.0.0.1
netmask 255.255.0.0
up ip route add 10.0.0.0/16 dev eth0 table Provider1_Cable1
up ip route add 10.0.0.0/16 dev eth0 table Provider2
up ip route add 10.0.0.0/16 dev eth0 table Provider1_Cable2
auto eth1
iface eth1 inet static
address 100.1.1.92
gateway 100.1.1.1
netmask 255.255.255.0
up ip route add 100.1.1.0/24 dev eth1 table Provider1_Cable1
up ip route add default dev eth1 via 100.1.1.1 table Provider1_Cable1
up ip rule add from 100.1.1.92 table Provider1_Cable1
up ip rule add from 10.0.0.0/8 fwmark 10000 table Provider1_Cable1
up ip rule add from 10.0.0.0/8 fwmark 10003 table Provider1_Cable1
up ip rule add from 10.0.0.0/8 fwmark 10006 table Provider1_Cable1
up ip rule add from 10.0.0.0/8 fwmark 10009 table Provider1_Cable1
up ip rule add from 10.0.0.0/8 fwmark 10012 table Provider1_Cable1
auto eth2
iface eth2 inet static
address 200.2.2.22
# gateway 200.2.2.1 #correct gateway value, but commented out to avoid routing conflicts
netmask 255.255.255.0
up ip route add 200.2.2.22/24 dev eth2 table Provider2
up ip route add default dev eth2 via 200.2.2.1 table Provider2
up ip rule add from 200.2.2.22 table Provider2
up ip rule add from 10.0.0.0/8 fwmark 10002 table Provider2
up ip rule add from 10.0.0.0/8 fwmark 10005 table Provider2
up ip rule add from 10.0.0.0/8 fwmark 10008 table Provider2
up ip rule add from 10.0.0.0/8 fwmark 10011 table Provider2
auto eth3
iface eth3 inet dhcp
address 100.1.1.93
# gateway 100.1.1.1 # correct gateway value, but commented out to avoid routing conflicts
netmask 255.255.255.0
up ip route add 100.1.1.0/24 dev eth3 table Provider1_Cable2
up ip route add default dev eth3 via 100.1.1.1 table Provider1_Cable2
up ip rule add from 100.1.1.93 table Provider1_Cable2
up ip rule add from 10.0.0.0/8 fwmark 10001 table Provider1_Cable2
up ip rule add from 10.0.0.0/8 fwmark 10004 table Provider1_Cable2
up ip rule add from 10.0.0.0/8 fwmark 10007 table Provider1_Cable2
up ip rule add from 10.0.0.0/8 fwmark 10010 table Provider1_Cable2
up ip rule add from 10.0.0.0/8 fwmark 10013 table Provider1_Cable2
For each interface, we added lines like “up ip rule add from 10.0.0.0/8 fwmark MARK table TABLE”. Each of them sends packets with a corresponding marking for routing to the specified table. In my example, numbers are intermixed between interfaces for the sake of uniformity.
Everything!
Optionally, scripts can be added to such a routing scheme, detecting the drop in one of the channels and rebalancing the load with this in mind. However, implementation details depend on taste and it is better to leave it to the reader.