Home/
 www.icosaedro.it 

 Virtual Private Network

In breve

Si descrive una soluzione VPN (Virtual Private Network) basata su S.O. Linux. Il canale autenticato e crittato viene creato con OpenSSH. A differenza di altre soluzioni analoghe, qui non si fa uso di altri programmi oltre al pppd e ssh e si sfrutta l'opzione pty disponibile nelle nuove versioni di pppd.

La stessa soluzione si trova descritta nel documento "VPN PPP-SSH Mini-HOWTO" di Scott Bronson citato nei riferimenti, che contiene anche una utile discussione di questa tecnica e contiene un quadro delle soluzioni alternative disponibili.

Storico degli aggiornamenti

2002-01-24
Inaugurazione di questa sezione.

Introduzione

Le VPN sono sempre più diffuse come soluzione WAN (Wide Area Network) a basso costo: esse sfruttano una rete pubblica preesistente, come Internet, per mettere in comunicazione fra di loro reti locali distanti. Un'altra applicazione fa uso di una VPN per mettere in comunicazione tra di loro due o più macchine che, per ragioni di sicurezza, devono comunicare fra loro in modo riservato e al riparo da ogni possibile intercettazione, come ad esempio in un sistema three-tier.

Il primo tipo di applicazione resta la più diffusa: tipicamente avremo la sede centrale di una azienda, dotata di una LAN (Local Area Network), che intende condividere i servizi di rete locale con la LAN di una o più sedi distaccate. I computer così interconnessi possono avvalersi delle soluzioni di rete tipiche di una LAN, come la condivisione dei file e delle stampanti, esattamente come se tutti i computer fossero interconnessi alla stessa LAN.

Per rendere la soluzione ancora più versatile ed economica, si può usare un sistema operativo come GNU/Linux con l'aggiunta del programma OpenSSH (o SSH). Di solito la stessa macchina funzionerà anche da gateway, firewall e router per la LAN, e magari svolgerà anche funzioni di mail server, WEB server, file server, ...

I nuovi kernel Linux incorporano una versione di ppp che implementa l'opzione pty: grazie a questa opzione è ancora più semplice realizzare un canale PPP senza bisogno di avvalersi di altri programmi (come ptyredir, tipicamente usato in altre soluzioni VPN di questo tipo).

Il nostro modello

L'installazione prototipo alla quale faremo riferimento prevede il collegamento tra una macchina A e una macchina B facenti parte di due reti locali distinte LAN A e LAN B rispettivamente. Le due macchine sono anche gateway Internet ed eseguono anche il masquerading degli indirizzi. Ecco il diagramma delle connessioni:

          LAN A                                  LAN B               
PC-A-1 -----------|                           |----------- PC-B-1    
PC-A-2 ---------| |                           | |--------- PC-B-2    
PC-A-3 -------| | |                           | | |------- PC-B-3    
PC-A-4 -----| | | |                           | | | |----- PC-B-4    
...         | | | |                           | | | |      ...       
            | | | |                           | | | |                
          +-o-o-o-o-+                       +-o-o-o-o-+              
          |   hub   |                       |   hub   |              
          +----o----+                       +----o----+              
               |                                 |                   
               |                                 |                   
               | eth0 (192.168.0.1)              | eth0 (192.168.1.1)
        +------o------+                   +------o------+            
        |             |                   |             |            
        |      A      |                   |      B      |            
        |             |                   |             |            
        +------o------+                   +------o------+            
               | eth1 (11.11.11.11)              | eth1 (22.22.22.22)
               |                                 |                   
               |                                 |                   
    ********************************************************         
    *                                                      *         
    *                                                      *         
    *                                                      *         
    *                   I n t e r n e t                    *         
    *                                                      *         
    ********************************************************         
Figura 1. Schema del sistema VPN.

Le reti locali usano la classica numerazione in classe C 192.168.x.y, dove x=0 per LAN A e x=1 per LAN B. Di conseguenza i computer client della LAN A avranno indirizzi della forma 192.168.0.y, mentre i computer client della LAN B avranno indirizzi della forma 192.168.1.y.

Le due reti locali si collegano ad Internet attraverso i gateway A e B. Ciascun gateway e' dotato di due interfacce di rete: eth0 verso la LAN e eth1 verso Internet. Tipicamente eth1 viene collegato ad un router o a un modem ADSL dotato di interfaccia Ethernet. A volte il collegamento avviene via PPTP, e allora l'interfaccia per il collegamento ad Internet da considerare nel nostro discorso sara' una porta PPP di nome ppp0, e quindi dove qui scrivero' "eth1" bisognera' sostituire in questo caso "ppp0".

I gateway rendono irraggiungibili dall'esterno i computer delle reti locali, mentre viceversa i computer delle reti locali possono raggiungere Internet perche' i gateway svolgono la funzione di NAT o di proxy.

Il problema

Consideriamo il computer PC-A-1 con indirizzo, poniamo, 192.168.0.101 inserito nella LAN-A, e consideriamo un altro computer PC-B-1 inserito nella LAN-B con indirizzo, poniamo, 192.168.1.101. Sebbene questi due computer possano comunicare sulla propria rete locale e con Internet, non possono comunicare direttamente tra di loro per vari motivi:

Tuttavia vogliamo comunque far comunicare i computer PC-A e PC-B sfruttando lo stesso canale di comunicazione Internet: ci serve una soluzione VPN.

La strategia

L'idea, in sostanza, e' questa: stabilisco un canale di comunicazione basato sul protocollo SSH tra le macchine A e B; all'interno di questo canale apro un collegamento PPP; impostando opportunamente forwarding, masquerading e routing alle due estremita' del canale, ottengo la VPN.

Una volta attivato il collegamento VPN, tutti i computer della LAN-A potranno comunicare direttamente con tutti i computer della LAN-B. Vediamo come la cosa funziona seguendo il percorso di un pacchetto emesso da PC-A (un generico computer della LAN-A) verso PC-B:

  1. Siccome la maschera di sottorete di PC-A e' 255.255.255.0, risulta che il pacchetto non sta nella sottorete, per cui viene inviato al gateway A per il necessario forwarding.
  2. Il gateway A riceve il pacchetto e, basandosi su di una tabella di routing appositamente predisposta, scopre che questo pacchetto deve essere inviato attraverso il canale VPN.
  3. Come vedremo, il pacchetto viene incapsulato in un pacchetto del protocollo PPP, viene crittato con l'algoritmo Blowfish, e quindi spedito via Internet verso il computer B.
  4. Il computer B riceve il pacchetto, lo decritta, lo estrae dal protocollo PPP e, in base alla sua tabella di routing, lo forwarda verso PC-B.

I pacchetti che vanno da PC-B verso PC-A subiscono un procedimento analogo.

Qualora si debbano collegare fra di loro piu reti LAN che devono comunicare via VPN con i computer della sede centrale installati su LAN-A, la struttura logica della VPN sara' una stella con al centro il computer A. Gli indirizzi da assegnare ai computer delle varie reti potrebbero essere cosi' scelti:

        Rete         Indirizzo     Netmask
        ----         -----------   -------------
        LAN-A        192.168.0.x   255.255.255.0
        LAN-B        192.168.1.x   255.255.255.0
        LAN-C        192.168.2.x   255.255.255.0
        ...          ...           ...

Su ogni LAN ci sara' un computer A, B, C con funzione di gateway Internet e di router per la VPN. La struttura logica delle interconnessioni tra le varie LAN ha la forma di una stella, con il gateway A al centro:

                          B ----- LAN B      
                         /                   
                        /                    
                       /                     
        LAN A ------ A -------- C ----- LAN C
                       \                     
                        \                    
                         \                   
                          D ----- LAN D      
Figura 2. Struttura a stella della WAN.

Installazione di SSH

Il protocollo SSH permette la comunicazione autenticata e crittata tra due macchine: nella macchina client A bastera' installare il client ssh, mentre nella macchina B si installera' il server sshd.

Per brevita' qui indichero' con A il numero di nodo IP o il nome pubblici del primo gateway, e similmente B e' il secondo gateway. Inoltre la scrittura root@A individua l'utente root su A, e similmente root@B individua l'utente root su B.

L'utente su A che avvia la VPN dovra' potersi autenticare in modo automatico su B. Per evitare che venga richiesta la password, A deve creare la sua coppia di chiavi e poi copiare la chiave pubblica nel keyring di B. Per creare la propria coppia di chiavi, A dovra' fare:

        root@A # ssh-keygen -t rsa1
        Generating RSA keys:  .............ooooooO................ooooooO
        Key generation complete.
        Enter file in which to save the key (/root/.ssh/identity):
        Enter passphrase (empty for no passphrase): (QUI PREMERE SOLO ENTER!)
        Enter same passphrase again: (QUI PREMERE SOLO ENTER!)
        Your identification has been saved in /root/.ssh/identity.
        Your public key has been saved in /root/.ssh/identity.pub.
        The key fingerprint is:
        19:e6:25:7b:df:7e:25:79:4a:b2:43:bc:44:cc:0b:c7 root@A

Come indicato nel dialogo, ora root@A possiede una chiave segreta nel file /root/.ssh/identity e una chiave pubblica nel file /root/.ssh/identity.pub.

Notare che abbiamo lasciato vuota la passphrase: cio' significa che la chiave segreta di A rimane in chiaro, cioe' non viene crittata: questo e' essenziale, altrimenti all'atto del collegamento verrebbe richiesta la passphrase e l'automatismo di avviamento non funzionerebbe.

Inviare a B la chiave pubblica identity.pub cosi' generata:

        root@A # scp /root/.ssh/identity.pub root@B:

e infine, da B, aggiungere la chiave pubblica di A al keyring:

        root@B # cat /root/identity.pub >> /root/.ssh/authorized_keys

Verificare da A se ora si puo' loggare in B direttamente e senza richiesta di password:

        root@A # ssh root@B
        Last login: Wed Jan 16 17:05:07

        root@B #

Perfetto! Adesso root@A puo' stabilire in modo automatico una connessione ssh con root@B. Il comando ssh supporta l'opzione -t che crea un canale permanente (pseudo-tty) che funziona similmente a una interfaccia seriale: esattamente quello che ci vuole per instradare il protocollo PPP.

Installazione di pppd

Il kernel di Linux ha il supporto integrato per il protocollo IP, TCP e PPP (a meno che queste funzionalita' non siano state espressamente rimosse ricompilando il kernel). Sulla distribuzione Red Hat 6.2 e 7.x il pacchetto da installare e' ppp, che contiene il comando pppd.

Il protocollo PPP e' stato inventato per trasportare vari protocolli a pacchetti, incluso IP, su di una connessione tipicamente seriale (cavo, modem, ecc.) del tipo punto-a-punto (cioe' ai due estremi della connessione ci sono due macchine con assegnato indirizzo IP che estraggono e smistano i pacchetti).

In Linux il comando pppd permette di controllare il supporto al protocollo PPP integrato nel kernel: avviamento, arresto, configurazione. Le prime versioni di pppd supportavano solo interfacce seriali /dev/ttySx. Le versioni piu' recenti (almeno dalla 2.4.0 in poi, che io sappia) supportano l'opzione pty: con questa opzione il comando che attiva il collegamento (che per noi sara' ssh) lascia aperto il suo stdin e stdout creando un canale pty virtuale dove si attacca il PPP.

In definitiva, il canale VPN si attiva da A con un comando di questo tipo (ometto le opzioni meno interessanti):

        root@A # pppd pty \
        "ssh -t -l root 22.22.22.22 'pppd passive 192.168.1.254:192.168.0.254'" \
        192.168.0.254:192.168.1.254 local

dove 22.22.22.22 e' l'indirizzo pubblico di B (v. figura 1).

  1. Il comando pppd scopre l'opzione pty: a seguire il comando che attiva il terminale pseudo-pty da usare.
  2. Il comando che attiva il terminale pseudo-pty e' ssh. Le opzioni di ssh dicono di collegarsi alla macchina B come utente root: la macchina B accetta il collegamento autenticando l'utente grazie alla chiave pubblica preventivamente inserita.
  3. Inoltre, il comando ssh avvia sul computer B il comando pppd che attiva il canale PPP in modo passivo, lasciandolo cioe' in attesa di richiesta di collegamento; l'interfaccia di rete creata sara' ppp0 sull'indirizzo 192.168.1.254, mentre all'altro estremo l'indirizzo sara' 192.168.0.254.
  4. Completato il comando di creazione dello pseudo-pty, il comando pppd avviato su A inizia la conversazione col protocollo PPP e crea una interfaccia di rete ppp0 con indirizzo 192.168.0.254, mentre all'altro estremo l'indirizzo sara' 192.168.1.254.
  5. A questo punto il collegamento e' stabilito. Non resta che sistemare opportunamente le tabelle di routing e il masquerading sui due computer. Il routing su A dovra' prendere i pacchetti destinati alla rete 192.168.1.x e instradarli verso l'interfaccia ppp0. Il routing sul computer B dovra' prendere i pacchetti destinati alla rete 192.168.0.x (LAN-A) e instradarli verso l'interfaccia ppp0.
               :                                 :
               |                                 |                   
               |                                 |                   
               | eth0 (192.168.0.1)              | eth0 (192.168.1.1)
        +------o------+                   +------o------+            
        |             |                   |             |            
        |      A      |                   |      B      |            
        |             |                   |             |            
        +------o------+                   +------o------+            
               : ppp0 (192.168.0.254)            : ppp0 (192.168.1.254)
               :                                 :                   
               :                                 :                   
    ********************************************************         
    *          .       Tunnel SSH per PPP        .         *         
    *          . . . . . . . . . . . . . . . . . .         *         
    *                                                      *         
    *                   I n t e r n e t                    *         
    *                                                      *         
    ********************************************************         
Figura 3. Il canale (o tunnel) VPN.

La figura 3 illustra la situazione per quanto riguarda il canale (o tunnel) VPN tra A e B: le due nuove interfacce virtuali ppp0 e i rispettivi numeri di nodo IP ci permetteranno di instradare correttamente i pacchetti da e verso il tunnel: tutti i pacchetti generati nella LAN A e destinati ad indirizzi della forma 192.168.1.x verranno inviati all'interfaccia ppp0 di A, e da questa al tunnel; similmente tutti i pacchetti generati nella LAN B e destinati ad indirizzi della forma 192.168.0.x verranno inviati all'interfaccia ppp0 di B, e da questa al tunnel.

Il programma

Ecco il programma vpn che attiva la VPN descritta.
#!/bin/bash
#
# Attiva/Disattiva VPN
#
#

# IP addr. privato interfaccia PPP su A:
gw1="192.168.0.254"

# IP addr. privato interfaccia PPP su B:
gw2="192.168.1.254"

# IP addr. pubblico host remoto B:
gw2_name=22.22.22.22

user=root
pppd=/usr/sbin/pppd
options="lock nopersist debug asyncmap 0 noauth ipcp-restart 5 lcp-restart 5"
IFCONFIG="/sin/ifconfig"


function which_ppp()
#
# Dato l'IP addr. del gateway, ritorna il device pppx associato.
# Se non lo trova ritorna la stringa vuota.
#
{
	for d in $($IFCONFIG | grep ^ppp | while read p x; do echo "$p"; done); do
		if $IFCONFIG $d | grep -q "inet addr:$1 " 2>/dev/null; then
			echo $d
		fi
	done
	echo ""
}



case $1 in

start)
	echo "Start della VPN..."
	$pppd nopersist $options \
	connect-delay 60000 \
	pty \
	"ssh -o 'KeepAlive no' -e none -c blowfish -t -l $user $gw2_name \
		'$pppd $options passive $gw2:$gw1'" $gw1:$gw2 local

	aspetta=10
	echo "(attendere $aspetta secondi...)"
	sleep $aspetta

	dev=$( which_ppp $gw1 )
	if [ -z "$dev" ]; then
		echo "Nessun device PPP associato al gateway $gw1: collegamento fallito"
		exit 1
	fi
	/sbin/route add -net 192.168.1.0/24 gw $gw1 $dev
	ssh -o 'Batchmode yes' -l $user $gw2_name /root/vpn-route
	err=$?
	if [ $err -ne 0 ]; then
		echo "Fallito avvio di /root/vpn-route su gw remoto $gw2_name (err=$err)."
	fi
	/sbin/ipfwadm -F -a accept -m -S 192.168.1.0/24 -D 192.168.0.0/24
	/sbin/ipfwadm -F -a accept -m -S 192.168.0.0/24 -D 192.168.1.0/24
	echo "VPN avviata."
	;;

stop)
	echo "Stop della VPN..."
	ssh -o 'Batchmode yes' -l $user $gw2_name 'killall pppd'
	err=$?
	if [ $err -ne 0 ]; then
		echo "Fallito killall pppd su gw remoto $gw2_name (err=$err)."
	fi
	echo "(attendere 10 secondi...)"
	sleep 10
	p=$( ps axw | grep pppd | grep "ssh" )
	if [ -z "$p" ]; then
		echo "Nessun processo PPPD verso $gw2"
	else
		echo "TROVATO: $p"
		pid=$( echo "$p" | ( read n x; echo $n ) )
		echo "kill verso PID=$pid..."
		kill $pid
	fi
	;;

restart) $0 stop ; $0 start ;;

status)
	dev=$( which_ppp $gw1 )
	if [ -z "$dev" ]; then
		echo "Nessun device PPP associato al gateway $gw1"
	else
		echo "VPN attiva su questo device:"
		$IFCONFIG $dev
	fi
	;;

*) echo "Usage: vpn start|stop|restart|status" ;;

esac

# FINE!

Listato 1. Il programma vpn.

Per impostare correttamente il computer B, il programma vpn invoca il programma vpn-route predisposto sul computer B che si occupa della configurazione della tabella di routing e del masquerading secondo necessità:


#!/bin/bash

# IP addr. privato interfaccia PPP su A:
gw1="192.168.0.254"

# IP addr. privato interfaccia PPP su B:
gw2="192.168.1.254"

# IP addr. pubblico host remoto B:
gw2_name=22.22.22.22
user=root
pppd=/usr/sbin/pppd
options=/root/pppd-options
IFCONFIG="/sbin/ifconfig"


function which_ppp()
#
# Dato l'IP addr. del gateway, ritorna il device pppx associato.
# Se non lo trova ritorna la stringa vuota.
#
{
	for d in $($IFCONFIG | grep ^ppp | while read p x; do echo "$p"; done); do
		if $IFCONFIG $d | grep -q "inet addr:$1 " 2>/dev/null; then
			echo $d
		fi
	done
	echo ""
}


ppp=$( which_ppp $gw2 )

if [ -z "$ppp" ]; then
	echo "$0: nessun ppp verso $gw2: non posso configurare il route."
	exit 1
fi

/sbin/route add -net 192.168.0.0/24 gw 192.168.1.254 $ppp
/sbin/ipfwadm -F -a accept -m -S 192.168.0.0/24 -D 192.168.1.0/24
/sbin/ipfwadm -F -a accept -m -S 192.168.1.0/24 -D 192.168.0.0/24

Listato 2. Il programma vpn-route.

La funzione which_ppp() e' comune ai due programmi, e permette di risalire al nome della interfaccia ppp assegnata al gateway indicato come parametro.

Il comando vpn accetta come parametri i consueti start|stop|restart|status con ovvio significato.

Debugging

Il computer A e' il cuore del sistema VPN, ed e' da qui che potremo sorvegliare il buon funzionamento del tutto.

Verifica dell'ssh. Dal computer A, loggati come root, proviamo il collegamento ssh con B:

      root@A # ssh root@22.22.22.22

che dovrebbe portare immediatamente al prompt dei comandi di root sul computer B. Se non avviene, controllare che su B stia girando il demone sshd. Se viene richiesta la password, probabilmente non abbiamo inserito la chiave pubblica di root copiandola da A a B. Eventualmente usare il flag -v di ssh per vedere il procedere del collegamento.

Verifica del programma vpn. Una volta configurato lo script vpn su A e lo script vpn-route su B, provare ad avviare la VPN da A con il comando

      root@A # vpn start

Se compare "VPN avviata." siamo a cavallo. Altrimenti, controllare i parametri configurati negli script. Usare i comandi ifconfig, route -n e traceroute -n per diagnosticare eventuali problemi. L'opzione -n evita il tentativo di risolvere in nomi degli indirizzi IP: poiche' probabilmente non avremo ancora settato il DNS, senza questa opzione questi comandi inpiegherebbero molto tempo prima di mostrare il loro output.

Prova del ping. Provare a pingare le varie macchine delle varie reti locali usando ping -n. Magari da un'altra finestra terminale, verificare con tcpdump -n -i ppp1 cosa passa nel canale VPN di A, e similmente di B. Rifare le stesse prove pingando fra di loro le macchine delle reti locali.

DNS

La corretta configurazione di un DNS e' molto utile per usare i nomi delle macchine al posto dei numeri. Questo riduce anche molto i tempi di risposta di molti comandi che tentano comunque di risolvere gli indirizzi in nomi. La ricetta, in sostanza e' questa (si presume comunque una certa dimestichezza nella configurazione di BIND):

A questo punto diventa facile anche il collegamento via SAMBA da Linux e via Explorer da Windows:

Prestazioni

Per elevare le prestazioni sul trasferimento di file non compressi, forse puo' convenire aggiungere il flag -C al comando ssh: verificare se cio' porta ad effettivi miglioramenti o magari peggiora il trasferimento di dati gia' compressi.

Affidabilita'

Il controllo dello stato della VPN si puo' fare con il comando vpn status, ma e' necessario verificare prima che sia raggiungibile l'host B:

        if ping <B> -q -c 1 -w 5 > /dev/null 2>&1  \
        && vpn status 2>/dev/null | grep ^Nessun ; then
                echo "VPN DOWN: INTERVENIRE!"
        fi

Per quanto riguarda il ripristino, il semplice lancio del programma vpn restart non sembra sufficiente per via delle condizioni non ben definite in cui si verrebbero a trovare le macchine A e B, anche se la VPN verrebbe comunque ripristinata. Inoltre, una sequenza troppo ravvicinata di riavvii della VPN senza successo, potrebbe portare all'esaurimento delle label di interfaccia virtuale ppp.

Il programma check-vpn proposto di seguito e' una prima implementazione di questi concetti:


#!/bin/bash
#
# Controlla stato VPN
#
#

vpn=/root/bin/vpn

# IP addr. privato interfaccia PPP su A:
gw1="192.168.0.254"

# IP addr. privato interfaccia PPP su B:
gw2="192.168.1.254"

# IP addr. pubblico host remoto B:
gw2_name=22.22.22.22

log=/root/vpn.log

data=$( date +%Y-%m-%d,\ %H:%M )


{
if ping $gw2_name -q -c 1 -w 5 > /dev/null 2>&1 ; then
	if $vpn status | grep -q ^Nessun 2>/dev/null; then
		echo "$data: $0: VPN DOWN"
		echo "$data: $0: Tento il riavvio della VPN..."
		$vpn restart
	else
		if ! tail -1 $log | grep -q "VPN up$"; then
			echo "$data: $0: VPN up"
		fi
	fi
else
	echo "$data: $0: VPN DOWN - manca collegamento con $gw2_name"
fi

} >> $log 2>&1

#FINE!

Listato 3. Il programma check-vpn.

Il programma check-vpn lo si puo' mettere nella crontab di root@A ad esecuzione periodica (ad es. ogni 10 minuti):

         */10  *  *  *  *   /path/del/progr/check-vpn

In questo modo, ogni 10 minuti il programma viene avviato e controlla lo stato della VPN e, in caso di problemi, provvede al suo riavvio.

Versioni del software utilizzato:

Riferimenti

Argomenti correlati


Umberto Salsi
Commenti
Contatto
Mappa
Home/
Still no comments to this page. Use the Comments link above to add your contribute.