219 |
jeremy |
1 |
#!/bin/bash
|
328 |
franck |
2 |
# $Id: alcasar-load_balancing.sh 1068 2013-04-10 05:20:10Z franck $
|
221 |
franck |
3 |
|
1068 |
franck |
4 |
# Generic Load balancer for multiple WAN links - version 1.1 (04 Feb 2011)
|
|
|
5 |
# (c) 2011 Pau Oliva Fora - http://pof.eslack.org
|
|
|
6 |
#
|
|
|
7 |
# Licensed under GPLv3 - for full terms see:
|
|
|
8 |
# http://www.gnu.org/licenses/gpl-3.0.html
|
|
|
9 |
#
|
|
|
10 |
# Adapted and debugged (adr et ping -S) by ALCASAR Team (3abtux@alcasar.net)
|
|
|
11 |
# (c) 2013 3abtux - http://www.alcasar.net
|
|
|
12 |
#
|
|
|
13 |
# Specify each WAN link in a separate column, example:
|
|
|
14 |
# In this example we have 3 wan links (vlanXXX interfaces) attached to a single
|
|
|
15 |
# physical interface because we use a vlan-enabled switch between the balancer
|
|
|
16 |
# machine and the ADSL routers we want to balance. The weight parameter should
|
|
|
17 |
# be kept to a low integer, in this case the ADSL line connected to vlan101 and
|
|
|
18 |
# vlan102 is 4Mbps and the ADSL line connected to vlan100 is 8Mbps (twice fast)
|
|
|
19 |
# so the WEIGHT value in vlan100 is 2 because it is two times faster.
|
|
|
20 |
#
|
|
|
21 |
#
|
|
|
22 |
# Modified by ALCASAR team :
|
219 |
jeremy |
23 |
|
672 |
richard |
24 |
|
1068 |
franck |
25 |
###############################
|
|
|
26 |
# MAIN PARAMETERs Configuration
|
|
|
27 |
###############################
|
|
|
28 |
# enable link failover watchdog? set to "yes" or "no".
|
|
|
29 |
WATCHDOG="yes"
|
|
|
30 |
SLEEP="30"
|
219 |
jeremy |
31 |
|
1068 |
franck |
32 |
# space separated list of public IPs to ping in watchdog mode
|
|
|
33 |
# set this to some public ip addresses pingable and always on.
|
|
|
34 |
TESTIPS="8.8.8.8 192.0.32.10"
|
219 |
jeremy |
35 |
|
1068 |
franck |
36 |
# set to 1 when testing, set to 0 when happy with the results
|
|
|
37 |
VERBOSE=1
|
219 |
jeremy |
38 |
|
1068 |
franck |
39 |
# CONFIGURATION ENDS HERE
|
|
|
40 |
###############################
|
219 |
jeremy |
41 |
|
|
|
42 |
|
|
|
43 |
|
1068 |
franck |
44 |
if [ $(whoami) != "root" ]; then
|
|
|
45 |
echo "You must be root to run this!" ; echo ; exit 1
|
219 |
jeremy |
46 |
fi
|
|
|
47 |
|
1068 |
franck |
48 |
# Adapter for ALCASAR project
|
|
|
49 |
CONF_FILE="/usr/local/etc/alcasar.conf"
|
219 |
jeremy |
50 |
|
1068 |
franck |
51 |
# Virtual interfaces creating
|
|
|
52 |
function create_eth () {
|
|
|
53 |
routecmd="ip route replace default scope global"
|
|
|
54 |
NBIFACE=`grep "^WAN" $CONF_FILE | wc -l` # Nbre interfaces virtuelles
|
|
|
55 |
i=0
|
|
|
56 |
while [ $i -le $NBIFACE ]
|
|
|
57 |
do
|
|
|
58 |
INT="WAN$i"
|
|
|
59 |
echo $INT
|
|
|
60 |
ACTIVE=`grep "$INT=" $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $1}'` # Active
|
|
|
61 |
WT=`grep "$INT=" $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $5}'` # WEIGHT
|
|
|
62 |
WT=${WT:-1}
|
|
|
63 |
IP=`grep "$INT=" $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $3}' | cut -d"/" -f1` # @IP
|
937 |
franck |
64 |
|
1068 |
franck |
65 |
if [ $i -ne 0 ]; then
|
|
|
66 |
[ -e /etc/sysconfig/network-scripts/ifcfg-eth0:$i ] && ifdown eth0:$i && rm -f /etc/sysconfig/network-scripts/ifcfg-eth0:$i
|
|
|
67 |
IFACE=`grep "$INT=" $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $2}'` # IFACE
|
|
|
68 |
IP_NET=`grep "^$INT=" $CONF_FILE | awk -F'"' '{print $2}' | awk -F, '{ print $3}'` # IP
|
|
|
69 |
NET="`ipcalc -n $IP_NET | cut -d"=" -f2`/`ipcalc -p $IP_NET|cut -d"=" -f2`"
|
|
|
70 |
GW=`grep "$INT=" $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $4}'` # @GW
|
|
|
71 |
MTU=`grep "$INT=" $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $6}'` # MTU
|
219 |
jeremy |
72 |
|
1068 |
franck |
73 |
# Config eth0:$i (Internet)
|
|
|
74 |
cat <<EOF > /etc/sysconfig/network-scripts/ifcfg-eth0:$i
|
|
|
75 |
DEVICE=$IFACE
|
|
|
76 |
BOOTPROTO=static
|
|
|
77 |
IPADDR=`echo $IP | cut -d"/" -f1`
|
|
|
78 |
NETMASK=`ipcalc -m $IP_NET | cut -d= -f2`
|
|
|
79 |
NETWORK=`ipcalc -n $IP_NET | cut -d= -f2`
|
|
|
80 |
MTU=$MTU
|
|
|
81 |
ONBOOT=yes
|
|
|
82 |
NOZEROCONF=yes
|
|
|
83 |
MII_NOT_SUPPORTED=yes
|
|
|
84 |
IPV6INIT=no
|
|
|
85 |
IPV6TO4INIT=no
|
|
|
86 |
ACCOUNTING=no
|
|
|
87 |
USERCTL=no
|
|
|
88 |
EOF
|
|
|
89 |
echo "ifup eth0:$i"
|
|
|
90 |
ifup eth0:$i
|
|
|
91 |
NET="`ipcalc -n $IP_NET | cut -d"=" -f2`/`ipcalc -p $IP_NET|cut -d"=" -f2`"
|
|
|
92 |
else
|
|
|
93 |
IFACE="eth0"
|
|
|
94 |
IP_NET=`grep "^PUBLIC_IP=" $CONF_FILE | awk -F'=' '{print $2}'` # IP/MSK
|
|
|
95 |
IP=`grep "^PUBLIC_IP=" $CONF_FILE | awk -F= '{ print $2 }' | cut -d"/" -f1` # @IP
|
|
|
96 |
GW=`grep "^GW=" $CONF_FILE | awk -F= '{print $2}'` # @GW
|
|
|
97 |
# MTU=`grep "^PUBLIC_MTU=" $CONF_FILE | awk -F= '{print $2}'` # MTU
|
|
|
98 |
fi # End
|
219 |
jeremy |
99 |
|
1068 |
franck |
100 |
NET="`ipcalc -n $IP_NET | cut -d"=" -f2`/`ipcalc -p $IP_NET|cut -d"=" -f2`"
|
|
|
101 |
if [ "$PARAM" == "add" ]; then
|
|
|
102 |
set -x
|
|
|
103 |
table=$(($i + 1))
|
|
|
104 |
ip route ${PARAM} ${NET} dev ${IFACE} src ${IP} table $table
|
|
|
105 |
ip route ${PARAM} default via ${GW} table $table
|
|
|
106 |
ip rule ${PARAM} from ${IP} table $table
|
|
|
107 |
set +x
|
|
|
108 |
fi
|
|
|
109 |
echo " Iface: ${IFACE}"
|
|
|
110 |
echo " IP: ${IP}"
|
|
|
111 |
echo " IP_NET: ${IP_NET}"
|
|
|
112 |
echo " NET: ${NET}"
|
|
|
113 |
echo " GW: ${GW}"
|
|
|
114 |
echo " Weight: ${WT}"
|
|
|
115 |
echo " MTU : ${MTU}"
|
|
|
116 |
echo
|
|
|
117 |
routecmd="${routecmd} nexthop via ${GW} dev ${IFACE} weight ${WT}"
|
|
|
118 |
i=$(($i + 1))
|
|
|
119 |
done # End While
|
219 |
jeremy |
120 |
|
1068 |
franck |
121 |
if [ "$PARAM" == "add" ]; then
|
|
|
122 |
echo "[] Balanced routing:"
|
|
|
123 |
# suppress default route
|
|
|
124 |
ip route del default scope global
|
|
|
125 |
set -x
|
|
|
126 |
${routecmd}
|
|
|
127 |
set +x
|
|
|
128 |
echo
|
|
|
129 |
fi
|
|
|
130 |
|
|
|
131 |
} # end create_eth
|
219 |
jeremy |
132 |
|
1068 |
franck |
133 |
###########################
|
|
|
134 |
# Fonction virtual Interfaces deleting
|
|
|
135 |
###########################
|
|
|
136 |
delete_eth () {
|
|
|
137 |
IFACE_COUNT=`ls -l /etc/sysconfig/network-scripts/ifcfg-eth0:* | wc -l`
|
|
|
138 |
echo $IFACE_COUNT
|
|
|
139 |
while [ $IFACE_COUNT -ne 0 ]
|
|
|
140 |
do
|
|
|
141 |
i=$IFACE_COUNT
|
|
|
142 |
echo "ifdown eth0:$i"
|
|
|
143 |
ifdown eth0:$i
|
|
|
144 |
rm -f /etc/sysconfig/network-scripts/ifcfg-eth0:$i
|
|
|
145 |
IFACE_COUNT=$(($IFACE_COUNT - 1))
|
|
|
146 |
done
|
|
|
147 |
ip route del default scope global
|
|
|
148 |
# ip route add default gw 192.168.1.1
|
|
|
149 |
}
|
|
|
150 |
|
219 |
jeremy |
151 |
|
1068 |
franck |
152 |
# do not modify below this line unless you know what you're doing :)
|
|
|
153 |
function getvalue() {
|
|
|
154 |
index=$1
|
|
|
155 |
VAR=$2
|
219 |
jeremy |
156 |
|
1068 |
franck |
157 |
n=1
|
|
|
158 |
for f in ${VAR} ; do
|
|
|
159 |
if [ "${n}" == "${index}" ]; then
|
|
|
160 |
echo "$f"
|
|
|
161 |
break
|
|
|
162 |
fi
|
|
|
163 |
n=$(($n++))
|
|
|
164 |
done
|
|
|
165 |
}
|
|
|
166 |
|
|
|
167 |
######################
|
|
|
168 |
# Fonction de FailOver
|
|
|
169 |
######################
|
|
|
170 |
function failover () {
|
|
|
171 |
|
|
|
172 |
echo "[] Watchdog started"
|
|
|
173 |
# 0 == all links ok, 1 == some link down
|
|
|
174 |
STATE=0
|
|
|
175 |
|
|
|
176 |
DOWNCOUNT_BAK=0
|
|
|
177 |
DOWN_BAK=""
|
|
|
178 |
NBIFACE=`grep "^WAN" $CONF_FILE | wc -l` # Nbre interfaces virtuelles
|
|
|
179 |
echo "Nombre interfaces = "$NBIFACE
|
|
|
180 |
WANIFACE[0]="eth0" # eth0 par défaut
|
|
|
181 |
c=0
|
|
|
182 |
while [ $c -le $NBIFACE ]; do
|
|
|
183 |
ITH=(`grep "WAN$c=" $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $2}'`) # IFACE
|
|
|
184 |
echo $ITH
|
|
|
185 |
WANIFACE="${WANIFACE} $ITH"
|
|
|
186 |
echo $WANIFACE
|
|
|
187 |
c=$(($c + 1))
|
|
|
188 |
done
|
|
|
189 |
echo "Liste des interfaces : "${WANIFACE[*]}
|
|
|
190 |
# Failover test
|
|
|
191 |
while : ; do
|
|
|
192 |
|
|
|
193 |
if [ $VERBOSE -eq 1 ]; then
|
|
|
194 |
echo "[] Sleeping, state=$STATE"
|
|
|
195 |
fi
|
|
|
196 |
sleep $SLEEP
|
|
|
197 |
|
|
|
198 |
IFINDEX=1
|
|
|
199 |
DOWN="" # liste des interfaces down
|
|
|
200 |
DOWNCOUNT=0 # nombre d'interface down
|
|
|
201 |
for iface in $WANIFACE ; do
|
|
|
202 |
COUNT=0 # compteur de test
|
|
|
203 |
FAIL=0 # Nombre de fois down
|
|
|
204 |
# Recup de l'adresse IP dynamiquement A tester avec le tableau ... ip=${ETH[$i:2]} basé sur iface=${ETH[$i:1]}
|
|
|
205 |
IP=`ifconfig $iface |grep "inet adr" |cut -f 2 -d ":" |awk '{print $1}'`
|
|
|
206 |
if [ $i -ne 0 ]; then
|
|
|
207 |
GW=`grep "$iface," $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $4}'` # @GW
|
|
|
208 |
WT=`grep "$iface," $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $5}'` # @WT
|
|
|
209 |
else
|
|
|
210 |
GW=`grep "^GW=" $CONF_FILE | awk -F= '{print $2}'` # @GW
|
|
|
211 |
fi
|
|
|
212 |
for TESTIP in $TESTIPS ; do
|
|
|
213 |
COUNT=$(($COUNT + 1))
|
|
|
214 |
ping -W 3 -I $IP -c 1 $TESTIP > /dev/null 2>&1
|
|
|
215 |
# ping -W 3 -I $IP -c 1 $TESTIP
|
|
|
216 |
# Si ping de la première adresse --> ok --> stop du test pour l'interface testée
|
|
|
217 |
if [ $? -eq 0 ]; then
|
|
|
218 |
break
|
|
|
219 |
else
|
|
|
220 |
# sinon on compte une erreur
|
|
|
221 |
FAIL=$(($FAIL + 1))
|
|
|
222 |
fi
|
|
|
223 |
done # End of test sur un serveur Internet
|
|
|
224 |
# Affichage du nombre de down
|
|
|
225 |
echo "FAIL=$FAIL"
|
|
|
226 |
# Si nombre de fois down = nombre de tests --> Iface down --> log dans fichier log avec l'heure
|
|
|
227 |
if [ $FAIL -eq $COUNT ]; then
|
|
|
228 |
echo "`date +%F-%Hh%mm%Ss` : [WARN] $iface is down!"
|
|
|
229 |
# Si etat différent de 1 (déjà tombé) --> changement de l'état général en default
|
|
|
230 |
if [ $STATE -ne 1 ]; then
|
|
|
231 |
echo "Switching state $STATE -> 1"
|
|
|
232 |
STATE=1
|
|
|
233 |
fi
|
|
|
234 |
# Rajout de l'iface dans la liste des interfaces down
|
|
|
235 |
DOWN="${DOWN} $IFINDEX"
|
|
|
236 |
echo "DOWN=$DOWN"
|
|
|
237 |
# Nombre d'interface down
|
|
|
238 |
DOWNCOUNT=$(($DOWNCOUNT + 1))
|
|
|
239 |
echo "DOWNCOUNT=$DOWNCOUNT"
|
|
|
240 |
fi
|
|
|
241 |
IFINDEX=$(($IFINDEX + 1))
|
|
|
242 |
echo "IFINDEX =$IFINDEX"
|
|
|
243 |
done # End Test Interface in WANIFACE
|
|
|
244 |
|
|
|
245 |
# 0 Passerelle down et état précédent différent (retour à la normale)) --> mise à la normale des passerelles
|
|
|
246 |
# if [ $DOWNCOUNT -eq 0 ] && [ $DOWNCOUNT -ne $DOWNCOUNT_BAK ]; then
|
|
|
247 |
if [ $DOWNCOUNT -eq 0 ] ; then
|
|
|
248 |
if [ $STATE -eq 1 ]; then
|
|
|
249 |
echo
|
|
|
250 |
echo "[] All links up and running :)"
|
|
|
251 |
set -x
|
|
|
252 |
${routecmd}
|
|
|
253 |
set +x
|
|
|
254 |
# Changement de l'état en normal
|
|
|
255 |
STATE=0
|
|
|
256 |
echo "Switching state 1 -> 0"
|
|
|
257 |
fi # End retour etat normal
|
|
|
258 |
# if no interface is down, go to the next cycle
|
|
|
259 |
continue
|
|
|
260 |
# cas ou au moins une passerelle down mais état identique au précédent Test --> rien à changer
|
|
|
261 |
else
|
|
|
262 |
if [ "$DOWN_BAK" == "$DOWN" ]; then
|
|
|
263 |
echo "DOWN_BAK == DOWN = $DOWN"
|
|
|
264 |
continue # --> état identique test precedent --> boucle suivante
|
|
|
265 |
# cas ou au moins une passerelle down mais état différent de test précédent --> remplacement par nouvelle règle
|
|
|
266 |
else
|
|
|
267 |
cmd="ip route replace default scope global"
|
|
|
268 |
IFINDEX=1
|
|
|
269 |
suffix=""
|
|
|
270 |
# Pour chaque interface --> traitement et application de la règle de routage
|
|
|
271 |
for iface in $WANIFACE ; do
|
|
|
272 |
echo "-------------------------"
|
|
|
273 |
echo "iface=$iface"
|
|
|
274 |
echo "Index = " $IFINDEX
|
|
|
275 |
FAILIF=0
|
|
|
276 |
# Pour chaque interface down -->
|
|
|
277 |
echo "Interfaces DOWN = $DOWN"
|
|
|
278 |
for lnkdwn in $DOWN ; do
|
|
|
279 |
echo "LINKDOWN = "$lnkdown
|
|
|
280 |
if [ $lnkdwn -eq $IFINDEX ]; then
|
|
|
281 |
FAILIF=1
|
|
|
282 |
break
|
|
|
283 |
else
|
|
|
284 |
continue
|
|
|
285 |
fi
|
|
|
286 |
done # End linkdown in DOWN
|
|
|
287 |
# Interface en etat normal --> rajout de la règle en mode nexthop
|
|
|
288 |
if [ $FAILIF -eq 0 ]; then
|
|
|
289 |
IP=`ifconfig $iface |grep "inet adr" |cut -f 2 -d ":" |awk '{print $1}'`
|
|
|
290 |
if [ $iface != "eth0" ]; then
|
|
|
291 |
GW=`grep "$iface," $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $4}'` # @GW
|
|
|
292 |
WT=`grep "$iface," $CONF_FILE | awk -F'"' '{ print $2 }' | awk -F, '{ print $5}'` # @GW
|
|
|
293 |
else
|
|
|
294 |
GW=`grep "^GW=" $CONF_FILE | awk -F= '{print $2}'` # @GW
|
|
|
295 |
fi
|
|
|
296 |
echo "GW=$GW"
|
|
|
297 |
echo "WT=$WT"
|
|
|
298 |
echo "suffix=$sufix"
|
|
|
299 |
suffix="${suffix} nexthop via ${GW} dev ${iface} weight ${WT:-1}"
|
|
|
300 |
fi # End interface = noFAIL
|
|
|
301 |
IFINDEX=$(($IFINDEX + 1))
|
|
|
302 |
done # End iface IN WANIFACE
|
|
|
303 |
# Commande globale
|
|
|
304 |
cmd="ip route replace default scope global $suffix"
|
|
|
305 |
|
|
|
306 |
if [ $VERBOSE -eq 1 ]; then
|
|
|
307 |
set -x
|
|
|
308 |
# echo "Avec commentaire : " ${cmd}
|
|
|
309 |
${cmd}
|
|
|
310 |
set +x
|
|
|
311 |
echo
|
|
|
312 |
else
|
|
|
313 |
${cmd} 2>/dev/null
|
|
|
314 |
echo ${cmd}
|
|
|
315 |
fi # end Application de la commande de routage globale
|
|
|
316 |
fi #
|
|
|
317 |
DOWN_BAK=$DOWN # Enregistrement de l'etat
|
|
|
318 |
fi # End
|
|
|
319 |
done
|
|
|
320 |
} # End of Failover
|
|
|
321 |
|
|
|
322 |
|
|
|
323 |
#################
|
|
|
324 |
# Main
|
|
|
325 |
#################
|
|
|
326 |
|
|
|
327 |
echo "[] Load balancer for multiple WAN interfaces - v2.1"
|
|
|
328 |
echo "[] (c) 2011 Pau Oliva Fora <pof> @eslack.org"
|
|
|
329 |
echo "[] (c) 2013 3abtux ALCASAR <3abtux> @alcasar.net"
|
|
|
330 |
echo
|
|
|
331 |
|
|
|
332 |
case $1 in
|
|
|
333 |
create)
|
|
|
334 |
create_eth
|
|
|
335 |
;;
|
|
|
336 |
delete)
|
|
|
337 |
delete_eth
|
|
|
338 |
;;
|
|
|
339 |
fail)
|
|
|
340 |
failover
|
|
|
341 |
;;
|
|
|
342 |
start)
|
|
|
343 |
PARAM="add"
|
|
|
344 |
create_eth
|
|
|
345 |
if [ $WATCHDOG != "yes" ]; then
|
|
|
346 |
exit 0
|
|
|
347 |
fi
|
|
|
348 |
ip route flush cache
|
|
|
349 |
# RETVAL=$?
|
|
|
350 |
# echo
|
|
|
351 |
# [ $RETVAL -eq 0 ] && touch /var/lock/subsys/alcasar-load_balancing
|
|
|
352 |
touch /var/lock/subsys/alcasar-load_balancing
|
|
|
353 |
failover
|
|
|
354 |
;;
|
|
|
355 |
stop)
|
|
|
356 |
PARAM="del"
|
|
|
357 |
prog="alcasar-load_balancing.sh"
|
|
|
358 |
gprintf $"Shutting down $prog: "
|
|
|
359 |
killproc $prog
|
|
|
360 |
RETVAL=$?
|
|
|
361 |
echo
|
|
|
362 |
[ $RETVAL -eq 0 ] && rm -f /var/lock/subsys/alcasar-load_balancing
|
|
|
363 |
return $RETVAL
|
|
|
364 |
ip route
|
|
|
365 |
;;
|
|
|
366 |
*)
|
|
|
367 |
echo "Usage: $0 [start|stop|create|delete]" ; echo ; exit 1
|
|
|
368 |
;;
|
|
|
369 |
esac
|
|
|
370 |
|
|
|
371 |
exit 0
|