Load Balancing fra due o piu` ADSL
Questo e` un setup che sto sperimentando per il load balancing fra due adsl. Per il momento non dispone di un meccanismo di failover (ovvero piu` esattamente di esclusione della linea che ha smesso di andare).
Il sistema sembra funzionare, ma forse e` il caso di aspettare ancora prima di cantare vittoria definitivamante.
Principio di funzionamento
L'idea di base e` semplice, si configura il routing per avere due gateway che vengono usati assieme, dividendo il traffico sull'uno o sull'altro a caso, al 50% oppure ad una percentuale diversa, a discrezione dell'utente. Questo sistema consente di avere un effettivo guadagno quando ci siano molte (centinaia) connessioni a host diversi, se si effettua per esempio un solo download da un solo server remoto, questo sistema e` totalmente inutile, occupera` una delle due adsl al 100% e l'altra restera` a zero.
Quando si va a fondo, pero`, si scopre che e` tutto molto piu` complesso del previsto. Il metodo funziona bene per le connessioni originate dalla macchina che e` fisicamente connessa ai due gateway, ma crea enormi problemi quando questa macchina cerca di fare connettere altre macchine ad internet per mezzo del NAT. Il problema e` che la decisione su quale dei due gateway usare di volta in volta non e` in nessun modo "legata" allo stato delle connessioni nattate, quindi succede che una connessione che e` iniziata sul gateway 1 puo` ad un certo punto essere routata sul gateway 2, ma continuare ad essere nattata con l' IP pubblico del gateway 1. Questo succede perche` il routing, che avviene "prima" del nat (SNAT, specificamente), ha un suo modo di tenere traccia delle connessioni e di decidere dove mandare i pacchetti, che e` totalmente slegato dalle decisioni prese dal nat e dalle sue tabelle di connection tracking. Nello specifico, almeno a quanto ho capito, il routing tiene una cache delle decisioni prese basata sull' IP dell'host remoto e basta, non su un sistema "intelligente" di connection tracking, e inoltre questa cache ha scadenza molto piu` breve rispetto a quella del connection tracking del nat.
Il risultato pratico di questo problema e` che la macchina che fa load balancing ogni tanto manda fuori pacchetti dall'interfaccia pubblica 1 nattandoli con l'indirizzo ip della pubblica 2, e viceversa. Questo non capita mai all'inizio di una connessione, ma durante la stessa, specie se e` rimasta idle per qualche secondo.
La soluzione a questo problema e` fornita da una patch al kernel che "collega" le decisioni di routing allo stato delle connessioni nelle tabelle di connection tracking del NAT. In questo modo quando una connessione risulta "attiva" nel connection tracking, il routing continuera` sempre ad usare lo stesso gateway per tutti i dati che appartengono a quella connessione. Il NAT dal canto suo continuera` ad usare sempre lo stesso IP pubblico per quella connessione, e tutto funziona. Nel momento in cui il NAT si "dimentica" di quella connessione, allora anche il routing se ne dimentichera`.
Patch al kernel
Io ho usato per il mio setup un kernel 2.6.15 (Debian) ricompilato da me. A questo kernel ho applicato le patch che si trovano qui: http://www.ssi.bg/~ja/#routes. Ho usato nello specifico la patch (dichiarata "obsoleta" sul sito) per i kernel dalla 2.6.14 alla 2.6.16.
Oltre ad applicare la patch al kernel, occorre ovviamente abilitare le funzioni di "ip: advanced router" e "ip: equal cost multipath", avendo cura di NON abilitare la funzione "ip: equal cost multipath with caching support", la quale sembra non funzionare bene o forse andare addirittura in conflitto con la patch.
Configurazione del routing
In /etc/network/interfaces ho tolto la definizione dei gateway, in modo che di default non vi sia alcun gateway. Fatto questo, tutto il routing va configurato in uno script apposito, che ventualmente puo` essere lanciato come "up" nell'ultima delle interfacce pubbliche specificate in /etc/network/interfaces.
Questo script, che e` stato piu` o meno scopiazzato da qui, http://lartc.org/howto/lartc.rpdb.multiple-links.html ha lo scopo di configurare il routing di default verso uno dei due gateway a caso, avendo cura pero` di fare si` che i pacchetti originati sulla macchina che ha i due gateway, e che hanno uno specifico source address, escano dall'interfaccia giusta. Per i pacchetti nattati, e` la patch al kernel che pensa a questo.
In questo esempio i due provider usati sono BT ed Ehiweb.
Prima di tutto, siccome uso due tabelle di routing chiamate, appunto, "BT" ed "ehiweb", devo andare a configurare questi nome dentro al file etc/iproute2/rt_tables, cosi`:
# # reserved values # 255 local 254 main 253 default 0 unspec # # local # #1 inr.ruhep 1 BT 2 ehiweb
Fatto questo, posso riferirmi alle tabelle per nome (e` piu` comodo) nello script che segue.
I commenti sono nello script stesso.
# ------------------
# Regole di routing:
# ------------------
# Per fare si` che io risponda tramite l'interfaccia corretta
# a chi mi chiama da internet usando una o l'altra interfaccia pubblica, devo
# definire due regole che dicono che se sto parlando con source address di BT
# (a un host che e` su internet) devo uscire per l'interfaccia (e il gateway)
# di BT. Lo stesso discorso vale per ehiweb.
# Nota: queste regole sono necessarie solo se sto rispondendo a una richiesta
# che viene da un host su internet, perche` se stessi rispondendo a una richiesta che
# viene da un host nella mia subnet pubblica di uno o dell'altro provider,
# basterebbero le regole di routing "standard" che dicono che fuori da un'interfaccia
# c'e` la subnet alla quale appartengo anche io (e le altre macchine pubbliche, nonche`
# il router) per sapere che devo mandare i pacchetti fuori per di li` quando parlo con le
# macchine che stanno nella mia stessa rete pubblica (cioe` insomma prima del router).
# Se parlo uscendo con l'indirizzo di BT, vado a vedere la table "BT"
# per sapere dove mandare i pacchetti
ip rule add from 217.221.234.74 table BT
# Se parlo uscendo con l'indirizzo di ehiweb, vado a vedere la table "ehiweb"
# per sapere dove mandare i pacchetti.
ip rule add from 83.211.205.162 table ehiweb
# ------------------
# Tabelle di routing
# ------------------
# Le regole dicono "quando succede X, usa la tabella X". Queste sono le tabelle che vengono
# "usate" per decidere cosa fare di un pacchetto quando questo matcha una delle regole
# precedentemente definite.
# Il contenuto di queste tabelle potrebbe limitarsi ad una voce che dice "il gateway e` il router"
# pero` devo anche duplicarci dentro tutti i dati della table "main", quella di default, perche`
# quando un pacchetto viene mandato qui dentro, non vedra` mai la table "main", ma solo il contenuto
# di questa specifica table.
# Tabella di routing del provider BT
# routing per la rete pubblica locale
ip route add 217.221.234.72/29 dev eth2 src 217.221.234.74 table BT
# il gateway di default (il mio router per il provider BT)
ip route add default via 217.221.234.73 table BT
# Devo inoltre duplicare qui le regole di routing che si trovano nella table "main"
# perche` se non lo faccio un host che si trovi fuori da una interfaccia locale
# che non e` definita qui e chi mi "chiami" al mio IP qui definito non avrebbe risposta,
# dato che io lo considereri "su internet" e manderei i pacchetti per lui fuori dal gateway.
# in pratica ogni table dovrebbe contenere anche una copia di tutto il routing standard, l'unica differenza
# fra le due tables dovrebbe essere il diverso default routing (ip e gateway)
ip route add 10.0.0.0/16 dev eth0 table BT
ip route add 192.168.2.0/24 dev eth1 table BT
ip route add 127.0.0.0/8 dev lo table BT
ip route add 83.211.205.160/29 dev eth4 table BT
# come sopra ma per ehiweb.
# regole specifiche per questa tabella (rete e gateway)
ip route add 83.211.205.160/29 dev eth4 src 83.211.205.162 table ehiweb
ip route add 10.0.0.0/16 dev eth0 table ehiweb
# regole copiate dalle altre altre tabelle
ip route add 192.168.2.0/24 dev eth1 table ehiweb
ip route add 127.0.0.0/8 dev lo table ehiweb
ip route add 217.221.234.72/29 dev eth2 table ehiweb
ip route add default via 83.211.205.161 table ehiweb
# ----------------------------------
# Regole di routing nella table main
# ----------------------------------
# nella routing table "main" inserisco due regole che secondo me dovrebbero esserci gia`,
# infatti sospetto che questo pezzo non serva a nulla. In pratica qui dico che la subnet
# che si trova fisicamente fuori dalla interfaccia con cui parlo con il router (veramente
# i routers, uno per provider) va routata fuori dalla specifica interfaccia, e che
# devo "presentarmi" con l' ip giusto su ogni interfaccia. Questa cosa, definizione
# dell'ip a parte, la fa di default il kernel quando assegno un indirizzo ad una interfaccia.
ip route add 217.221.234.72/29 dev eth2 src 217.221.234.74
ip route add 83.211.205.160/29 dev eth4 src 83.211.205.162
# ---------------------------------
# Definizione del doppio gateway
# ---------------------------------
# per finire, sempre nella tabella "main" metto due gateway diversi con lo stesso peso
# per fare load balancing del traffico al 50% sulle due ADSL. Chiaramente viene bilanciato
# solo il traffico generato dai client (nattati) oppure dal firewall stesso, quando questo
# non venga generato da applicazioni che hanno bindata una sola delle due interfacce pubbliche
# o uno solo degli indirizzi IP pubblici, perche` in questo caso le due regole messe in testa
# allo script forzerebbero, dato l' ip di provenienza, ad usare uno solo dei due GW.
# Non viene bilanciato nemmeno il traffico che ENTRA da internet, perche`
# ovviamente quello e` diretto ad uno solo dei miei due indirizzi pubblici, e quindi
# viaggera` sempre sulla stessa interfaccia da cui e` entrato.
ip route add default scope global nexthop via 217.221.234.73 dev eth2 weight 1 \
nexthop via 83.211.205.161 dev eth4 weight 1
Configurazione del firewall e del NAT
Per fare nat del traffico dalla mia LAN ai due GW, bastano due righe di SNAT. Posto che le due interfacce pubbliche sono eth2 e eth4, posso fare:
iptables -t nat -A POSTROUTING -o eth2 -j SNAT -s 192.168.2.0/24 --to-source 217.221.234.74 iptables -t nat -A POSTROUTING -o eth4 -j SNAT -s 192.168.2.0/24 --to-source 83.211.205.162
E` importante indicare ovviamente il giusto indirizzo in "--to-source" altrimenti uscirei sistematicamente con l'indirizzo sbagliato dall'interfaccia sbagliata! Chiaramente in questo esempio eth2 ha indirizzo 217.221.234.74 ed eth4 ha indirizzo 83.211.205.162.
NAT entrante (DNAT)
Volendo fare NAT entrante (per esempio la porta 25 per l'smtp verso una macchina che si trova nella LAN o in una DMZ) ovviamente dovro` applicare le regole di DNAT a tutte e due le interfacce pubbliche. Nella mia prima versione ho configurato la macchina dentro la DMZ con due alias, come e` riportato nel seguente paragrafo (che e` un pezzo della vecchia documentazione), pero` immagino che non sia davvero necessario, e che possa funzionare anche con un solo indirizzo ip sulla macchina interna.
Indaghero` e faro` prove a riguardo.
VECCHIO DA VERIFICARE: NAT entrante con il load balancing
Quando si volesse mettere una macchina tipo per dire un mail server dietro alle due ADSL, e` facile (per il caso del mail server) fare sia load balancing che failover. Si mettono i due indirizzi pubblici come MX del dominio, a pari livello. Poi si fa NAT entrante per la porta 25 sul firewall, avendo cura di nattare il traffico entrante sulla 25 su DUE indirizzi IP privati diversi, a seconda dell'interfaccia da cui entra. Si assegneranno poi al mail server (allo stesso computer) tutti e due gli ip privati usati nel NAT, come alias della stessa interfaccia. Il motivo di questa scelta di usare due indirizzi e` per garantire che non possa succedere che, a causa della persistenza della cache del routing, possano entrare i pacchetti per una ADLS ed uscire per l'altra ADSL. Per esempio, se un singolo host remoto che dovesse mandare una mail prima ad un MX e poi all' altro, succederebbe che il routing per quell'host verrebbe memorizzato in cache come "routa sulla ADSL numero 1" e poi, quando questo mandasse la mail all'indirizzo della ADSL numero 2, i pacchetti di risposta verrebbero di nuovo instradati sulla numero 1.
Usando indirizzi IP totalmente diversi,come se stessimo nattando a due macchine distinte, si evita questo problema.
Un esempio di configurazione del firewall e`:
# accetto il traffico per il mail server su tutte e due le interfacce di ingresso iptables -A FORWARD -i $WAN -m state --state NEW -p tcp --dport 25 -j ACCEPT iptables -A FORWARD -i $WAN2 -m state --state NEW -p tcp --dport 25 -j ACCEPT iptables -A FORWARD -i $WAN -m state --state NEW -p tcp --dport 110 -j ACCEPT iptables -A FORWARD -i $WAN2 -m state --state NEW -p tcp --dport 110 -j ACCEPT # DNAT a seconda dell'interfaccia su due IP privati diversi iptables -t nat -A PREROUTING -i $WAN -p tcp --dport 25 -j DNAT --to 192.168.2.1 iptables -t nat -A PREROUTING -i $WAN2 -p tcp --dport 25 -j DNAT --to 192.168.2.2 iptables -t nat -A PREROUTING -i $WAN -p tcp --dport 110 -j DNAT --to 192.168.2.1 iptables -t nat -A PREROUTING -i $WAN2 -p tcp --dport 110 -j DNAT --to 192.168.2.2
Chiaramente il mail server avra` una eth0 con indirizzo 192.168.2.1 e una eth0:0 con indirizzo 192.168.2.2.