Linux Load Balancing

Note: you must use a 2.4.x kernel or a 2.6.x kernel of at least version 2.6.10-rc2. MASQUERADE will not work as it needs to otherwise. There is a patch you can apply to older 2.6 kernels if you need.

You can wget either of these scripts if that's more convenient for you:

wget http://imbezol.org/makeroutes-1.4.1.sh
wget http://imbezol.org/makeiptables.sh
				

makeroutes-1.4.1.sh
#!/bin/bash
#    makeroutes.sh - load balance two or more internet connections
#
#    This program is free software; you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation; either version 2 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
#    Written by Travis Morgan
#    Basic idea taken from a script by Robert Kurjata Sep, 2003.
#    Jul    2004 - 1.0
#    May 21 2004 - 1.1   - greatly modularized
#    Oct  5 2005 - 1.2   - include provisions for static addresses and improve network info detection
#    Dec 12 2005 - 1.3   - allow external interfaces that are not load balanced
#    Feb  1 2006 - 1.4   - removed Telus Interface Declarations
#    Feb  5 2006 - 1.4.1 - start marking rules at 5 instead of 4 so iptables rules don't have to be adjusted

#########################################################################
####  User defined variables                                         ####
#########################################################################

# define which interfaces will be load balanced
#LB_IFS=( eth0 eth1 )
#LB_IFS=( eth1 )
LB_IFS=( eth0 )
# weights of the load balanced interfaces - lower means more traffic
#WEIGHTS=( 1 2 )
WEIGHTS=( 1 )

# define all EXTERNAL interfaces to be configured 
# internal interfaces should not appear in this because they don't get their own routing table.. they instead hit the multipath table
#EXT_IFS=( eth0 eth1 )
EXT_IFS=( eth0 )
#EXT_IFS=( eth2 )

# define the interfaces that need to get their address from dhcp
#DHCP_IFS=( eth1 )
# define the interfaces that need static addresses set
STATIC_IFS=( eth0 )
# define the ips for static interfaces
STATIC_IPS=( 216.194.110.140 )
# define the netmasks for static interfaces
STATIC_NMS=( 255.255.255.0 )
# define the broadcasts for static interfaces
STATIC_BCS=( 216.194.110.255 )
# define the gateways (if any) for static interfaces
STATIC_GWS=( 216.194.110.1 ) 

# you can adjust the coloring with these
COLOR=""
NOCOLOR=""

# adjust these paths to be proper for your system
IP=/sbin/ip
WHATMASK=/usr/bin/whatmask
DHCPCD=/sbin/dhcpcd
DHCPCD_OPTS="-n"
IFCONFIG=/sbin/ifconfig
ROUTE=/sbin/route

LOWMARK=4 # the lowest interface will be seeing packets marked with 0x4

# LOGFILE="2>>/var/log/makeroutes.log"	# log to a file
LOGFILE=""								# log to stdout and stderr

#########################################################################
####  Get dhcp on external interfaces that require it                ####
#########################################################################
echo "${COLOR}Getting DHCP on ${#DHCP_IFS[@]} interfaces..${NOCOLOR}"
for ((i=0; $i < ${#DHCP_IFS[@]} ; i++)); do
	${DHCPCD} ${DHCPCD_OPTS} ${DHCP_IFS[$i]}
done

#########################################################################
####  Set the static address on interfaces that require it           ####
#########################################################################
echo "${COLOR}Setting static addresses on ${#STATIC_IFS[@]} interfaces..${NOCOLOR}"
for ((i=0; $i < ${#STATIC_IFS[@]} ; i++)); do
	if [[ ! ${STATIC_IFS[$i]} =~ ':' ]]
	then
		echo ${IFCONFIG} ${STATIC_IFS[$i]} down
		${IFCONFIG} ${STATIC_IFS[$i]} down
	fi
	echo ${IFCONFIG} ${STATIC_IFS[$i]} ${STATIC_IPS[$i]} netmask ${STATIC_NMS[$i]} broadcast ${STATIC_BCS[$i]}
	${IFCONFIG} ${STATIC_IFS[$i]} ${STATIC_IPS[$i]} netmask ${STATIC_NMS[$i]} broadcast ${STATIC_BCS[$i]}
done

#########################################################################
####  Gather network information                                     ####
#########################################################################
echo "${COLOR}The following ${#EXT_IFS[@]} interfaces have been configured..${NOCOLOR}"
for ((i=0; $i < ${#EXT_IFS[@]} ; i++)) ; do
	# we must check if it's dhcp or static
	isdhcp="false"
	for ((j=0; $j < ${#DHCP_IFS[@]} ; j++)) ; do
		if [ "${DHCP_IFS[$j]}" = "${EXT_IFS[$i]}" ] ; then
			isdhcp="true";
		fi
	done
	if [ "${isdhcp}" = "true" ] ; then
		# get ip address
		EXT_IPS[$i]=`${IFCONFIG} | grep ${EXT_IFS[$i]}\  -A 1 | tail -n 1| awk -F: {'print $2'} | awk {'print $1'}`
		# get netmask
		EXT_NMS[$i]=`${ROUTE} -n | grep ${EXT_IFS[$i]} | grep -v -e '^0.0.0.0' | awk {'print $3'}`
		# convert netmask to / notation
		EXT_NMS[$i]=`${WHATMASK} ${EXT_NMS[$i]} | grep CIDR | awk -F/ {'print $2'}`
		# calculate gateway
		EXT_GWS[$i]=`${WHATMASK} ${EXT_IPS[$i]}/${EXT_NMS[$i]} | grep "First Usable" | sed -e 's/.*: \([0-9].*\)/\1/'`
	else
		# find the index number in the static array
		static_index=0
		for ((j=0; $j < ${#STATIC_IFS[@]} ; j++)) ; do
			if [ "${STATIC_IFS[$j]}" = "${EXT_IFS[$i]}" ] ; then
				static_index=$j
			fi
		done
		# now we can add the interface info to EXT
		EXT_IPS[$i]=${STATIC_IPS[$static_index]}
		# convert and assign the netmask in / notation
		EXT_NMS[$i]=`${WHATMASK} ${STATIC_NMS[$static_index]} | grep CIDR | awk -F/ '{print $2}'`
		EXT_GWS[$i]=${STATIC_GWS[$static_index]}
	fi
	EXT_NWS[$i]=`${WHATMASK} ${EXT_IPS[$i]}/${EXT_NMS[$i]} | grep ^Network | sed -e 's/.*: \(.*\)/\1/'`
	echo -n "${COLOR}*${NOCOLOR} ${EXT_IFS[$i]} ${EXT_IPS[$i]} netmask /${EXT_NMS[$i]}"
	if [ "${EXT_GWS[$i]}" != "" ]
	then
		echo " gw ${EXT_GWS[$i]}"
	else
		echo
	fi
done

#########################################################################
####  Remove default routes set by dhcp                              ####
#########################################################################
echo "${COLOR}Removing default routes from table main..${NOCOLOR}"
${IP} route show table main | grep ^default | while read LINE ; do 
	echo ${IP} route del "$LINE" table main
	#${IP} route del "$LINE" table main
	echo ${IP} route del default
	${IP} route del default
done

#########################################################################
####  Flush rules and tables                                         ####
#########################################################################
echo "${COLOR}Removing old rules and flushing tables..${NOCOLOR}"
${IP} rule | while read LINE ; do
	prio=`echo $LINE | awk -F\: '{print $1}'`
	if [ $prio -eq 50 ] ; then
		${IP} rule del prio $prio table main ${LOGFILE}
	elif [ $prio -ge 100 ]&&[ $prio -le 120 ] ; then
		${IP} rule del prio $prio ${LOGFILE}
		${IP} route flush table $prio ${LOGFILE}
	elif [ $prio -ge 200 ]&&[ $prio -le 220 ] ; then
		ruleip=`echo $LINE | awk -F\  '{print $3}'`
		${IP} rule del prio $prio from $ruleip table $prio ${LOGFILE}
		${IP} route flush table $prio ${LOGFILE}
	elif [ $prio -eq 221 ] ; then
		${IP} rule del prio $prio table $prio ${LOGFILE}
		${IP} route flush table $prio ${LOGFILE}
	fi
done

#########################################################################
####  Create new rules for marked packets                            ####
#########################################################################
echo "${COLOR}Building new route rules and tables..${NOCOLOR}"
echo "${COLOR}*${NOCOLOR} rule main"
${IP} rule add prio 50 table main ${LOGFILE}

for (( i=0; $i < ${#EXT_IFS[@]} ; i++ )); do
	# we must check if it's supposed to be load balanced
	isbalanced="false"
	for ((j=0; $j < ${#LB_IFS[@]} ; j++)) ; do
		if [ "${LB_IFS[$j]}" = "${EXT_IFS[$i]}" ] ; then
			isbalanced="true";
		fi
	done
	if [ "$isbalanced" == "true" ]
	then
		echo "${COLOR}*${NOCOLOR} rule/table $(( 100 + $i )) (force mark $(( $LOWMARK + $i )) packets to ${EXT_IFS[$i]})"
		${IP} rule add prio $(( 100 + $i )) fwmark $(( $LOWMARK + $i )) table $(( 100 + $i )) ${LOGFILE}
		${IP} route show table main | grep -Ev ^default | while read LINE ; do 
			${IP} route add table $(( 100 + $i )) $LINE ${LOGFILE}
		done
		${IP} route add table $(( 100 + $i )) default via ${EXT_IPS[$i]} ${LOGFILE}
	fi
done

#########################################################################
####  Force traffic coming from an address in one of our             ####
####  external link scopes to go out the correct interface           ####
#########################################################################
for (( i=0; $i < ${#EXT_IFS[@]} ; i++ )); do
	echo "${COLOR}*${NOCOLOR} rule/table $(( 200 + $i )) (${EXT_IFS[$i]})"
	${IP} rule add prio $(( 200 + $i )) from ${EXT_NWS[$i]}/${EXT_NMS[$i]} table $(( 200 + $i )) ${LOGFILE}
	${IP} route add default `if [ "${EXT_GWS[$i]}" != "" ] ; then echo via ${EXT_GWS[$i]} ; fi` dev `echo ${EXT_IFS[$i]}|awk -F: '{print $1}'` src ${EXT_IPS[$i]} proto static table $(( 200 + $i )) ${LOGFILE}
	${IP} route append prohibit default table $(( 200 + $i )) metric 1 proto static ${LOGFILE}
done

#############################################################################
####  Create Multipath Table (for inside traffic bound for the internet) ####
#############################################################################
echo "${COLOR}*${NOCOLOR} 221 (multipath)"
${IP} rule add prio 221 table 221 ${LOGFILE}
multistring=""
for (( i=0; $i < ${#EXT_IFS[@]} ; i++ )); do
	# we must check if it's supposed to be load balanced
	isbalanced="false"
	for ((j=0; $j < ${#LB_IFS[@]} ; j++)) ; do
		if [ "${LB_IFS[$j]}" = "${EXT_IFS[$i]}" ] ; then
			isbalanced="true";
		fi
	done
	if [ "$isbalanced" == "true" ]
	then
		multistring="${multistring} nexthop via ${EXT_GWS[$i]} dev ${EXT_IFS[$i]} weight ${WEIGHTS[$i]}"
	fi
done
${IP} route add default table 221 proto static ${multistring} ${LOGFILE}

#########################################################################
####  Flush routing cache and test connectivity                      ####
#########################################################################
echo "${COLOR}Flushing route cache..${NOCOLOR}"			
${IP} route flush cache ${LOGFILE}

echo "${COLOR}Pinging gateways..${NOCOLOR}"
for (( i=0; $i < ${#EXT_IFS[@]} ; i++ )); do
	if [ "${EXT_GWS[$i]}" != "" ]
	then
		echo "${COLOR}*${NOCOLOR} `ping -c 1 ${EXT_GWS[$i]} | grep 'bytes\ from'`"
	fi
done
							


makeiptables.sh
#!/bin/bash

# flush all tables
iptables -t nat -F
iptables -t mangle -F
iptables -t -F

# delete all user defined chains
iptables -t nat -X
iptables -t mangle -X
iptables -t -X

# nat rules
iptables -t nat -P PREROUTING ACCEPT
iptables -t nat -P POSTROUTING ACCEPT
iptables -t nat -P OUTPUT ACCEPT

iptables -t nat -A PREROUTING -i eth0 -p tcp -m tcp --dport 22 -j DNAT --to-destination 10.1.2.20
iptables -t nat -A PREROUTING -i eth2 -p tcp -m tcp --dport 22 -j DNAT --to-destination 10.1.2.20
iptables -t nat -A PREROUTING -i eth1 -p tcp -m tcp --dport 22 -j DNAT --to-destination 10.1.2.20
iptables -t nat -A POSTROUTING -s 10.1.2.0/255.255.255.0 -d ! 10.1.0.0/255.255.0.0 -o eth0 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 10.1.2.0/255.255.255.0 -d ! 10.1.0.0/255.255.0.0 -o eth1 -j MASQUERADE
iptables -t nat -A POSTROUTING -s 10.1.2.0/255.255.255.0 -d ! 10.1.0.0/255.255.0.0 -o eth2 -j MASQUERADE

# mangle rules
iptables -t mangle -P PREROUTING ACCEPT
iptables -t mangle -P INPUT ACCEPT
iptables -t mangle -P FORWARD ACCEPT
iptables -t mangle -P OUTPUT ACCEPT
iptables -t mangle -P POSTROUTING ACCEPT

iptables -t mangle -N MARK_RULES
iptables -t mangle -N SHAW
iptables -t mangle -N TB52
iptables -t mangle -N TB24

iptables -t mangle -A PREROUTING -i eth4 -m state --state NEW -j MARK_RULES
iptables -t mangle -A PREROUTING -i eth4 -m state --state RELATED,ESTABLISHED -j CONNMARK --restore-mark
iptables -t mangle -A PREROUTING -i eth0 -j CONNMARK --set-mark 0x4
iptables -t mangle -A PREROUTING -i eth1 -j CONNMARK --set-mark 0x5
iptables -t mangle -A PREROUTING -i eth2 -j CONNMARK --set-mark 0x6
iptables -t mangle -A PREROUTING -d 192.168.1.0/255.255.255.0 -j MARK --set-mark 0x6
iptables -t mangle -A PREROUTING -d 192.168.2.0/255.255.255.0 -j MARK --set-mark 0x4
iptables -t mangle -A OUTPUT -s 1.2.3.2 -j MARK --set-mark 0x4
iptables -t mangle -A OUTPUT -s 3.4.5.2 -j MARK --set-mark 0x5
iptables -t mangle -A OUTPUT -s 2.3.4.2 -j MARK --set-mark 0x6
iptables -t mangle -A OUTPUT -s 192.168.2.0/255.255.255.0 -j MARK --set-mark 0x6
iptables -t mangle -A OUTPUT -s 192.168.1.0/255.255.255.0 -j MARK --set-mark 0x4
iptables -t mangle -A POSTROUTING -o eth0 -j CONNMARK --set-mark 0x4
iptables -t mangle -A POSTROUTING -o eth1 -j CONNMARK --set-mark 0x5
iptables -t mangle -A POSTROUTING -o eth2 -j CONNMARK --set-mark 0x6
iptables -t mangle -A MARK_RULES -s 10.1.2.0/255.255.0.0 -d 5.6.7.0/255.255.255.0 -j TB52
iptables -t mangle -A MARK_RULES -s 10.1.2.0/255.255.0.0 -d 6.7.8.0/255.255.255.0 -j SHAW
iptables -t mangle -A MARK_RULES -j RETURN
iptables -t mangle -A SHAW -j MARK --set-mark 0x5
iptables -t mangle -A TB24 -j MARK --set-mark 0x6
iptables -t mangle -A TB52 -j MARK --set-mark 0x4

# filter rules
iptables -P INPUT DROP
iptables -P FORWARD ACCEPT
iptables -P OUTPUT ACCEPT

iptables -t filter -A INPUT -p icmp -j ACCEPT
iptables -t filter -A INPUT -s 127.0.0.1 -j ACCEPT
iptables -t filter -A INPUT -p ipv6-frag -j ACCEPT
iptables -t filter -A INPUT -p ipv6-route -j ACCEPT
iptables -t filter -A INPUT -p ipv6 -j ACCEPT
iptables -t filter -A INPUT -p udp -m multiport --dports 500:4500 -j ACCEPT
iptables -t filter -A INPUT -p esp -j ACCEPT
iptables -t filter -A INPUT -p ah -j ACCEPT
iptables -t filter -A INPUT -s 10.1.2.0/255.255.255.0 -i eth4 -j ACCEPT
iptables -t filter -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -t filter -A INPUT -p tcp -m tcp --dport 25 -j ACCEPT
iptables -t filter -A INPUT -p udp -m udp --dport 53 -j ACCEPT
iptables -t filter -A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
iptables -t filter -A INPUT -p udp -m udp --dport 32768 -j ACCEPT
iptables -t filter -A INPUT -p udp -m udp --dport 32769 -j ACCEPT