Due dei problemi presenti in ogni connessione ad Internet sono la scarsità di banda ed il fatto che, a volte, il collegamento si interrompe. In questa pagina presento una soluzione relativamente semplice che permette di utilizzare due connessioni ad Internet (nota 1). Due gli obbiettivi:
Consideriamo una scuola (il mio caso) o un'azienda con molti PC:
Quanto mostrato utilizza come router una macchina Linux Debian Buster, ma dovrebbe essere adattabile ad altre distribuzioni ed anche a router non basati su Linux.
Nella seguente figura sono mostrati lo schema della rete e gli indirizzi IP esemplificativi che utilizzerò in seguito:
Alcune macchine della rete 172.16.0.0/16 si collegheranno ad internet attraverso la connessione ISP-A, usando il modem/router 192.168.10.1, altre attraverso ISP-B usando il modem/router 192.68.111.254.
La decisione della connessione da utilizzare dipende dall'indirizzo IP della macchina da cui la connessione ha origine, da cui il nome source routing, caso particolare di policy routing; il tutto sarà gestito dal router e dovrà essere assolutamente trasparente rispetto agli utenti.
Chiunque abbia una conoscenza anche minima delle reti sa che un router IP decide come inoltrare un pacchetto in base all'indirizzo di destinazione, consultando la sua tabella di routing (nota 10).
Il problema potrebbe quindi essere risolto usando rotte statiche basate sulla destinazione dei pacchetti IP, in parte instradati attraverso ISP-A ed in parte attraverso ISP-B. Infatti lo scopo del livello 3 ISO/OSI (IP nel nostro caso) è garantire una connessione end-to-end tra destinazione e sorgente, indipendentemente dal percorso.
Peccato che, se la rete è nattata, cade il concetto di connessione end-to-end a livello IP; e tutte le reti IPv4 ordinarie sono nattate... (nota 3).
I primi esperimenti di bilanciamento di due linee li ho fatti con il routing normale: facile da implementare ed il tutto sembrava funzionare... Purtroppo alcuni servizi internet usano centinaia di indirizzi IP e tutti devono essere raggiunti con lo stesso IP pubblico, pena errori casuali e difficili da identificare. Se non vi interessano servizi come quelli offerti da Microsoft, Google o Amazon, potete implementare anche un routing normale, altrimenti quella del source routing è l'unica soluzione funzionante (nota 4).
Innanzitutto occorre rendere operativo l'host che fungerà da router affinché possa collegarsi ad internet potendo scegliere la connessione da utilizzare.
Di seguito il file di configurazione della rete.
vv@router:~# cat /etc/network/interfaces
[...]
# ISP-B
auto ens192
iface ens192 inet static
address 192.168.111.100/24
# LAN
auto ens224
iface ens224 inet static
address 172.16.0.1/16
# ISP-A
auto ens256
iface ens256 inet static
address 192.168.10.100/24
Si noti l'assenza di qualsivoglia rotta di default e di conseguenza l'impossibilità di connettersi ad Internet (nota 5):
vv@router:~# ip route
172.16.0.0/16 dev ens224 proto kernel scope link src 172.16.0.1
192.168.10.0/24 dev ens256 proto kernel scope link src 192.168.10.100
192.168.111.0/24 dev ens192 proto kernel scope link src 192.168.111.100
vv@router:~# ping 8.8.8.8
connect: La rete non è raggiungibile
Verifichiamo di poter raggiungere i due modem/router:
vv@router:~# ping 192.168.10.1
PING 192.168.10.1 (192.168.10.1) 56(84) bytes of data.
64 bytes from 192.168.10.1: icmp_seq=1 ttl=64 time=0,956 ms
vv@router:~# ping 192.168.111.254
PING 192.168.111.254 (192.168.111.254) 56(84) bytes of data.
64 bytes from 192.168.111.254: icmp_seq=1 ttl=64 time=1,83 ms
Verifichiamo la connessione ad internet di entrambi i modem/router definendo una rotta predefinita provvisoria. Cominciamo con ISP-B:
root@router:~# ip route add default via 192.168.111.254
root@router:~# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=11.9 ms
root@router:~# wget -qO- ipinfo.io/ip
82.56.121.xxx
root@router:~# ip route del default
Passiamo ora a ISP-A:
root@router:~# ip route add default via 192.168.10.1
root@router:~# ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=7.99 ms
root@router:~# wget -qO- ipinfo.io/ip
2.44.3.xxx
root@router:~# ip route del default
Si noti il cambio del TTL e dell'indirizzo pubblico tra il primo ed il secondo blocco di test.
Occorre ora creare due nuove tabelle di routing, ciascuna specifica per una connessione ad Internet. Il nome viene definito nelle due righe evidenziate in /etc/iproute2/rt_tables; i numeri indicati non sono rilevanti:
root@router:~# cat /etc/iproute2/rt_tables
255 local
254 main
253 default
0 unspec
10 ISPA
111 ISPB
Occorre quindi popolare le tabelle di routing e specificare quando devono essere utilizzate. Durante i test potrebbe essere opportuno inserire i comandi in uno script, oppure fare il Copia&Incolla:
root@router:~# nano local-source-routing.sh
#!/bin/sh
ip route flush table ISPB
ip rule flush table ISPB
ip rule add from 192.168.111.100 table ISPB
ip route add 192.168.111.0/24 dev ens192 table ISPB
ip route add default via 192.168.111.254 table ISPB
ip route flush table ISPA
ip rule flush table ISPA
ip rule add from 192.168.10.100 table ISPA
ip route add 192.168.10.0/24 dev ens256 table ISPA
ip route add default via 192.168.10.1 table ISPA
I due blocchi sono simili:
Creiamo le tabelle di routing, eseguendo lo script:
root@router~# ./local-source-routing.sh
Abbiamo ora tre tabelle di routing principali, le due che abbiamo creato e la main, generata automaticamente dal sistema operativo:
root@router:~# ip route show table ISPA
default via 192.168.10.1 dev ens256
192.168.10.0/24 dev ens256 scope link
root@router:~# ip route show table ISPB
default via 192.168.111.254 dev ens192
192.168.111.0/24 dev ens192 scope link
root@router:~# ip route show table main
172.16.0.0/16 dev ens224 proto kernel scope link src 172.16.0.1
192.168.10.0/24 dev ens256 proto kernel scope link src 192.168.10.100
192.168.111.0/24 dev ens192 proto kernel scope link src 192.168.111.100
Per conoscere le regole che permettono di scegliere quale tabella utilizzare:
vv@router~# ip rule
0: from all lookup local
32764: from 192.168.10.100 lookup ISPA
32765: from 192.168.111.100 lookup ISPB
32766: from all lookup main
32767: from all lookup default
Più il numero è basso, maggiore è la priorità: in pratica le regole vanno lette nell'ordine con cui sono presentate dal comando ip rule.
Verifichiamo il funzionamento, specificando quale IP sorgente deve essere utilizzato e, di conseguenza, quale tabella di routing e quale connessione. Il primo test utilizza un semplice ping:
pivv@router:~$ ping 8.8.8.8 -I 192.168.111.100
PING 8.8.8.8 (8.8.8.8) from 192.168.111.100 : 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=115 time=9.44 ms
vv@router:~$ ping 8.8.8.8 -I 192.168.10.100
PING 8.8.8.8 (8.8.8.8) from 192.168.10.100 : 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=117 time=7.60 ms
L'uso di traceroute (nota 6) permette di ricavare informazioni sul percorso
vv@router:~$ traceroute -s 192.168.10.100 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
1 192.168.10.1 (192.168.10.1) 1.444 ms * 1.351 ms
2 2.44.0.1 (2.44.0.1) 10.169 ms 10.187 ms 10.149 ms
3 83.224.46.174 (83.224.46.174) 10.597 ms 14.003 ms 14.062 ms
4 83.224.46.173 (83.224.46.173) 14.050 ms 14.003 ms 14.095 ms
5 185.210.48.3 (185.210.48.3) 13.741 ms 83.224.46.237 (83.224.46.237) 13.929
ms 185.210.48.3 (185.210.48.3) 14.096 ms
6 * 74.125.245.225 (74.125.245.225) 13.567 ms *
7 8.8.8.8 (8.8.8.8) 8.397 ms 10.698 ms 10.467 ms
vv@router:~$ traceroute -s 192.168.111.100 8.8.8.8
traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets
1 192.168.111.254 (192.168.111.254) 8.077 ms 8.443 ms 8.108 ms
2 10.4.0.254 (10.4.0.254) 8.419 ms 8.362 ms 8.262 ms
3 * * *
4 * 172.17.17.80 (172.17.17.80) 19.094 ms 21.164 ms
5 172.17.22.72 (172.17.22.72) 17.189 ms 172.17.16.18 (172.17.16.18) 17.202
ms 172.17.22.82 (172.17.22.82) 17.410 ms
6 172.19.177.24 (172.19.177.24) 17.594 ms 12.211 ms 12.578 ms
7 195.22.205.98 (195.22.205.98) 12.754 ms 13.511 ms 195.22.205.116
(195.22.205.116) 16.222 ms
8 72.14.219.236 (72.14.219.236) 19.999 ms 72.14.221.64 (72.14.221.64) 12.125
ms 72.14.216.154 (72.14.216.154) 14.726 ms
9 108.170.245.65 (108.170.245.65) 14.675 ms * 108.170.245.81
(108.170.245.81) 13.762 ms
10 8.8.8.8 (8.8.8.8) 11.455 ms 11.640 ms 72.14.234.75 (72.14.234.75) 10.850
ms
Infine per la conferma degli indirizzi IP pubblici (nota 7):
vv@router:~# wget -qO- ipinfo.io/ip
--bind-address=192.168.10.100
2.44.3.xxx
vv@router:~# wget -qO- ipinfo.io/ip
--bind-address=192.168.111.100
82.56.121.xxx
Così come è, questa doppia connessione non è particolarmente utile in quanto la maggior parte delle volte si preferisce non dover utilizzare esplicitamente l'indirizzo di binding. Un uso interessante potrebbe però essere quello di effettuare da linea di comando il download contemporaneo di due file, uno per ciascuna connessione, in pratica raddoppiando la velocità. Di seguito un esempio che utilizza contemporaneamente due linee FTTC da 55 Mbit/s, cioè 6.9 MB/s (nota 7):
vv@router:~# wget --bind-address=192.168.10.100 debian.zero.com.ar/debian-cd/10.4.0/amd64/iso-bd/debian-edu-10.4.0-amd64-BD-1.iso
[...]
debian-edu-10.4.0-amd64-BD-1.iso 78%[============================>
] 4,18G 5,96MB/s prev 4m 7s
vv@router:~# wget --bind-address=192.168.111.100 cdimage.debian.org/cdimage/weekly-builds/amd64/iso-dvd/debian-testing-amd64-DVD-1.iso
[...]
debian-testing-amd64-DVD-1.iso 9%[========> ] 348,70M 5,85MB/s prev 12m 3ss
Se sul router è presente un proxy, è possibile fargli utilizzare entrambe le linee. Qui è descritto come fare.
Nelle regole di una policy routing possono apparire anche altri selettori oltre al solo indirizzo sorgente. L'elenco può essere ottenuto direttamente dal manuale:
vv@router:~# ip rule help
[...]
SELECTOR := [ not ] [ from PREFIX ] [ to PREFIX ] [ tos TOS ] [ fwmark
FWMARK[/MASK] ]
[ iif STRING ] [ oif STRING ] [ pref NUMBER ] [ l3mdev ]
[ uidrange NUMBER-NUMBER ]
[ ipproto PROTOCOL ]
[ sport [ NUMBER | NUMBER-NUMBER ]
[ dport [ NUMBER | NUMBER-NUMBER ] ]
Per esempio il comando:
root@router:~# ip rule add from 192.168.10.100 ipproto ICMP table ISPA
Permette di indicare che la tabella ISPA verrà utilizzata solo dai pacchetti ICMP in uscita, in pratica il ping. Useremo questa possibilità in seguito.
Il nostro scopo non è configurare un host generico per sfruttare due connessioni Internet, ma un router che possa connettere alcune macchine della LAN usando la connessione ISP-A, altre macchine usando ISP-B
Per ottenere ciò è innanzitutto necessario attivare l'inoltro dei pacchetti:
root@router:~# nano /etc/sysctl.conf
# [...]
# Uncomment the next line to enable packet forwarding for IPv4
net.ipv4.ip_forward=1
# [...]
Occorre inoltre attivare il Source NAT (PAT secondo la terminologia Cisco) sulle due interfacce connesse ad internet (nota 8):
root@router:~# iptables -t nat -A POSTROUTING -o ens192 -j SNAT --to
192.168.111.100
root@router:~# iptables -t nat -A POSTROUTING -o ens256 -j SNAT --to
192.168.10.100
Infine serve aggiungere alle due tabelle di routing ISPA e ISPB le regole e le rotte qui sotto evidenziate:
ip route flush table ISPB
ip rule flush table ISPB
ip rule add from 192.168.111.100 table ISPB
ip rule add from 172.16.1.2 table ISPB
ip rule add from 172.16.1.4 table ISPB
ip route add 192.168.111.0/24 dev ens192 table ISPB
ip route add 172.16.0.0/16 dev ens224 table ISPB
ip route add default via 192.168.111.254 table ISPB
ip route flush table ISPA
ip rule flush table ISPA
ip rule add from 192.168.10.100 table ISPA
ip rule add from 172.16.2.0/24 table ISPA
ip route add 192.168.10.0/24 dev ens256 table ISPA
ip route add 172.16.0.0/16 dev ens224 table ISPA
ip route add default via 192.168.10.1 table ISPA
Si noti in particolare come tra le regole è possibile aggiungere come sorgente sia gli indirizzi di singoli host (primo blocco) che intere reti (secondo blocco).
Di seguito la schermata di due computer Windows 10 che mostrano IP privato ed IP pubblico, uno connesso tramite ISP-A e l'altro tramite ISP-B. Il sistema operativo installato sui computer utenti è irrilevante e non è richiesta alcuna configurazione particolare (nota 9)
Alcune delle impostazioni che abbiamo fatto sono persistenti e quindi non necessitano di ulteriori interventi:
Le impostazioni create con iptables possono essere rese persistenti digitando dopo ogni modifica:
root@router~# dpkg-reconfigure iptables-persistent
Tale comando rende persistenti anche tutte le (eventuali) regole del firewall gestite attraverso iptables.
Per rendere permanenti le tabelle di routing (regole e rotte) è possibile modificare /etc/network/interfaces, usando la parola chiave post-up e gli stessi comandi sopra descritti. In alternativa è possibile utilizzare uno script eseguito ad intervalli regolari, come quello mostrato per gestire il fail-over. Di seguito un esempio completo, coerente con la tabella già descritta:
root@router~# cat /etc/network/interfaces
source /etc/network/interfaces.d/*
# The loopback network interface
auto lo
iface lo inet loopback
# LAN
auto ens224
iface ens224 inet static
address 172.16.0.1/16
# ISP-B
auto ens192
iface ens192 inet static
address 192.168.111.100/24
post-up ip rule add from 192.168.111.100 table ISPB
post-up ip rule add from 172.16.1.2 table ISPB
post-up ip rule add from 172.16.1.4 table ISPB
post-up ip route add 192.168.111.0/24 dev ens192 table ISPB
post-up ip route add 172.16.0.0/16 dev ens224 table ISPB
post-up ip route add default via 192.168.111.254 table ISPB
# ISP-A
auto ens256
iface ens256 inet static
address 192.168.10.100/24
post-up ip rule add from 192.168.10.100 table ISPA
post-up ip rule add from 172.16.2.0/24 table ISPA
post-up ip route add 192.168.10.0/24 dev ens256 table ISPA
post-up ip route add 172.16.0.0/16 dev ens224 table ISPA
post-up ip route add default via 192.168.10.1 table ISPA
Non penso servano commenti.
Unica avvertenza: i vari comandi devono utilizzare solo reti ed indirizzi già attivati. In particolare l'interfaccia LAN deve essere attivata per prima in quanto alcuni comandi successivi utilizzano gli indirizzi della rete 172.16.0.0/16.
Ipotizziamo il seguente scenario: una delle due connessioni ad Internet si interrompe.
L'idea è utilizzare solo l'altra connessione per tutto il traffico fino a quando entrambe le linee linea tornano operative (nota 11).
Per fare ciò utilizzeremo uno script che:
Lo script potrebbe essere il seguente, da adattare alla proprio rete e qui relativo allo stesso scenario descritto in questa pagina (nota 13):
root@router:~# cat /etc/script/fail-over.sh
#!/bin/bash
wanA=192.168.10.100
gwA=192.168.10.1
nameA=ISP-A
wanB=192.168.111.100
gwB=192.168.111.254
nameB=ISP-B
test_host=8.8.8.8 # Deve essere un serve affidabile
ping_c=2 # Numero di test da effettuare. Con linea molto carica, un
raramente un ping potrebbe fallire
cur_date=`date "+%Y %b %d %H:%M"`
# Il ping ha successo per le due connessioni?
testA=`ping -I $wanA -c $ping_c $test_host | grep "64 bytes" | wc -l`
testB=`ping -I $wanB -c $ping_c $test_host | grep "64 bytes" | wc -l`
# Esistono regole specifiche per pacchetti ICMP, indice di un precedente
malfunzionamento
icmpA=`ip rule show table ISPA | grep icmp | wc -l`
icmpB=`ip rule show table ISPB | grep icmp | wc -l`
# Il test attuale ha successo, ma un test precedente ha disattivato una
connessione. Riattivo ISPA
if [ $testA -gt 0 ] && [ $icmpA -eq 1 ]; then
echo "$cur_date $nameA links come back"
ip rule flush table ISPA
ip rule add from $wanA table ISPA
fi
# Il test fallisce; passo alla connessione predefinita e disattivo la
tabella ISPA (tranne che per gli ICMP)
if [ $testA -eq 0 ]; then
echo "$cur_date $nameA links is Down"
ip route del default
ip route add default via $gwB
ip rule flush table ISPA
ip rule add from $wanA ipproto ICMP table ISPA
fi
if [ $testB -gt 0 ] && [ $icmpB -eq 1 ]; then
echo "$cur_date $nameB links come back"
ip rule flush table ISPB
ip rule add from $wanB table ISPB
fi
if [ $testB -eq 0 ]; then
echo "$cur_date $nameB links is Down"
ip route del default
ip route add default via $gwA
ip rule flush table ISPB
ip rule add from $wanB ipproto ICMP table ISPB
fi
Questo script deve essere eseguito con una certa frequenza; per esempio è possibile utilizzare cron, avviandolo una volta il minuto:
root@router:~# cat /etc/crontab
[...]
* * * * * root /etc/script/fail-over.sh
#
Di seguito l'output generato leggendo l'IP pubblico mentre, manualmente, è stato staccato il cavo fisico utilizzato dai due provider, ad intervalli di qualche minuto (nota 12); si notino i due indirizzi pubblici diversi oppure uguali, in funzione del numero di linee funzionanti.
vv@router:~# wget -qO- ipinfo.io/ip
--bind-address=192.168.111.100
82.56.121.xxx
vv@router:~# wget -qO- ipinfo.io/ip
--bind-address=192.168.10.100
2.44.3.xxx
vv@router:~# wget -qO- ipinfo.io/ip
--bind-address=192.168.111.100
2.44.3.xxx
vv@router:~# wget -qO- ipinfo.io/ip
--bind-address=192.168.10.100
2.44.3.xxx
vv@router:~# wget -qO- ipinfo.io/ip
--bind-address=192.168.111.100
82.56.121.xxx
vv@router:~# wget -qO- ipinfo.io/ip
--bind-address=192.168.10.100
2.44.3.xxx
vv@router:~# wget -qO- ipinfo.io/ip
--bind-address=192.168.111.100
82.56.121.xxx
vv@router:~# wget -qO- ipinfo.io/ip
--bind-address=192.168.10.100
82.56.121.xxx
Un problema: ogni volta che viene modificata una tabella di routing si interrompono le connessioni che la utilizzano, causando, a seconda dell'applicazione, interruzioni di qualche secondo o, addirittura, il blocco dell'applicazione. Questo metodo funziona quindi solo nel caso in cui entrambe le reti possono garantire il funzionamento continuo di almeno qualche ora consecutiva.
Data di creazione di questa pagina: luglio 2020
Ultima modifica: 12 gennaio 2o22
Il taccuino tecnico - Permanentemente in fase di
riscrittura
Copyright 2013-2024, Vincenzo Villa (https://www.vincenzov.net)
Quest'opera è stata rilasciata con licenza Creative Commons | Attribuzione 4.0 Internazionale (CC BY 4.0)