OpenBSD Version: Erm... keine. o.O
Arch:            Alle
NSFP:            Ja, ne.

Disclaimer: Der Text ist mehr oder minder auto-uebersetzt, mit einigen manuellen aenderungen damit es nicht gaaaaanz so schlimm ist. Ich empfehle den englischsprachigen Artikel, falls du auch Englisch sprichst (oder zumindest liest).

Die Mastodon-Instanz von Digitalcourage e.V. hatte in den letzten paar Monaten ein paar Netzwerkproblemen. User konnten Dateien nur mit ca. 100KB/s von der Instanz downloaden, und alles war irgendwie langsam. Das war natürlich eher ‘nicht so gut’.

Ueber die Zeit zeichnete sich in den Rueckmeldungen von Usern ein klares Muster ab. Konkret schienen hauptsächlich User der Deutschen Telekom betroffen zu sein, während andere in der Lage waren, Dateien mit der erwarteten Geschwindigkeit abzurufen. Die Admins von Digital Courage hatten sogar versucht, die IP-Adresse des Systems zu ändern: Vergeblich. Interessanterweise schnitt ein anderes System direkt neben der Mastodon-Instanz bei digitalcourage.social ziemlich gut ab, selbst für ansonsten betroffene Deutsche Telekom-User. Letztendlich führte dies zu einer ziemlich ausführlichen Diskussion darüber, ob die Deutsche Telekom den Datenverkehr zu den Servern von Digitalcourage möglicherweise etwas ungleicher behandelt, als sie sollte.

Ich bin letzten Mittwoch (9. November 2022) auf die Diskussion aufmerksam geworden, und das Probleme hat es mir irgendwie angetan. Ueber einen Bekannten bei der Deutschen Telekom, der bereits an der Fehlersuche beteiligt war, kam ich mit den Digitalcourage-Leuten in Kontakt, und schlug vor zu schauen, ob ich eventuell helfen kann. Diese fanden die Idee nicht schlecht, und somit konnte der Debug-Spasz beginnen.

Da es sicher einige interessiert was nun wirklich passiert ist (und das wirklich habe ich auch erst heute nacht herausgefunden), dachte ich, ich schreib mal einen Blogartikel.

DEBUG: Start

Der erste Schritt beim Debuggen eines solchen Problems, ist natürlich das Problem zu reproduzieren. Für den vorliegenden Fall war dies überraschend einfach. Als ich versuchte, eine Testdatei an meinem Rechner herunterzuladen, sah das nicht so gut aus:

% wget -O /dev/null https://digitalcourage.social/1GB.bin
--2022-11-09 18:53:17--  https://digitalcourage.social/1GB.bin
Resolving digitalcourage.social (digitalcourage.social)... 217.197.90.87
Connecting to digitalcourage.social (digitalcourage.social)|217.197.90.87|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1048576000 (1000M) [application/octet-stream]
Saving to: ‘/dev/null’

/dev/null             0%[                    ] 615.70K  98.8KB/s    eta 2h 53m 

Hier ist das ausnahmsweise gut.

  • a) Es gibt mir ein System, auf dem ich Debuggen kann
  • b) Mein Netz geht durch einen Tunnel, der letztendlich durch AS59645 zum Internet kommt; Und IN-BERLIN (der Hoster von Digitalcourage) ist einer meiner Upstreams.
  • c) Es bedeutet, dass die Deutsche Telekom hier unschuldig ist. Daher sollte sich–was auch immer kaputt ist–auf den Systemen von Digitalcourage fixen lassen.

Also, die Telekom ist raus, ich kann auf den kompletten Pfad bis zur Uebergabe an IN-BERLIN schauen… los gehts.

Der nächste Schritt bestand also darin, zu sehen, ‘wo’ Dinge kaputt gehen.

  • Das Abrufen der Datei von meiner Workstation war langsam.
  • Das Abrufen der Datei vom ersten Router war langsam.
  • Die Datei von meinem Router drüben bei IN-BERLIN abzurufen war schnell. Da die Verbindung zwischen meinem Router bei IN-BERLIN und dem vor meiner Workstation über eine weniger-als-1500b-MTU-Verbindung verläuft, beschlich mich doch recht schnell der Verdacht, dass wir hier ein MTU Problem haben.

MTU?

Die MTU oder Maximum Transmission Unit ist ein Wert in Bytes, der Rechnern im Internet mitteilt, wie groß Netzwerkpakete sein dürfen. Die MTU haengt mit der MSS (Maximum Segment Size) für TCP-Verbindungen zusammen. Üblicherweise ist die MSS ‘MTU - 20b’ (für IPv4) und ‘MTU - 40b’ (für IPv6), d.h. wenn die MTU 1500 ist (wie es für die meisten Ethernet-Verbindungen und im Wesentlichen die meisten Verbindungen im Internet ist), sollte die MSS 1480 für IPv4 und 1460 für IPv6 sein.

Leider haben nicht alle Links im Internet eine MTU von 1500. Meine (wireguard-basierte) Tunnelverbindung hat beispielsweise eine MTU von 1420. Daher ist die MSS für IPv4 1400 hier. Ebenso haben DSL-User der Deutschen Telekom eine MTU von 1492, da für PPPoE 8b benötigt werden. Kabelnetzbetreiber verwenden normalerweise kein PPPoE, und daher haben ihre User normalerweise eine MTU von 1500.

Da Links unter 1500b üblich sind, gibt es natürlich Mechanismen für Hosts, um die maximale MTU auf einem Pfad zu einem Server/Client zu bestimmen, damit sie sicherstellen können, dass keine größeren Pakete gesendet werden. Dies wird allgemein als Path MTU Discovery (PMTUD) bezeichnet, und ich hatte bereits meinen fairen Anteil an Kopfschmerzen damit. Die Idee hinter PMTUD ist im Wesentlichen, dass jeder Host auf dem Pfad, sobald er ein Paket nicht weiterleiten kann weil es zu groß ist, eine ICMP-Fehlermeldung vom Typ 3 Code 4 (Fragmentation Needed and Don’t Fragment was set) sendet. Wenn der sendende Host dieses Paket erhält, weiß er, dass er kleinere Pakete senden muss, wenn er möchte, dass sie ankommen.

Witzigerweise würde die MTU-Problematik auch erklären, warum Telekom-User betroffen sind, und andere nicht: Die 1492b-MTU wegen PPPoE.

MTU? Bist du’s wirklich?

Es war vergleichsweise einfach zu überprüfen, ob dieses Problem wirklich mit der MTU zusammenhängt. Ich habe tcpdump auf dem externen Interface meines Routers bei IN-BERLIN gestartet (wo Pakete auf einer 1500-MTU-Verbindung eingehen und über eine 1420-MTU-Verbindung hinausgehen) und einen Filter für ICMP-Pakete gesetzt, die für digitalcourage.social bestimmt sind; Dann habe ich versucht, eine Datei von meiner Workstation zu wgeten. Und tatsächlich:

% tcpdump -i vio0 -n host 217.197.90.87 and icmp
tcpdump: listening on vio0, link-type EN10MB
20:16:38.979978 IP 217.197.83.197 > 217.197.90.87: ICMP 195.191.197.217 unreachable - need to frag (mtu 1420), length 36
20:16:39.984627 IP 217.197.83.197 > 217.197.90.87: ICMP 195.191.197.217 unreachable - need to frag (mtu 1420), length 36
20:16:39.985268 IP 217.197.83.197 > 217.197.90.87: ICMP 195.191.197.217 unreachable - need to frag (mtu 1420), length 36
20:16:40.984956 IP 217.197.83.197 > 217.197.90.87: ICMP 195.191.197.217 unreachable - need to frag (mtu 1420), length 36
...

Das ist natuerlich etwas seltsam. Eigentlich sollte die Box die da mit zu groszen Packeten um sich wirft (217.197.90.87) recht schnell merken, dass die MTU zu grosz ist. So nach dem ersten oder zweiten ‘Packet too large’. Aber das ist der Box wohl egal…

Wo es bei der PMTUD harkt

Zusammen mit Christian von Digitalcourage (der mir beim Debuggen geholfen hat und meine /bin/instantmessangersh zur Digitalcourage-Infrastruktur war, da ich auf deren Servern natürlich keinen Login habe) habe ich nun angefangen zu graben. Irgendwo muessen die Paeckchen ja verloren gehen. Das Graben war relativ schnell erfolgreich, da sowohl der Router von Digitalcourage als auch digitalcourage.social nur ICMP-Typ 8 (Echo-Antworten, was gemeint ist wenn Menschen von ‘ping’ reden) zuließ, aber nicht Typ 3 (Code 4). Interessanterweise legte die Mastodon-Dokumentation dies nahe; Dankenswerterweise gibt es da aber schon einen pull-request bereits aktualisiert.

Zwei (in die Firewall-Frameworks der Systeme integrierte) iptables -A INPUT -p icmp -m icmp --icmp-type 3 -j ACCEPT später kamen die ICMP-Pakete endlich auch dort an, wo sie hingehören. Also, wget and, schneller Download, oder? Nope. -.-'

Irgendwas is komisch…

Irgendwas ist also komisch. Genaugenommen: Eine ganze Menge. Die MSS wird einem entfernten Host mitgeteilt, wenn eine Verbindung hergestellt wird. Eigentlich nageln meine Router die MSS fuer Verbindungen ueber Links mit einer MTU unter 1500 auf 1320 fest. Also, PMTUD beiseite, die hätte eigentlich nichtmal gebraucht werden duerfen. Und da PMTUD nun eigentlich funktioniert… sollte wirklich nichts diese Hosts davon abhalten, so zu kommunizieren, wie sie sollten.

Wir haben also angefangen, das funktionierende (wo user schnell downloaden koennen) mit dem anderen Host zu vergleichen. Insbesondere haben wir uns sysctl -a (nicht auffaellig), iptables -L -v -n (wieder nichts) und route get $myip/route show $myip (nope) angesehen. Wir haben auch nochmal ein tcpdump an die Leitung gehalten um zu verifizieren, dass mein Client die richtige MSS übermittelte (1320, und ja, das kam beim Starten der TCP-Session an). Obwohl nichts an digitalcourage.social speziell zu sein schien ignorierte die box die existenz von MTUs unter 1500. Jedes Packet wurde erst mit einer Groesze von 1500b gesendet… und dann 40b Schrittweise bei den retransmissions verkleinert, bis es irgendwann durch kam. Das gibt natuerlich viel overhead und wenig Bandbreite.

Dinge zum Laufen bringen (vorerst)

Etwas frustriert (und mit etwas Input von anwlx), entschieden wir uns, einfach mal die MTU fuer meine IP mit einer statischen Route (vorsicht, Fachbegriff) festzudengeln.

ip route add 195.191.197.206 via 217.197.90.81 mtu lock 1320

Und siehe da… es funktioniert. 100Mbit+ beim Downloaden auf meiner Workstation.

Trotz erweitertem Rumprobierens konnten wir leider nicht herausfinden, warum der Host MTU und MSS ignorierte. An diesem Punkt kurz nach 00:00 Uhr schlug ich vor, einfach einen Hotfix auf das Problem zu werfen, der für die meisten User funktioniert:

ip r a 0.0.0.0/1 via 217.197.90.81 mtu lock 1320
ip r a 128.0.0.0/1 via 217.197.90.81 mtu lock 1320

Damit hatten wir die MTU fuer zwei spezifischere Routen als die default-Route (0.0.0.0/0) auf 1320 festgedengelt; Und da sie spezifischer als die default route sind, werden diese beiden dann auch dieser vorgezogen. Und im zweifel ist das einfach etwas entspannter, als die default Route auf einer Prod-Box zu aendern. Christian war ziemlich glücklich über diesen Vorschlag und warf ihn gleich auf die Box. Für den Moment war das Problem also erstmal weg, und Antworten auf die Ankündigung, dass Dinge jetzt funktionieren sollten, suggerieren, dass es tatsächlich funktionierte.

Muss das so?

Mir hat das natuerlich alles keine Ruhe gelassen (und ich hatte irgendwie am Anfange des Artikels eine richtige Erklaerung versprochen… ): Also habe ich mal versucht das ganze nachzubauen

  • Erstmal eine VM mit der gleiche Software (Debian 10) aufsetzen (Debuggen in Prod ist immer etwas unlustig)
  • Mastodon ohne Docker installieren (vielleicht macht das irgendwas kaputt?!)
  • Die sysctl settings und geladenen Kernel-Module an die Einstellungen der prod-box anpassen

Ich habe das am Samstag eingerichtet und war eigentlich ziemlich hoffnungsvoll, dass das nicht funktioniert. Also er… der Fehler auftritt. Tat er aber nicht. Kein Problem fuer mich.

Womit wir bei heute wären. Mich beschlich die Frage, ob vielleicht irgendwas an der Virtualisierungsumgebung komisch ist. Ich habe mich wieder bei Christian gemeldet und gefragt, was fuer eine Virtualisierungsloesung laeuft (libvirt+kvm auf Debian). Mein Bauchgefuehl liesz mich dann auch nach einem lspci von der Mastodon Box fragen. Vielleicht ist da was dabei?

Und da fiel mir in der tat gleich was auf:

00:03.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL-8100/8101L/8139 PCI Fast Ethernet Adapter (rev 20)

KVM VMs machen eigentlich nur mit virtio Netzwerkkarten so richtig spasz.

Also, schneller Wechsel auf meiner Test-VM, die NIC wird eine ‘rtl8139’ und nichtmehr ‘virtio’, uuuuuuuuuuuund… Karpot. Endlich. wget von meiner Workstation benimmt sich nun auch so wie es (nicht) soo, mit nur etwa 100 KB/s im Download. Die zweite Digitalcourage Box, von der alle User immer mit der zu erwartenden Geschwindigkeit downloaden konnten, nutzt auch erwartungsgemaesz eine virtio NIC:

00:03.0 Ethernet controller: Red Hat, Inc Virtio network device

Mit einer Idee wonach ich nun suchen koennte (ok, rtl8139, kaputt’ ist nicht wirklich ein einzigartiger Suchbegriff), fand ich schließlich diesen Thread auf den qemu-dev/netdev Mailinglisten. Es scheint, dass andere tatsächlich schonmal das gleiche Problem hatten. Vor sechs Jahren. Der Thread stellt ziemlich schnell fest, dass die MTU für das TCP-Segment-Offloading im Qemu rtl8139-Code auf 1500 festgedengelt zu sein scheint. Der Thread schlägt sogar einen Patch vor, der das Problem beheben sollte (aber danach wirds still im thread). Letztendlich kommt der Code mit diesem Fehler vermutlich aus dem Jahr 2006, ist also rund 16 Jahre alt (oder auch: war schon zehn Jahre alt, als das Thema auf der Mailingliste aufkam).

Wenn ich mir den ‘Modifications:’ Header in rtl8139.c ansehe, habe ich den starken Verdacht, dass dies letztendlich auf diese Änderung hinausläuft:

 *  2006-Jul-04                 :   Implemented TCP segmentation offloading
 *                                  Fixed MTU=1500 for produced ethernet frames

Dazu passt auch, dass das abschalten von TSO/GSO den gleichen positiven Effekt wie eine lock mtu 1320 route hat:

ethtool -K ens18 tx off sg off tso off

Aber da ich nicht wirklich eine C-artige Person oder übermäßig qualifiziert bin was Systemrogrammierung angeht bin, dachte ich, es wäre vielleicht besser, einfach ein Ticket zu erstellen und Feierabend zu machen..

Damit gibt das Problem mir ruhe, und die Leute von Digitalcourage haben einen Grund, den Netzwerkschnittstellentyp ihrer virtualisierten NIC zu ändern. ^^

Woran ich denken sollte

Zu zum Abschluss als ‘lessons learned’:

Beim Vergleichen zweier virtueller Maschinen auf dem selben Hypervisor ist es nicht moeglich davon auszugehen, dass diese die gleiche virtuelle ‘Hardware’ haben.

Oder davon auszugehen, dass VMs keine Hardwareprobleme haben…