L2TP/Tunneldigger Serverdoku-Thread

Als ich den zuletzt getestet habe, hatte der Stabilitätsprobleme, aber kompatibel sollte er sein.

Grüße
Matthias

Ich bekomme ihn aktuell nicht mal gestartet. Die Doku ist veraltet (erwähnt z.B. eine Datei broker/requirements.txt die es nicht mehr gibt), und Bugs kann man auch nicht berichten (Registrierung im Bugtracker erfolgreich, aber Login geht mit diesem Account nicht; ich hatte deswegen Ende letzten Jahres schon mal nachgefragt aber bislang keine Antwort bekommen).

Ich bleibe dann wohl erstmal bei einem Fork.

Hallo, vielleicht hilft euch L2TP und Tunneldigger – Freifunk Franken ein bisschen weiter, so läuft l2tp bei uns

Mit dem alten Setup + @ChrisD s conntrack.py läufts leider auch nicht richtig. Es sieht so aus als ob, sobald die 2. Verbindung aufgebaut wird, die erste abbricht.

Das Verbindungsproblem konnte ich noch nicht beheben… aber immerhin habe ich die Ursache der SEGFAULTs gefunden. Das Problem besteht darin, dass tunneldigger hier die Python ctypes Bibliothek verwendet. Und wenn man nicht aufpasst, dann rundet die Pointer auf 32bit runter. Auf Debian Jessie geht das offenbar weil höhere Adressen nicht verwendet werden, auf auf Stretch werden sie verwendet und deshalb fliegt alles in die Luft.

Der Fix sieht wie folgt aus:

3 „Gefällt mir“

Ich habe jetzt immerhin herausgefunden, warum er nur einen Tunnel anlegen kann. Aber das wirft dann die Frage auf, warum das jemals geklappt hat…

Wie sich rausstellt, kann man mit tcpdump netfilter-Pakete abfangen. Damit konnte ich dann sehen, dass tatsächlich eine der Netfilter-Queries vom Tunneldigger schief geht. Eigentlich hat Tunneldigger Code, um solche Fehler zu erkennen und zu melden, aber der hatte einen Bug. Folgender Commit behebt den Fehler in der Fehlererkennung:

Damit sehe ich jetzt immerhin, dass das Erstellen einer L2TP-Session im schon aufgebauten L2TP-Tunnel schief geht. Fehler: EEXIST. Offenbar ist der Kernel der Meinung, die session ID würde schon verwendet. Nun, die session ID ist immer 1 (das ist im Client sogar hard-coded), aber das sind ja sessions in verschiedenen Tunneln. Ich habe lange gesucht und durch den Kernel-Quelltext geschaut, um herauszufinden, ob session IDs jetzt global oder pro Tunnel eindeutig sein müssen – und schließlich folgenden Kommentar im Kernel gefunden:

	/* In L2TPv3, session_ids are unique over all tunnels and we
	 * sometimes need to look them up before we know the
	 * tunnel.
	 */

Der Kommentar existiert in v4.12 nicht mehr, weil die ganze Funktion entfernt wurde – aber das muss ja nicht heißen, dass er nicht mehr gültig ist. Es gibt immer noch Code, der eine globale Liste aller Sessions verwaltet.

Wenn das richtig ist, dass die session IDs global eindeutig sein müssen, dann wären auch Änderungen am Client nötig – schließlich muss jedes Ende des L2TP-Tunnels die session ID der anderen Seite kennen.

Was mich gerade am meisten wundert, ist, dass das ganze früher überhaupt geklappt hat. Gab es einen Bug im Kernel, sodass doppelte session IDs akzeptiert wurden? Oder was ist hier los? Und wenn das bei euch alles klappt – welche Kernel-Version verwendet ihr? Ich habe 4.9 und 4.12 probiert und mit beiden das gleiche Problem.

EDIT: Aha! Im alten Kernel gab es zwar die Liste auch schon, aber er hat nicht geprüft, ob da Einträge doppelt drin sind. Und mit Version v4.9.36 landete dann folgender Commit:

https://github.com/linux-stable/linux-stable/commit/d9face6fc62a73059f0fc3a3de4dfe8f53536aa7

und seit dem werden die IDs korrekt geprüft. Und vermutlich habt ihr alle euer Debian 9 installiert bevor v4.9.36 in Debian ankam? Das würde dann alles erklären – aber das Problem nicht lösen.

5 „Gefällt mir“

Vielen, vielen Dank, dass Du den Bug zur Strecke gebracht hast.
Ich habe seit Monaten Bauchschmerzen, dass ich auf alten Kerneln auf den l2tp-VMs festgenagelt bin…

Nun, zur Strecke gebracht ist er noch nicht, nur in die Ecke gedrängt. :wink:

Wenn meine Analyse stimmt, müssen Server und Client so angepasst werden, dass sie sich auf eine eindeutige session ID einigen. Der Server teilt dem Client bereits eine tunnel ID mit (CONTROL_TYPE_TUNNEL), da bietet es sich m.E. an, einfach dieselbe ID auch als session ID zu verwenden. Das Problem dabei ist die Rückwärtskompatibilität: Damit sich alte Clients mit neuen Brokern verbinden können, müssen Broker weiterhin per Default session ID 1 verwenden. Neue Clients müssen dem broker irgendwie (in der CONTROL_TYPE_PREPARE?) signalisieren, dass sie gerne die tunnel ID als session ID verwenden würden, und wenn der Broker dem zustimmt (in der CONTROL_TYPE_TUNNEL?), müssen beide dieselbe ID verwenden.

Ich werde mir mal das Broker-Protokoll genauer anschauen.

3 „Gefällt mir“

Lasse mich mal zusammenfassen:
Das Problem lässt sich mit neueren Kerneln ja dann offenbar nicht lösen, da Clientseitig ja das fehlerhafte Verhalten durch das Hardcoding der Session ID erzwungen wird. Du hast Serverseitig mit neueren Kerneln da auch nicht die Möglichkeit das auszubügeln, da die Session ID ja auf beiden Seiten bekannt und gleich sein muss.
Aus meiner Sicht gibt es daher nur die Möglichkeit erst alle Clients zu updaten bevor man neuere Kernel einsetzen kann.
Das einfachste Wäre wirklich:

  • Client und Broker anpassen
  • Zweiten Broker mit neuem Verhalten parallel auf neuem Port laufen lassen
  • Firmware update mit neuem Client und auf den neuen Port angepasster site.conf ausrollen
  • Erst wenn kritische Masse erreicht den Kernel updaten

Bedingt natürlich, das autoupdate aktiv ist und funktioniert.

Was leider in Problem ist, da überraschend viele Leute ihren Freifunk-Knoten in Dinge wie „AVM Gastlan“ gestellt haben, wo per default nur 80/443/53 frei ist (ich spare mir mal die Details) und wo dann für L2TP die „Ausnahme“ für Port10000 (YMMD) händisch eingefügt wurde.
Bei einer Migration im Emscherland „Domain05“ hat’s dabei etwa 30% der Knoten „in Geschäften“ den Uplink gekostet. Trotz Ankündigung. (Da kann man jetzt in den Heise-Foristen-Modus schalten, nicht nicht ‚Mit Linux wär‘ das nicht passiert’, sondern der andere Standardspruchdort. Hilft aber in der Sache nicht.)

Sprich: Portwechsel: ganz schlecht. Da würde ich mir eher noch IP-Adressen besorgen… Oder in Domains mit mehreren Supernodes nacheinander umzustellen.

Da gibt es leider nach wie vor viele Experten, die das abgeschalten habe, weil sie sich „spezialconfigs“ gebaut haben, von denen sie ahnen(!), dass die nicht updatefest sind… Eine zusätzliche Hürde.
Aber da bin ich dann hart und sage: Wer nach 14 Tagen auch auf Mail (siehe PPA) nicht reagiert oder keine Mailadresse angegeben hat, auf den wird keine Rücksicht genommen.

Könnte man nicht auch - sofern je Domäne zwei Gateways genutzt werden - auf einem Gateway die alte Tunneldigger-Version und auf dem anderen die dann gepatchte Version laufen lassen? Klar wäre dann für die Übergangszeit die Redundanz weg, aber sofern das geht wäre das auf den ersten Blick weniger problembehaftet so wie ich das sehe. Ich weiß nur gerade nicht, ob der alte Tunneldigger auf den Knoten irgendwann automatisch zum anderen Gateway wechselt, wenn das erste Gateway zwar erreichbar ist, aber der Tunnelaufbau fehlschlägt - das wäre die Voraussetzung für diese Lösung.

Das war was ich meinte mit

Sofern man nicht die verbugte Tunneldigger-Version auf den Plasteroutern hat, die sich bei mehr als einem (zwei) Supernodes dabei verhakt: Ja, das geht.

1 „Gefällt mir“

Ah, den einen Satz habe ich irgendwie überlesen, sorry. Dann wäre das doch eine mögliche Lösung für den Umstieg :slightly_smiling_face: So wie @ralfj es beschrieben hat, klingt es jedenfalls schwierig, eine abwärtskomaptible Lösung im Tunneldigger selbst zu schaffen.

Wenn doch dem „alten“ Serverkernel die l2tp-ID egal ist und alle Clients da derzeit alle eine „0“ vorgeben (und das funktioniert dann trotzdem, warum auch immer):

Könnte man nicht erst die Clients updaten, so dass die eben die Tunnel ID auch als L2TP ID setzen? Dann würde man erst nach dem letzten Nachzügler den Server fixen.

Das hab ich mal gemacht: Use Tunnel ID also as Session ID · kaechele/tunneldigger@d2511d4 · GitHub

Das Problem dürfte sein, dass, sobald das Verhalten und damit die Session ID serverseitig geändert würde, der Client diese Änderung abbilden können müsste. In der bisherigen Version kann er es nicht, daher würde keine funktionierende Session zustande kommen.
Ohne Parallelbetrieb auf anderem Port / anderer IP wird es nicht gehen, denke ich.

Daher mein Vorschlag, den Server erstmal nicht zu ändern.
Vermutlich übersehe ich einen Punkt, aber warum sollte ein gefixter Client nicht mit einem „alten“ Tunneldigger-L2TP-Server Konnentieren können?

Weil aktuell auf beiden seiten hardcoded ist, dass die Session ID lokal (auf dem client) wie remote (auf dem broker) 1 ist.
Der gefixte Client würde nun, da das Tunneldigger Protokoll keine Remote Session ID überträgt, die Remote Tunnel ID als Session ID heranziehen, da diese auf dem Remote Server einzigartig ist (uns interessiert hier erstmal nur die Einzigartigkeit der Session ID auf dem Server, da der Client stets nur einen Tunnel und eine Session aufbaut).
Der kaputte Broker würde aber weiterhin stets die 1 als Session ID wählen, da er nicht unterscheiden kann ob ein gefixter oder kaputter Client gerade connecten möchte.
Das würde also dazu führen, dass der gefixte Client gerne mit einer Remote Session ID von 100 (= Remote Tunnel ID) sprechen möchte, der Server seine Session ID aber auf 1 gesetzt hat und daher den Traffic „überhört“.
Dem Client nun eine Möglichkeit per Protokoll zu verpassen die Remote Session ID beim Verbindungsaufbau in Erfahrung zu bringen würde das Protokoll erweitern, was alte Clients und Server ebenfalls inkompatibel machen würde.

Dass im Tunneldigger-Protokoll auch in der Richtung „Client->Server“ die (vom Client) gewählte Tunnel-ID nicht übertragen wird, das war mir nicht bekannt.

Die „im Tunnel“ ankommenden Pakete „per DPI“ anzuschauen ist vermutlich nicht nur das falsche Layer, sondern auch schlicht zu spät, da sich die ID nachträglich nicht mehr ändern lassen wird und man die bestehende Session damit ruinieren würde…

Ein etwas pragmatischerer Ansatz wäre: Ein Verfahren (neu/alt) zum Default deklarieren und „neue“ (expire-timer…) Client-IPs erstmal mit dem Default-Verfahren verbinden.
Und wenn das fehlschlägt (Client testet Funktiontät der Tunnel per cron), dann darauf warten, dass der Client mit dem anderen Verfahren in 2-3 Minuten nochmal anklopft.
Oder aber schlicht randomly das Verfahren am Server auswählen (während der Migrationsphase).
Nach dem Motto „es geht vorbei und in einem Jahr braucht es sowieso niemand mehr, weil niemand mehr Kernel von März 2017 betreiben wird. Eine technisch schöne Lösung ist unnötig.“

Die Tunnel ID wird übertragen; die Session ID nicht. Jede Seite hat eine Tunnel ID und eine Session ID und muss beide IDs der Gegenseite kennen.


Ich denke allerdings nicht, dass der Zug schon völlig abgefahren ist für eine rückwärtskompatible Lösung. Ich werde mal herumexperimentieren. Immerhin ist das hier UDP, man hat also die Paketlänge als zusätzliches Datum zur Verfügung. Sprich, wenn der Server an die CONTROL_TYPE_PREPARE mehr Daten hinten dran tut, sollten alte Clients das einfach ignorieren. Da könnte man dann erstmal eine Versionsnummer, und danach von der Versionsnummer abhängig mehr Daten dazutun. Ähnlich für die Antwort, CONTROL_TYPE_TUNNEL.


Eine andere Sache noch: Meines Erachtens wäre es wünschenswert, einen „kanonischen Upstream“ für tunneldigger (Client+Broker) zu etablieren. Aktuell scheint jeder seinen eigenen Fork zu haben, mit Bugfixes, von denen dann sonst niemand was hat. Das ist doch schade. Zum Beispiel habe ich einen Commit von @ChrisD bei uns übernommen. Idealerweise gäbe es ein Repo, wo alle, wenn sie Bugfixes haben, einen PR hinsenden, und dann können auch andere Communities von diesem Fix profitieren.

Ich dachte eigentlich, das Repo vom FFRL hätte diese Rolle, aber das scheint nicht der Fall zu sein. wlanslovenija ist dafür leider aktuell nicht geeignet, da man keine Bugs berichten und daher wohl auch eher keine PRs versenden kann (Bugtracker-Registrierung scheint defekt, mindestens seit einem Jahr). Gibt es so ein Repo? Und wenn nicht, spricht irgendwas dagegen, das vom FFRL zu nehmen? Dafür müsste halt gewährleistet sein, dass da jemand PRs reviewt und/oder merged.

1 „Gefällt mir“

Das könnte man schon so machen. Dass es quasi unmöglich ist bei wlanslovenija Bugs und Patches beizutragen rechtfertigt dies aus meiner Sicht.
Wir müssten dann nur noch festlegen ob wir weiter auch das Repo von wlanslovenija + unsere Bugfixes tracken wollen oder vollständig divergieren (wie es aktuell der Fall ist).