Wie man ein VPN mit ppp über stunnel implementiert:

( translations to other languages are welcome )

Dieses HOWTO erklärt, wie man ein VPN mit pppd und stunnel aufbaut. Dabei CHAP-Authentifizierung verwendet, die Routen anpasst und für weitere Tunnel gewapnet ist. Dabei wird folgender Beispielfall implementiert:

  Client Server
BS linux linux
WeltIP/name client.nu server.nu
Subnetz 10.8.0.0/24 10.3.0.0/24
TunnelIP 10.99.99.2 10.99.99.1
CHAP-name florenz
CHAP-secret

F1renz3

1. Zunächst benötigt man natürlich die entsprechenden Pakete

2. Zertifikate auf jeder Seite erstellen

Wenn man die Pakete installiert hat, und ein Aufruf openssl bzw. stunnel einige Fehlermeldungen regnen, kann es losgehen. Als erstes benötigt man für SSL pro Seite ein X.509 Zertifikat. Darin stehen der öffentliche und der private Schlüssel. Zusätzlich sind dort noch einige Daten über den Eigentümer und den Unterzeichner - der CA - drin. Das Zertifikat müsste also von einer CA unterschrieben werden. Da wir nur privat plaudern wollen, und openssl glücklicherweise sowas bietet, unterschreiben wir unsere Zertifikate einfach selbst. Bei einer größeren Installation sollte man erwägen eine eigene CA einrichten. Diese Zertifikate dienen wohlgemerkt lediglich der verschlüsselung des Kanals. Nicht zur Authentifizierung der Partner!

Selbst unterschrieben, mit Passwort:

> openssl req -new -x509 -keyout pop.pem -out certificate.pem -days 7000

Selbst unterscrieben, ohne Passwort:

> openssl req -new -x509 -nodes -keyout pop.pem -out certificate.pem -days 7000

3. Eine stunnel.pem Erzeugen

Da stunnel ein spezielles Dateiformat haben: möchte, müssen wir die beiden erzeugten Dateien zu einer neuen zusammenfügen. Die einzelnen sollten dannach gelöscht werden:

Format:

-----BEGIN RSA PRIVATE KEY-----
[encoded key]
-----END RSA PRIVATE KEY-----
[empty line]
-----BEGIN CERTIFICATE-----
[encoded certificate]
-----END CERTIFICATE-----
[empty line]

Mit folgenden Kommandos beiden Gateways:

> echo | cat certificate.pem - pop.pem > /etc/stunnel/stunnel.pem
> echo >> /etc/stunnel/stunnel.pem

Fertig ist das Zertifikat. Nach genau diesem Namen sucht stunnel, wenn es ohne -p aufgerufen wird. Also genau das was wir wollen

4. Authentifizierung einrichten

Natürlich wollen wir nicht jedem Daherglaufenen erlauben zu unserem Router eine Verbindung aufzubauen. Daher benutzen wir die Autentifizierung (CHAP) vom pppd. Dazu müssen die /etc/ppp/chap-secrets - Dateien auf beiden Seiten angepasst werden. In unserem Beispielfall ist also die chap-secrets beider Seiten um folgende Zeile zu ergänzen:

'florenz' * 'F1renz3' *

Das bedeutet soviel wie: Für benutzer florenz benutze 'F1renz3' als Secret, IP-Adressen sind egal.

5. Auf einer Seite stunnel und pppd im Servermodus starten:

Test:

>stunnel -d 5555 -f -v1 -D7 -L /usr/sbin/pppd -- pppd local nodetach auth require-chap

Dies startet den pppd innerhalb eines stunnels (im Vordergrund). Dabei ist der pppd angewiesen nur per CHAP-Authentifizierung zu arbeiten und auf andere pppds zu warten. Man könnte auch dem Server-pppd die IP-Adressen mitgeben (PPP ist ja ein symmetrisches Protokoll), doch damit handelt man sich einige Schwierigkeiten ein: Jeder Client, der sich verbindet, bekommt die gleichen Adressen. Meherer Tunnel wären nur möglich, wenn man stunnel noch auf anderen Ports startet. Folglich gibt der client die IPs an.

6. Auf der anderen Seite stunnel und pppd im Clientmodus starten:

>stunnel -c -r server.nu:5555 -D7 -L /usr/sbin/pppd -- pppd local noauth user florenz lcp-echo-interval 50 lcp-echo-failure 3 10.99.99.2:10.99.99.1

Dieser Aufruf bewirkt, daß sich die beiden pppd im stunnel treffen. Dabei soll sich unser pppd dem anderen gegenüber als florenz (mit dem Secret aus chap-secrets) authentifizieren aber selber keine Autentifizierung des anderen verlangen. Das Paar von IP-Adressen sind für die beiden Peers. Die Tatsache, daß der Client die Adressen bestimmt hat den entscheidenden Vorteil, daß wir zum Server mehrere Clients mit jeweils unterschiedlichen Adressen hinverbinden können. Dabei sind wir allerdings nicht davor gefeit, daß 2 Tunnel die gleichen IPs haben. Um solche unsinnigen Tunnel gleich wieder abzubauen sind in der ip-up des Servers entsprechende vorkehrungen getroffen. Der lcp*-kram sagt dem pppd, daß er regelmäßig prüfen soll, ob die Verbindung noch steht. Prektischer Nebeneffekt: Eine ADSL-Leitung "schläft" nicht ein.

Die obige Zeile (mit & am Ende) kann man zum Beispiel in seine ip-up.local aufnehmen. Doch Vorsicht: Man muß hier unbedingt die Netzschnittstellen unterscheiden, sonst baut der pppd einen Tunnel nach dem anderen! In der ip-up.local am Ende ist die Position mit "## STUNNEL" gekennzeichnet. Natürlich nur beim Client sinnvoll. Oder man macht es wie in den Nächsten Punkten beschreiben.

7. Server und Client automatisiert starten

Der Server sollte nach dem Systemstart automatisch verfügbar sein und wenn er stirbt, sollte er nach kurzer Zeit wieder gestartet werden. Zum Starten bei Kontaktaufnahme würde sich inetd anbieten, doch ich habe mich für init entschieden. Der initd beherrscht es abgestürzte Programme erneut zu starten. Zu diesem Zweck muß man die Datei /etc/inittab um folgenden Eintrag ergänzen. Am besten irgndwo im hinteren Teil.

# hier startet der stunnel Server Durch diesen inittab-Eintrag
# wird er erneut gestartet, sollte er sterben.
55:35:respawn:/usr/sbin/stunnel -d 5555 -f -v 1 -D0 -L /usr/sbin/pppd -- pppd local nodetach auth require-chap

Der Client sollte sich nach einem Verbindungsabbruch, wie ihn ja viele alle 24 Std. haben, seine Verbindung wieder aufbauen. Sollte er den Server dabei nicht finden, sollte er so lange rumprobieren bis es geht. Allerdings sollte er dabei keine Bandbreite verschwenden. Dieser Aufruf in einer Datei wie der /etc/init.d/rc.local bzw. etc/init.d/boot.local läßt den pppd ewig stunnel starten und an sein pty verbinden, bis er es geschafft hat. Dabei wartet er zwischen den Versuchen 50 Sek. Eine Erkennung für tote Verbinungen gibt es auch: Mit den lcp-Optionen prüft der pppd alle 60 sek ob die Verbinung noch steht. Meldet sich nach dem dritten Mal immer noch keiner, versucht der pppd eine neue Verbindung aufzubauen. Dieses Konstrukt eignet sich übrigens auch hervorragend, um ein "einschlafen" der DSL-Leitung zu verhindern.

pppd local noauth user florenz lcp-echo-interval 60 lcp-echo-failure 3 10.99.99.2:10.99.99.1 maxfail 0 pty 'stunnel -c -r server.nu:5555 -D0; sleep 50'

8. Die Routingtabellen um die Netze erweitern + doppel-IP vermeiden

Dieser Teil ist aus Sicht des Servers beschrieben. Die Clientseite unterscheidet sich nur in den IP-Adressen und hat andere erreichbare Subnetze.

Nun wollen wir Subnetze koppeln, und nicht nur einen schlichten Tunnel haben. Wer das will, kann hier aufhören. Das koppeln der Subnetze erfolgt durch hinzufügen entsprechender Einträge in der Routingtabelle. Also daß das Netz 10.8.0.0/24 über den Router 10.99.99.2 zu erreichen ist:

Test:

>route add -net 10.8.0.0/24 gw 10.99.99.2

Schon gehen pings zu Hosts im Gegennetz. Um das zu Automatisieren, muß man seine /etc/ppp/ip-up.local (wird idR. von ip-up aufgerufen) erweitern. Ich habe dazu eine Erweiterung geschreiben. Meine Erweiterung geht davon aus, daß ppp0 für den Zugang zum Internet verwendet wird, und desshalb gesondert behandelt wird. ppp[1-n] werden jedoch als Tunnel aufgefasst.

In dem ppp[1-n]-Bereich wird die P-t-p-Adresse (REMOTEIP) ausgewertet, und wenn ein entspr. Subnetz gefunden wurde, werden in das Array REMNET[] alle darüber erreichbaren Subnetze eingetragen. Das wird dannach verwendet, um entsprechend viele Routen zu setzen.

Da der pppd bei Verbindungsende von sich aus alle Routen mit dem entspr device löscht, können wir uns ein löschen der Routen sparen.

Um jetzt noch doppelte IP-adressen zu vermeiden, dient der Abschitt vor dem Routenkram. Er überprüft, ob die REMOTEIP in der Ausgabe von ifconfig zu finden ist. Wenn dem so ist, wird der aktuelle pppd gekillt. Die entsprechenden PIDs findet man in /var/run/ppp*. Dieser Teilabschnitt ist für den Server gedacht, verhält sich auf dem Client aber neutral.

#!/bin/bash

LOGDEVICE=$6
REALDEVICE=$1
INTERFACE=$1
DEVICE=$2
SPEED=$3
LOCALIP=$4
REMOTEIP=$5

case "$INTERFACE" in
ppp0)
# IP -Adresse Updeiten
######################
logger "Aktualisiere die dyndns auf $LOCALIP"
cd /etc/ppp
/etc/ppp/ipcheck.py -i ppp0 xxxx xxxx xxxxx
## STUNNEL
;;

ppp?)
# IP -Routen zu den Subnetzen einrichten
######################

logger "$INTERFACE wird untersucht"

# Testen, ob der tunnel schon da
# ist und pppd evtl. killen
##############################

ERG=`/sbin/ifconfig |grep -c $LOCALIP`
if [ "$ERG" = "2" ]; then
kill `cat /var/run/$INTERFACE.pid`
logger "Ein Tunnel mit dieser IP existiert bereits - ende."
fi

# Subnetze zu Routern bestimmen
############################

case "$REMOTEIP" in
10.99.99.*)
# erreichbare netze
REMNET[0]="10.8.0.0/24";
;;

10.99.98.*)
# erreichbare netze
REMNET[0]="168.169.1.0/24";
REMNET[1]="10.4.0.0/24";
REMNET[2]="10.7.0.0/24";
;;

*)
REMOTENET="nix";
;;

esac

# Routen eintragen
###############

if [ "$REMOTENET" != "nix" ]; then
for REMOTENET in ${REMNET[@]}; do
/sbin/route add -net $REMOTENET gw $REMOTEIP
logger "Trage $REMOTENET mit Gateway $REMOTEIP ein"
done
fi

;;

esac

9. Hinweise:

!!!HALT!!!

Bevor du mir eine Frage stellst, solltest du die doku zu

  • openssl = man openssl
  • stunnel = man stunnel
  • pppd = man pppd

gelesen haben. Aber am besten stellst du keine Frage, denn für triviale Fragen bin ich zu schade und wenns komplizierter wird, hakts bei mir vermutlich auch aus.

© Robert Köpferl