Hetzner

Aus Freifunk Franken

Vorab: Dies ist keine korrekte Lösung. Es ist lediglich ein sehr unschöner Hack. Eine Lösung kann für solche Situationen nur in Zusammenarbeit mit dem Hoster erfolgen. Verweigert sich dieser, bleibt nur der Hack.

Hetzner monitored den Traffic, den die Gateways verursachen. Sollten Verbindungsversuche zu nicht gerouteten IPs dabei sein, generiert Hetzner Abuse und schickt es (Achtung!) nicht an die hinterlegte Abuse-Adresse. Was man dagegen tun kann ist hier beschrieben.

Man benötigt zunächst alle routbaren Adresse/Prefixe. Diese kann man sich z.B. bei routeviews.org runterladen. Diese Adressen können dann in die Routing-Tabelle eingepflegt werden. Am besten geht das, wenn das Gateway in einer VM ist, dann kann der Host die Filterung übernehmen.

In diesem Beispiel kommunizieren die Gateways, welche in VMs laufen, über das Interface "f3n". Normalerweise wird jeder Traffic von diesem Interface einfach weiter zum default Gateway geschickt. (Bei professionellen Hostern wird dann spätestens beim Border-Gateway jeglicher auf null geroutet). Der Mechanismus ist nun, per "ip rules" alle Verbindungen, die aus f3n (also von den VMs) kommen in eine eigene Routing Tabelle (hetzner1) zu sperren. In dieser werden alle routbaren Prefixe mit dem default gateway von Hetzner als next-hop eingetragen. Als default gateway in dieser Tabelle wird 127.0.0.1 über lo eingetragen. Alle x Stunden sollte die Tabelle aufgefrischt werden. Dazu wird eine neue (hetzner2) angelegt und anschließend die Regel umgebogen.

Hetzner-Script auf dem Host

Dies ist die einfachere und "bessere" Implementierung, denn das Script läuft nur einmalig auf dem Hostrechner, während die einzelnen Freifunk-VMs davon entkoppelt sind.

/etc/iproute2/rt_tables:

..
11 hetzner1
12 hetzner2

hetzner.sh:

cd /tmp

current=$(ip ru | grep hetzner | sed 's/.*hetzner//g' | tr -d '[:space:]')

if [ "$current" = "1" ]; then
	next="2"
else
	next="1"
fi

echo "Downloading routing snapshot"
wget -q http://archive.routeviews.org/oix-route-views/oix-full-snapshot-latest.dat.bz2 -O oix-full-snapshot-latest.dat.bz2
echo "Unpacking"
bzip2 -d -f oix-full-snapshot-latest.dat.bz2

echo "Parsing"
allIP=oix-full-snapshot-latest.uniq
tail -n+6 oix-full-snapshot-latest.dat | cut -d' ' -f3 | uniq | grep -v "0.0.0.0/0" > $allIP

echo "Aggregating the prefixes"
filterIP=oix-full-snapshot-latest.filtered
unlink $filterIP
for i in $(seq 0 255); do
       (grep "^$i\." $allIP | aggregate >> $filterIP) 2>&1 | grep -v "aggregate: maximum prefix length permitted will be 32" | grep -v "aggregate: no prefixes supplied"
done

echo "Cleaning up hetzner$next"
ip ro flush ta hetzner$next

echo "Inserting new valid routes to hetzner$next"
for prefix in $(cat $filterIP | grep -v "^#"); do
	ip ro add $prefix via 5.9.89.33 table hetzner$next
done
echo "Inserting null route to hetzner$next"
ip ro add default via 127.0.0.1 dev lo table hetzner$next

echo "Adding new rule for hetzner$next"
ip ru add from all iif f3n lookup hetzner$next

if [ -n "$current" ]; then
	echo "Removing old rule for hetzner$current"
	ip ru del from all iif f3n lookup hetzner$current
	echo "Cleaning up old hetzner$current"
	ip ro flush ta hetzner$current
fi


Hetzner-Script auf der VM

Manche haben nur eine VM gemietet und keinen Zugang zum Host. Mit etwas Bastelei ist auch dieser Betriebsfall realisierber.

Vorbereitung

Wir benötigen außer "fff" noch drei zusätzliche Routing-Tabellen.

vi /etc/iproute2/rt_tables
...
20 fff
21 hetzner1
22 hetzner2
30 fffdefault
...

Babel erzeugt für interne Zwecke ein paar Tabellen direkt auf 10 folgend, deshalb bei der Nummerierung etwas Platz lassen. Die Tabellen "hetzner1" und "hetzner2" werden wechselweise vom Hetzner-Script befüllt. Die Tabelle "fffdefault" bekommt eine Dummy-Defaultroute. Hintergrund: In der fff-Tabelle darf keine Defaut-Route sein. Dann wird nämlich in der Hetzner-Tabelle weitergesucht, und dort schreibt das Script eine Defaultroute auf Lokalhost und macht damit den Internet-Exit für alle IPv4 Adressen dicht, die nicht in der Hetzner-Tabelle aufgelistet sind.

Beim Systemstart vor Aufruf des Hetzner-Scripts wird der Internet-Exit dicht gemacht. Außerdem wird die oben erwähnte Dummy-Defaultroute gesetzt. Diese Dummy-Route wäre ein gültiger Internet-Exit für alles, deshalb darf die Tabelle "fffdefault" unter keinen Umständen angesprungen werden. Diese Route wird nur angelegt, damit Babel eine gültige Defaultroute findet. Sonst würde es das Adressband 0.0.0.0/0 nicht anbieten. Im Beispiel ist "eth0" der Internet-Exit.

ip route replace default via 127.0.0.1 dev lo table fff
ip route replace default dev eth0 proto static table fffdefault

Alle Interfaces, die auf die fff-Tabelle verweisen, sollen zuerst in der fff-Tabelle nachschauen, und danach in der gerade aktiven Hetzner-Tabelle "hetzner1" bzw. "hetzner2". Dafür bekommt jedes Interface eine Priorität (priority) in fff zugewiesen und später im Hetzner-Script eine niedrigere Priorität (höhere Nummer) in der Hetzner-Tabelle. Hinweis: Die Prioritäten müssen höher (kleinere Nummer) als 100 sein, weil Babel die Priorität 100 für interne Zwecke benötigt.

vi /etc/network/interfaces
iface bat0 inet manual
  ...
  post-up ip rule add iif $IFACE table fff priority 41
  ...

iface bat1 inet manual
  ...
  post-up ip rule add iif $IFACE table fff priority 42
  ...

iface fff-ab-dc-01 inet static
  ...
  post-up ip rule add iif $IFACE table fff priority 51
  ...

Babel soll in der Freifunk-Tabelle (fff) und in der Tabelle mit der Dummy-Defaultroute (fffdefault) nachschauen, nicht jedoch in der Hetzner-Tabelle. Es wird also "import-table 30" ergänzt, wenn die Nummerierung wie oben gewählt wurde.

vi /etc/babeld.conf
...
export-table 20
import-table 20
import-table 30
...

Änderungen im Hetzner-Script

Bei mehreren Interfaces wird in die Variable "current" mehrfach die Nummer des aktuell aktiven Hetzner-Scripts geschrieben. Für die spätere Logik muss das auf ein Zeichen gekürzt werden.

...
current=$(ip rule | grep hetzner | sed 's/.*hetzner//g' | tr -d '[:space:]')
current=$(expr substr $current 1 1)
...

Die IP im folgenden Scriptteil muss auf das Gateway des Server-Vermieters zeigen, nicht auf die eigene IP. Je nach Server-Rack sind die Gateways unterschiedlich, hier im Beispiel also 8.8.1.1.

...
echo "Inserting new valid routes to hetzner$next"
for prefix in $(cat $filterIP | grep -v "^#"); do
  ip ro add $prefix via 8.8.1.1 table hetzner$next
done
...

Alle Interfaces, denen vorhin Prioritäten für die fff-Tabelle zugeteilt wurden, müssen nun auch (niedrigere) Prioritäten für die Hetzner-Tabelle bekommen (höhere Nummern).

...
echo "Adding new rule for hetzner$next"
ip ru add from all iif bat0 lookup hetzner$next priority 60
ip ru add from all iif bat1 lookup hetzner$next priority 61
ip ru add from all iif fff-ab-dc-01 lookup hetzner$next priority 62

if [ -n "$current" ]; then
  echo "Removing old rule for hetzner$current"
  ip ru del from all iif bat0 lookup hetzner$current
  ip ru del from all iif bat1 lookup hetzner$current
  ip ru del from all iif fff-ab-dc-01 lookup hetzner$current
...

Am Ende des Scripts wird zum Abschluss die Defaultroute aus der fff-Tabelle gelöscht. Achtung, Babel legt eine Defaultroute in fff an, wenn nicht (wie hier) beim Systemstart schon eine angelegt wurde.

...
ip route del default table fff

Script zyklisch aufrufen

Das Script muss nun beim Systemstart und zyklisch alle X Stunden aufgerufen werden, möglichst nicht zu Zeiten mit Spitzenlast. Während der ca. 5 Minuten Abarbeitungszeit geht die Prozessorlast auf 100%, deshalb empfiehlt sich mit "nice -n 19" das Script mit niedrigster Priorität zu starten.

vi /etc/rc.local
# call Hetzner routing workaround with lowest priority
(nice -n 19 sh /root/hetzner.sh) &
crontab -e
0 4 * * * nice -n 19 sh /root/hetzner.sh