spacer

Sysresccd-networking it Destination-port-routing

History


Il routing della porta di destinazione

In casi normali, routing sul protocollo TCP/IP, la porta di destinazione è basata sull'indirizzo IP del destinatario. Questo articolo si focalizza su come impostare delle scelte sulla porta di destinazione. Questo potrebbe essere usato per dividere il traffico tra molti links. E' inoltre molto utile su reti particolarmente cariche. Per esempio, potresti voler instradare il traffico SSH usando un link di tipo ADSL, ed il traffico web su un altro link ADSL. E' anche possibile prevenire le sessioni interattive prima che diventino inattive. Se tutti i pacchetti in uscita sono instradati ad un singolo link ADSL, con nessun controllo del traffico, la vostra sessione ssh/telnet/vnc potrebbe diventare inattiva, esattamente come se quanlcuno scaricasse un pesante file dalla vostra rete. Con questa tecnica potete instradare tutto il traffico interattivo ad una linea dedicata, in modo da mantenere una trasmissione veloce di pacchetti verso il server.

Non avete bisogno di nulla di particolare, per creare un routing avanzato. Dovrebbe funzionare su tutte le versioni di Linux con kernel 2.6 (non è sicuro che lavori con linux-2.4). Tutte le caratteristiche che usiamo, sono all'interno della mainline kernel, non avete quindi bisogno di un una specifica kernel patch. Dovete stare attenti alle opzioni di rete, se compilate il vostro kernel. Avrete anche bisogno di iproute2 package e dei networking tools di base installati, ma sono all'interno di tutte le principali distribuzioni linux.

Esempi di ambienti di rete

Per spiegare come fare il routing della porta di destinazione, useremo i seguenti ambienti di rete:

  • Sono presenti tre macchine nella rete: saturn, jupiter, neptune.
  • Jupiter è il router principale della rete. Tutta l'installazione della rete viene fatta da questa macchina
  • Sono presenti due link tra Jupiter e Neptune. In questo esempio di test noi usiamo solo semplici link ethernet. Questi link potrebbero essere link esterni (Adsl, cavo, ...) se usassimo un indirizzo IP pubblico alla loro fine. L'installazione del routing su Jupiter sarebbe la stessa.
  • Saturn e Neptune sono localizzati al termine della rete. Vogliamo che Saturn spedisca pacchetti a Neptune usando i due link.
  • La proposta di questo articolo è di fare tutta la configurazione della rete su Jupiter. Tutte le decisioni sul routing sono fatte su questa macchina. Non sarà necessario creare un advanced routing su Saturn o Neptune.
  • Noi vogliamo che un tipo di pacchetti inviati a Neptune siano spediti attraverso il primo link (il traffico ssh, quindi tutti i pacchetti TCP con dport=22), e un altro tipo di pacchetti a Neptune attraverso un secondo link (sarà il traffico web, quindi tutti i pacchetti TCP con dport=80).
  • Noi vogliamo che il routing sia sdimmetrico. Questo significa che una volta che la connessione TCP è stabilita tra Saturn e Neptune, tutti i pacchetti che appartrengono al TCP stream vadano sullo stesso link. Non vogliamo usare un routing asimettrico. Nel routing asimmetrico i pacchetti da Saturn a Neptune potrebbero usare il link1, e le risposte a questi pacchetti potrebbero essere spedite attraverso un secondo link. Potrebbe funzionare se il Reverse Path Filtering fosse spento, ma questo dovrebbe significare che i pacchetti da Neptune a Saturn non rispettano il routing che l'amministratore vorrebbe avere.
  • Tutti i test sono fatti con Saturn, essendo il client, e Neptune fa da server. Quindi tutte le connessioni TCP sono stabilite da Saturn.
  • Per realizzare questa rete nel modo più semplice, configuriamo una interfaccia dummy0 su Neptune con 172.16.1.100. L'idea è di dividere il traffico da Saturn (indirizzo mittente 192.168.157.3) a Neptune (indirizzo di destinazione 172.16.1.100) e di dividerli tra i due link. Nella vita reale probabilmente avrete molte macchine dietro Neptune. L'interfaccia dummy0 ci permette di avere un indirizzo IP su Neptune che non è legato ai due link. Questo dà l'illusione che 172.16.1.100 sia l'indirizzo di un computer remoto dietro Neptune e dietro i due link.

dport-routing-01.png


Overview del routing

Adesso guardiamo come possiamo fare il routing della porta di destinazione. Il vecchio percorso, e il recente iproute2 tools non hanno opzioni per selezionare i pacchetti usando la porta di destinazione. Ma ip rule ha interessanti opzioni che usano attributi del pacchetto per decidere come instradarli.

Noi useremo l'attributo fwmark per fare questo. Questo attributo non appartiene all'intestazione del pacchetto IP, è solamente memorizzato nella memoria della macchina locale che lavora sul pacchetto. Ciò significa che decaderà non appena il pacchetto avrà lasciato il router. Comunque, questo è tutto ciò che ci serve finchè questo attributo può essere usato sia da netfilter che da iproute2. La prima cosa da fare è usare le opzioni avanzate di packet matching date con iptables e netfilter per "segnare" i pacchetti. Una volta che i pacchetti sono stati marcati, possiamo usare iproute2 per seguire le regole di routing e usare questo attributo.

Ovviamente il pacchetto deve essere marcato da netfilter prima di raggiungere la cosa di routing. Questo perchè è importante ricordare quando netfilter lavora sui pacchetti. Netfilter ha 5 ganci nella kernel stack, ciò significa che ci sono 5 posti in cui le funzioni di netfilter possono lavorare sui pacchetti. Qui ci sono i tipi di pacchetti che possono essere visti da ognuno dei cinque ganci:

  • PREROUTING: tutti i pacchetti entranti qualunque sia l'indirizzo di destinazione
  • POSTROUTING: tutti i pacchetti uscenti qualunque sia l'indirizzo mittente
  • FORWARD: tutti i pacchetti che sono instradati
  • INPUT: tutti i pacchetti che sono spediti alla macchina locale
  • OUTPUT: tutti i pacchetti spediti dalla macchina locale

Quindi se noi marchiamo i pacchetti come POSTROUTING, la coda di routing non vedrà il segno e l'advanced routing non avrà effetto. Questo è il perchè noi dobbiamo lavorare nel gancio PREROUTING dei pacchetti entranti, e nell'OUTPUT se vogliamo instradare i pacchetti spediti dal router stesso.


dport-routing-02.png


Etichettare i pacchettio con iptables

Netfilter e iptables lavorano con tre tabelle:

  • filter: la tabella più importante, è maggiormente usata per l'attività di firewall, per accettare o rifiutare pacchetti
  • nat: è usata per il Network Address Translation
  • mangle: è maggiormente usata per modificare i pacchetti

Noi lavoreremo con la tabella mangle finchè noi vorremo cambiare l'attributo di un pacchetto. Vogliamo dividere il traffico tra i due link. Consideriamo che noi vogliamo indirizzare il traffico ssh attraverso il primo link e il traffico web attraverso il secondo. Etichetteremo, quindi, i pacchetti TCP con dport=22 (porta di destinazione) con mark=1 e i pacchetti TCP aventi dport=80 con mark=2:

iptables -t mangle -A PREROUTING -i eth0 -p tcp -m tcp --dport 22 -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j MARK --set-mark 2

Qui è riportato il codice completo che pulisce la prima tabella e ne fa il logging:

iptables -t mangle -F
iptables -t mangle -X
iptables -t mangle -N LOG_FWMARK1
iptables -t mangle -A LOG_FWMARK1 -j LOG --log-prefix 'iptables-mark1: ' --log-level info
iptables -t mangle -A LOG_FWMARK1 -j MARK --set-mark 1
iptables -t mangle -N LOG_FWMARK2
iptables -t mangle -A LOG_FWMARK2 -j LOG --log-prefix 'iptables-mark2: ' --log-level info
iptables -t mangle -A LOG_FWMARK2 -j MARK --set-mark 2
iptables -t mangle -A PREROUTING -i eth0 -p tcp -m tcp --dport 22 -j LOG_FWMARK1
iptables -t mangle -A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j LOG_FWMARK2

Instradare i pacchetti etichettati

Per instradare i pacchetti con l'attributo modificato, dobbiamo usare il comando ip rule, che rappresenta le regole per l'instradamento. Dobbiamo creare una tabella di routing secondaria che verrà usata quando l'attributo mark del pacchetto risponderà alla regola.

Creare una nuova tabella di routing

Per prima cosa dobbiamo creare due tabelle di routing da editare con /etc/iproute2/rt_tables. Qui viene riportato il codice che automaticamente crea due tabelle chiamate rt_link1 e rt_link2.

if ! cat /etc/iproute2/rt_tables | grep -q '^251'
then
       echo '251     rt_link1' >> /etc/iproute2/rt_tables
fi
if ! cat /etc/iproute2/rt_tables | grep -q '^252'
then
       echo '252     rt_link2' >> /etc/iproute2/rt_tables
fi

Qui di seguito viene anche riportata la lista delle tabelle di routing che dovremmo avere su Jupiter:

# -----------/etc/iproute2/rt_tables------------
# reserved values
255     local
254     main
253     default
0       unspec
# custom routes
252     rt_link2
251     rt_link1

Adesso dobbiamo riempire le due tabelle. La cosa migliore da fare è di aggiungere un percorso di default in ogni tabella. Ogni percorso di default guida i pacchetti alla scheda ethernet dove il link da usare è connesso. In questo modo, quando un pacchetto con dport=22 segue il percorso di default scritto su rt_link1, questo verrà spedito a Neptune attraverso il device eth1. Possiamo anche usare in parallelo ip route per essere sicuri che la tabella sia vuota.

ip route flush table rt_link1
ip route add table rt_link1 default dev eth1
ip route flush table rt_link2
ip route add table rt_link2 default dev eth2

Usare le nuove tabelle con le regole di routing

Adesso dobbiamo usare il comando ip rule per dire cosa fare con i pacchetti etichettati. Le linee seguenti dicono che i pacchetti aventi fwmark=1 devono seguire le istruzioni di routing della tabella chiamata rt_link1, e i pacchetti con la seconda etichetta devono usare rt_link2. Al termine scaricheremo le cache del routing per essere sicuri che le nuove regole siano state assimilate nell'account.

ip rule del from all fwmark 2 2>/dev/null
ip rule del from all fwmark 1 2>/dev/null
ip rule add fwmark 1 table rt_link1
ip rule add fwmark 2 table rt_link2
ip route flush cache

Qui c'è la lista di tutte le regole dopo aver eseguito i comandi:

# ip rule show
0:      from all lookup local
32764:  from all fwmark 0x2 lookup rt_link2
32765:  from all fwmark 0x1 lookup rt_link1
32766:  from all lookup main
32767:  from all lookup default

Parametri di rete di Linux

Sono presenti due parametri che devono essere selezionati se si vuole che il router si comporti nel modo aspettato. Per prima cosa dobbiamo essere sicuri che il kernel su Jupiter sia configurato per instradare i pacchetti. Per abilitare il routing su IPv4 bisogna settare ip_forward a 1 (1 significa abilitato, 0 disabilitato).

echo 1 >| /proc/sys/net/ipv4/ip_forward

Bisogna anche disabilitare il Reverse Path Filtering. Questa è un'opzione abilitata di default che incrementa la sicurezza e previene che l'indirizzo del mittente dei pacchetti entranti si accordi con la tabella di routing sulla macchina locale. Nel caso di un'installazione complessa , questa opzione farà cadere i pacchetti, per questo deve essere disabilitata.

echo 0 >| /proc/sys/net/ipv4/conf/all/rp_filter

Questi cambiamenti verranno persi nel caso venga riavviato il server. Puoi assicurarti che questo venga eseguito in automatico grazie ad uno script durante l'avvio, oppure si possono editare i file di configurazione della rete per essere sicuri che questi cambiamenti verranno tenuti anche dopo il reboot. Su Gentoo e Redhat bisogna editare /etc/sysctl.conf:

# /etc/sysctl.conf
# 
# Enables packet forwarding
net.ipv4.ip_forward = 1
# Disable reverse path filtering
net.ipv4.conf.all.rp_filter = 0

Source Network-Address-Translation (SNAT

Adesso i pacchetti dovrebbero essere instradati da Saturn a Neptune, come aspettato. Ma c'è ancora un problema da risolvere. Le risposte spedite da Neptune a Saturn ignoreranno il routing e verranno sempre spedite attraverso lo stesso link, quello che segue il percorso a 192.168.157.3 che è configurato su Neptune. Quando Neptune riceve i pacchetti da Saturn, l'indirizzo mittente è 192.168.157.3. Fino a quando non viene configurato il routing anche su Neptune, i pacchetti verso Saturn seguiranno il percorso normale.

Questo è il caso di una rete asimmetrica. I pacchetti da Saturn a Neptune, avendo dport=80 vengono instradati attraverso il secondo link grazie alle regole di routing che abbiamo configurato su Jupiter. E le risposte a questi pacchetti sono spediti attraverso il primo link, perchè segue il routing normale. Una soluzione a questo problema potrebbe essere configurare le regole di routing anche su Neptune, come su Saturn. Ma noi vogliamo mantenere la configurazione il più semplice possibile e vogliamo configurare solamente su Neptune.

La cosa migliore è di configurare SNAT (Source Network-Address-Translation) su Jupiter così che tutti i pacchetti spediti attraverso link1 o link2 arrivino con un indirizzo mittente assegnato. Vogliamo che i pacchetti del link1 abbiamo come indirizzo mittente 10.37.1.253 mentre i pacchetti del link2 lindirizzo 10.37.2.253. In questo modo Neptune riceverà pacchetti con un indirizzo mittente che seguirà i link esattamente come sono venuti. Quando Neptune risponde alle richieste derivanti da link1 o link2 userà l'indirizzo mittente visto in questi pacchetti come nuovo indirizzo di destinazione.

Si può anche notare che SNAT coinvolge un implicito DNAT (Destination Network-Address-Translation). Quando Jupiter riceve i pacchetti su eth2 (l'interfaccia dove è connesso il secondo link), funziona perchè l'indirizzo destinazione è 10.37.2.253. Questa è una risposta al pacchetto da Saturn (192.168.157.3), quindi noi vogliamo che Jupiter cambi l'indirizzo destinazione e inoltrarlo a Saturn. Questo viene fatto da un implicito DNAT.

E' importante notare che l'indirizzo mittente NAT è eseguito in POSTROUTING. Quindi viene eseguito dopo l'instradamento, che è il punto in cui vengono guidati i pacchetti verso il giusto device (o eth1 o eth2 su Jupiter). La iptable SNAT usa l'"outgoing device" per determinare quale indirizzo mittente deve essere scritto nell'intestazione del pacchetto.

Nel caso in cui si usi link ADSL tra Jupiter e Neptune, bisognerà forzare l'uso dell'indirizzo IP pubblico fuori dalla rete locale. La maggior parte dei modem posso fare il NAT da soli. In questo caso non ti devi preoccupare.

Qui c'è il codice per configurare SNAT su Jupiter:

iptables -t nat -F
iptables -t nat -X
iptables -t nat -A POSTROUTING -o eth1 -j SNAT --to-source 10.37.1.253
iptables -t nat -A POSTROUTING -o eth2 -j SNAT --to-source 10.37.2.253


Soluzione Problemi

Nel caso in cui qualcosa non funzionasse:

Controlla il tuo firewall

In questo articolo è stato considerato che il filtraggio dei pacchetti non fosse abilitato sul router o sulla tua rete. Nel caso in cui usassi già iptables dovrai controllare che le nuove tabelle di routing modifichino la porta di destinazione.

Accedere ai pacchetti con iptables

Puoi anche usare iptables per accedere ai pacchetti interessati usando -j LOG nelle tue regole. Non dimenticarti di installare syslog sulla tua macchina. Viene riportato un esempio di cosa puoi ottenere:

iptables-mark1: IN= OUT=eth1 SRC=10.37.1.100 DST=10.37.3.101 LEN=60 TOS=0x00 PREC=0x00 TTL=64 
                ID=53236 DF PROTO=TCP SPT=44443 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0
iptables-mark1: IN= OUT=eth1 SRC=10.37.1.100 DST=10.37.3.101 LEN=60 TOS=0x00 PREC=0x00 TTL=64 
                ID=53237 DF PROTO=TCP SPT=44443 DPT=22 WINDOW=5840 RES=0x00 SYN URGP=0

Per abilitare il logging, puoi sostituire una semplice azione iptables (come il MARK) con una personalizzata (cone LOG_FWMARK). Ogni volta che un pacchetto viene etichettato avrai anche un messaggio scritto nei tuoi log. Puoi sostituire questo semplice comando:

iptables -t mangle -A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j MARK --set-mark 2

Con il seguente:

iptables -t mangle -N LOG_FWMARK2
iptables -t mangle -A LOG_FWMARK2 -j LOG --log-prefix 'iptables-mark2: ' --log-level info
iptables -t mangle -A LOG_FWMARK2 -j MARK --set-mark 2
iptables -t mangle -A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j LOG_FWMARK2

Uasare uno sniffer di rete

Puoi usare uno sniffer come tcpdump (console) o wireshark (metodo grafico) per individuare quali pacchetti vengonmo trasmessi e con quale attributo.

Routing configuration su Saturn

Anche se il 95% della configurazione di rete deve essere fatta sul router (Jupiter) non dimenticare di settare il percorso a Neptune su Saturn. Qui c'è quello che devi fare su Saturn:

ip route add 176.16.1.100 via 192.168.157.253

Viaggio dettagliato di un pacchetto

Per capire meglio come funziona questa configurazione di routing, prendiamo un semplice esempio di un pacchetto di rete spedito da Saturn a Neptune. Consideriamo che l'utilizzatore si voglia collegare a Neptune via ssh. Nel nostro esempio si suppone che i pacchetti ssh vengano instradati attraverso il secondo link su Jupiter.

  • Su Saturn, l'utilizzatore avvia ssh 176.16.1.100 per connettersi a Neptune.
  • Saturn trova che i pacchetti a 176.16.1.100 devono essere inviati attraverso 192.168.157.253 così il livello link di Saturn manda il pacchetto a Jupiter. Questo pacchetto contiene ip.src=192.168.157.3, ip.dst=172.16.1.100, tcp.dport=22.
  • Su Jupiter, il pacchetto viene prima processato da PREROUTING. L'istruzione aggiunta in mangle con "-i eth0 -p tcp -m tcp --dport 80 -j MARK --set-mark 2" viene eseguita e viene scritto l'attributo mark=2 all'interno delle informazioni del pacchetto nella memoria di Jupiter.
  • Su Jupiter il routing code trova che il pacchetto non viene spedito al suo indirizzo locale, così il pacchetto viene instradato. Questo segue le regole di routing, raggiungendo la regola che dice che "i pacchetti aventi attributo mark=2 devono usare la routing table chiamat rt_link2". Fino a che il percorso di default in questa tabella di routing è "default dev eth2" il pacchetto è spedito al device chiamato eth2.
  • Su Jupiter, in POSTROUTING, il pacchjetto segue "-i eth0 -o eth2" quindi "-j SNAT --to-source 10.37.2.253" viene eseguito. Il codice SNAT riscrive l'indirizzo mittente nel pacchetto:
    • PRIMA di SNAT: ip.src=192.168.157.3, ip.dst=172.16.1.100, tcp.dport=22
    • DOPO SNAT: ip.src=10.37.2.253, ip.dst=172.16.1.100, tcp.dport=22
  • Il pacchetto viene spedito da Jupiter a Neptune con ip.src=10.37.2.253, ip.dst=172.16.1.100, tcp.dport=22
  • Su Neptune il pacchetto viene consegnato al servizio locale ssh perchè la destinazione ip è 172.16.1.100 che è l'indirizzo locale. Il server ssh accetta la connessione e risponde con un altro pacchetto. La risposte viene spedita all'indirizzo che che era l'indirizzo mittente nel pacchetto arrivato, quindi il nuovo indirizzo mittente sarà 10.37.2.253. Il pacchetto di risposta viene spedito con i seguenti attributi: ip.src=172.16.1.100, ip.dst=10.37.2.253, tcp.sport=22
  • Su Neptune, il codice routing trova che 10.37.2.253 appartiene alla sottorete di eth2. Così il pacchetto viene spedito a Jupiter attraverso eth2.
  • Jupiter riceve la risposta da Neptune su eth2. Il pacchetto incontra il codice DNAT che viene eseguito quando c'è uno SNAT. Il Destination NAT sostituisce l'indirizzo di destinazione nel pacchetto di risposta con l'indirizzo che era scritto nell'intestazione prima che fosse riscritto dal codice SNAT:
    • PRIMA di DNAT: ip.src=172.16.1.100, ip.dst=10.37.2.253, tcp.sport=22
    • DOPO DNAT: ip.src=172.16.1.100, ip.dst=192.168.157.3, tcp.sport=22
  • Su Jupiter il codice di routing sa che il pacchetto a 192.168.157.0/24 deve essere consegnato attraverso eth0
  • Jupiter spedisce la risposta da Neptune a Saturn attraverso eth0 con ip.src=172.16.1.100, ip.dst=192.168.157.3, tcp.sport=22
  • Saturn riceve la risposta da Neptune e Saturn manda un nuovo pacchetto TCP, ...

Script completo di configurazione del router

Questo è lo script per eseguire su Jupiter la configurazione della porta di destinazione:

#!/bin/bash
echo 1 >| /proc/sys/net/ipv4/ip_forward
echo 0 >| /proc/sys/net/ipv4/conf/all/rp_filter
iptables -t mangle -F
iptables -t mangle -X
iptables -t mangle -A PREROUTING -i eth0 -p tcp -m tcp --dport 22 -j MARK --set-mark 1
iptables -t mangle -A PREROUTING -i eth0 -p tcp -m tcp --dport 80 -j MARK --set-mark 2
iptables -t nat -F
iptables -t nat -X
iptables -t nat -A POSTROUTING -o eth1 -j SNAT --to-source 10.37.1.253
iptables -t nat -A POSTROUTING -o eth2 -j SNAT --to-source 10.37.2.253
if ! cat /etc/iproute2/rt_tables | grep -q '^251'
then
       echo '251     rt_link1' >> /etc/iproute2/rt_tables
fi
if ! cat /etc/iproute2/rt_tables | grep -q '^252'
then
       echo '252     rt_link2' >> /etc/iproute2/rt_tables
fi
ip route flush table rt_link1
ip route add table rt_link1 default dev eth1
ip route flush table rt_link2
ip route add table rt_link2 default dev eth2
ip rule del from all fwmark 2 2>/dev/null
ip rule del from all fwmark 1 2>/dev/null
ip rule add fwmark 1 table rt_link1
ip rule add fwmark 2 table rt_link2
ip route flush cache
spacer