Configurare Ubuntu come gateway con failover/load balancing su doppia WAN
Abbiamo due WAN e vogliamo impostare una sorta di failover ( ridondanza delle connessioni, se cade una il gateway passa alla seconda e/o viceversa ) o di load balancing ( per suddividere il carico fra le due connessioni ).
Se non ci interessa niente di troppo particolare o personalizzabile si fa prima a utilizzare le distro preposte a questo, si veda pfSense: failover e load balancing su doppia WAN.
A me serve poter avere più flessibilità sulla macchina gateway e poter installare altri programmi quindi ho provato a replicarne le funzionalità su un Ubuntu server 14.04.3.
Si veda l’articolo Configurare Ubuntu come gateway per le istruzioni base su come configurare un gateway.
La situazione della prova è la seguente ( gli IP sono fittizzi, i dati delle 2 WAN ovviamente dipendono dagli ISP ):
- LAN: eth0 ( 192.168.1.1 )
- WAN1: eth1 ( 192.168.10.2 / gateway 192.168.10.1 )
- WAN2: eth2 ( 192.168.20.2 / gateway 192.168.20.1 )
Visto che normalmente non si potrebbe impostare due gateway diversi occorre utilizzare il programma iproute che ci da qualche scappatoia.
1 |
sudo apt-get install iproute |
Per prima cosa occorre creare due nuove tabelle per il routing:
1 |
sudo nano /etc/iproute2/rt_tables |
E si aggiungono le nuove tabelle:
1 2 3 4 5 6 7 |
253 default # tabelle aggiunte 101 wan1 102 wan2 0 unspec |
Occorre ora istruire iproute, ho creato un semplice script base:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
#!/bin/bash # Cancello la routing table base ip route del default table main # Imposto la nuova routing table della wan1 ip route flush table wan1 ip route add 192.168.10.0/24 dev eth1 src 192.168.10.2 table wan1 ip route add default via 192.168.10.1 dev eth1 table wan1 ip route add 192.168.1.0/24 dev eth0 table wan1 ip route add 192.168.20.0/24 dev eth2 table wan1 ip route add 127.0.0.0/8 dev lo table wan1 # Imposto la nuova routing table della wan2 ip route flush table wan2 ip route add 192.168.20.0/24 dev eth2 src 192.168.20.2 table wan2 ip route add default via 192.168.20.1 dev eth2 table wan2 ip route add 192.168.1.0/24 dev eth0 table wan2 ip route add 192.168.10.0/24 dev eth1 table wan2 ip route add 127.0.0.0/8 dev lo table wan2 # Applico le regole di routing ip rule add from 192.168.10.2 table wan1 ip rule add from 192.168.20.2 table wan2 ip rule add order 10 from all lookup main ip rule add order 20 iif lo lookup wan1 # Istruzioni per poter poi suddividere il carico tramite iptables ip rule add fwmark 1 table wan1 prio 1024 ip rule add fwmark 2 table wan2 prio 1025 # IP Stack for f in /proc/sys/net/ipv4/conf/*/rp_filter ; do echo 0 > $f ; done echo 0 > /proc/sys/net/ipv4/route/flush |
Se a questo punto ci servisse solo un bilanciamento del carico o failover automatico basterebbe aggiungere il comando:
1 2 |
ip route add default scope global nexthop via 192.168.10.1 dev eth1 weight 1 \ nexthop via 192.168.20.1 dev eth2 weight 1 |
Ma a me serve poter decidere chi usa quale connessione e solo nell’eventualità che una delle due cada allora deve spostare temporaneamente tutti su quella attiva, quindi come primo passo ho creato un script che si occupa di indirizzare le richieste da una parte all’altra, e se parametrizzata forza tutti ad usare una specifica connessione:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#!/bin/bash # Per forzare tutti i gruppi sulla stessa WAN (es. link assente) # occorre richiamare: # <nome-script> wan1 # oppure: # <nome-script> wan2 # Tabelle TBL1=1 TBL2=2 # Controllo parametri WAN=$1 case $WAN in "wan1") TBL2=$TBL1 ;; # Forza tutti i gruppi si WAN1 "wan2") TBL1=$TBL2 ;; # Forza tutti i gruppi su WAN2 esac # Gruppo #1 GRP1=( 192.168.1.10 192.168.1.11 192.168.1.12 ) # Gruppo #2 GRP2=( 192.168.1.20 192.168.1.21 192.168.1.22 ) # Regole balancing iptables -t mangle -F iptables -t mangle -X for i in "${GRP1[@]}" do iptables -t mangle -A PREROUTING -s $i -j MARK --set-mark $TBL1 done for i in "${GRP2[@]}" do iptables -t mangle -A PREROUTING -s $i -j MARK --set-mark $TBL2 done |
Come ultima fase occorre controllare lo stato delle due connessioni ed in caso che una o l’altra risulti scollegata va spostato il carico su quella attiva, per poi ripristinare il tutto quando la connessione ritorna.
Questo script quando eseguito verifica tramite ping in uscita sulla WAN da controllare se ha risposte, in caso contrario segna la WAN come offline e sposta tutto il carico sulla WAN attiva.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 |
#!/bin/bash # parametri FORCE=false EMAIL=true for i in $@; do case $i in "--force" | "-f" ) FORCE=true ;; # Forza l'aggiornamento "--no-email" | "-n" ) EMAIL=false ;; # Inibisce l'email esac done # va installato un mailserver/relay e un client # ad esempio sendmail/postfix/nullmailer e mutt EMAIL_TXT="" # Contenuto del messaggio da mandare via email EMAIL_PRG="mutt" EMAIL_TO="xxxxx@xxx.xx" # directory corrente DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # script da eseguire per il bilanciamento SCRIPT="<nome-script-per-il-bilanciamento-dei-gruppi>" # file dove salvare l'ultimo stato del controllo STATUS="$0-last" # indirizzi IP da controllare lo status connessioni IP1="8.8.8.8" IP2="8.8.4.4" # comandi di eliminazione/aggiunta lookup DEL="ip rule del order 20" ADD="ip rule add order 20 iif lo lookup" # controllo status wan2 WAN2="off" # rimuovo tutte le regole in ordine 20, quelle che uso come lookup localhost for i in $(ip rule show | grep "20:" | awk {'print $1'}) do $DEL > /dev/null 2>&1 done # aggiungo il lookup sulla wan2 $ADD wan2 ping -I eth2 $IP1 -c 1 > /dev/null 2>&1 # se fallisce il primo ping provo il secondo if [ $? -ne 0 ]; then ping -I eth2 $IP2 -c 1 > /dev/null 2>&1 fi # se questa volta non ho errori allora la wan2 è attiva if [ $? -eq 0 ]; then WAN2="on" fi # controllo status wan1 WAN1="off" # qui elimino solo una regola 20 perchè gli eventuali doppioni li ho tolti prima $DEL > /dev/null 2>&1 # aggiungo il lookup sulla wan1 $ADD wan1 ping -I eth1 $IP1 -c 1 > /dev/null 2>&1 # se fallisce il primo ping provo il secondo if [ $? -ne 0 ]; then ping -I eth1 $IP2 -c 1 > /dev/null 2>&1 fi # se questa volta non ho errori allora la wan1 è attiva if [ $? -eq 0 ]; then WAN1="on" fi # se dall'ultimo controllo la situazione è cambiata avviso via email if [ -f $STATUS ]; then LAST=$(cat $STATUS) fi CURR="wan1: $WAN1 | wan2: $WAN2" if [ $FORCE == true ] || [ "$CURR" != "$LAST" ]; then EMAIL_TXT=$(date)"\n\nLast status: $LAST\nCurrent status: $CURR\n" # aggiorno lo status echo $CURR > $STATUS $DEL > /dev/null 2>&1 if [ $WAN1 = "off" -a $WAN2 = "on" ]; then EMAIL_TXT=$EMAIL_TXT"\nForced WAN2" $ADD wan2 # forzo il bilanciamento solo sulla wan2 $SCRIPT wan2 elif [ $WAN1 = "on" -a $WAN2 = "off" ]; then EMAIL_TXT=$EMAIL_TXT"\nForced WAN1" $ADD wan1 # forzo il bilanciamento solo sulla wan1 $SCRIPT wan1 else EMAIL_TXT=$EMAIL_TXT"\nUse WAN1 and WAN2" $ADD wan1 # se entrambe le wan sono su eseguo il bilanciamento normale $SCRIPT fi #spedire email if [ $EMAIL == true ]; then EMAIL_TITLE="$HOSTNAME: Connection status changed" if [ $FORCE == true ]; then EMAIL_TITLE="$EMAIL_TITLE (FORCED)"; fi echo -e $EMAIL_TXT | $EMAIL_PRG -s "$EMAIL_TITLE" $EMAIL_TO fi fi |
Lo script è possibile farlo girare in un cron ogni minuto, se occorre avere un tempo di controllo più ristretto occorre appoggiarsi ad un ulteriore script (che girerà ogni minuto) e che eseguirà lui ogni tot secondi (che non superi il minuto ovviamente) il controllo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#!/bin/bash # directory corrente DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" # numero di ripetizioni al minuto REP=6 if (( $REP > 30 )); then echo "Error in config" exit 1 fi WAIT=$(expr 60 / $REP) for (( i=1; i<$REP; i++ )) do $DIR/<nome-dello-script-per-il-controllo-della-connessione> sleep $WAIT done |
Nel caso sulla macchina gateway ci sia anche installato il proxy server Squid occorre anche dire a lui quale IP debba uscire da quale WAN.
In tal caso si creano due ACL con gli IP o i range di suddivisione (si possono far popolare ogni volta in automatico dallo script che si occupa della suddivisione del carico) e nel file squid.conf si utilizza la regola tcp_outgoing_address:
1 2 |
tcp_outgoing_address 192.168.10.2 acl-wan1 tcp_outgoing_address 192.168.20.2 acl-wan2 |
Ricordandoci poi di riavviare Squid ad ogni modifica delle liste che il semplice reload delle regole non mi funziona con questo parametro.