- Eine IPv6-Zone ist eine Schreibweise, mit der sich das Ziel-Interface unterscheiden lässt, wenn mehrere Interfaces denselben Link-Local-Bereich
fe80:: verwenden, etwa wie in fe80::4%eth0
- In URLs werden IPv6-Adressen in Klammern gesetzt, um Port und Doppelpunkte zu unterscheiden, also etwa
[fe80::4]:80; mit Zone ergibt sich die eckige Klammernotation [fe80::4%eth0]:80
- Go
net/url interpretiert ]:80 fälschlich als ungültiges URL-Escape %et und wirft den Fehler invalid URL escape
- RFC 6874 definiert ein IPv6-Literal mit Zone als
IPv6address "%25" ZoneID, daher muss % in URLs per Percent-Encoding als %25 kodiert werden
- Wenn Anubis auf eine IPv6-Zonenadresse zeigen soll, muss diese Schreibweise verwendet werden; damit bleibt es ein Edge Case, der bis zu Kompatibilitätsproblemen bei Origin und Bibliotheken reicht, wie etwa bei Browsern, nginx und Requests
Konflikt zwischen IPv6-Zone und URL-Syntax
- Link-Local-Adressen in IPv6 können auf jedem Interface im Bereich
fe80::whatever liegen. Wenn also zwei Netzwerkschnittstellen vorhanden sind, muss für das Ziel fe80::4 eine IPv6-Scope/Zone) verwendet werden, um das Zielinterface zu unterscheiden
- Das Format des Zonenwerts hängt vom Betriebssystem ab: Unter Linux wird der Interface-Name verwendet, unter Windows die Interface-ID
- Im Beispiel ist
eth0 der Name des Ethernet-Geräts, und die Adresse wird wie folgt dargestellt
fe80::4%eth0
- Host und Port werden normalerweise durch einen Doppelpunkt getrennt, aber IPv6-Adressen verwenden Doppelpunkte ebenfalls zur Trennung von Hex-Gruppen. Deshalb muss
fe80::4 mit Port 80 wie folgt in eckige Klammern gesetzt werden
[fe80::4]:80
- Mit Zone ergibt sich folgendes Format
[fe80::4%eth0]:80
- Setzt man dies direkt als Hostnamen in die URL
http://[fe80::4%eth0]:80, schlägt Go net/url fehl, weil %et als ungültiges URL-Escape interpretiert wird
panic: parse "http://[fe80::4%eth0]:80": invalid URL escape "%et"
Standardkonforme Lösung und verbleibende Probleme
- Werte, die nicht zur URL-Syntax passen, müssen mit Percent-Encoding kodiert werden;
%20 in einer URL ist zum Beispiel die Kodierung eines in URLs ungültigen ASCII-Leerzeichens
- Auch das
% der IPv6-Zone selbst muss kodiert werden. In Go muss daher http://[fe80::4%25eth0]:80 verwendet werden, damit Hostname() als Ergebnis fe80::4%eth0 zurückgibt
- RFC 9844 bietet Richtlinien für Benutzeroberflächen beim Umgang mit IPv6-Zonen, und RFC 6874 definiert die Syntax für IPv6-Literale mit Zone in URLs wie folgt
IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]"
ZoneID = 1*( unreserved / pct-encoded )
IPv6addrz = IPv6address "%25" ZoneID
- Derselbe Edge Case taucht auch im nginx-Ticket, im Requests-Issue und im HTTP link-local URI BCP draft auf
- Browser unterstützen IPv6-Zonen derzeit nicht, weil sie damit das Konzept des „Origin“ aufbrechen würden, das für viele subtile Verhaltensweisen verwendet wird. Der genannte Draft ist ein Versuch, eine Zone-Origin für IPv6 zu definieren, die von Browsern verwendet werden kann
- Wenn Anubis auf eine IPv6-Zonenadresse zeigen soll, muss
% per Percent-Encoding kodiert werden, und solange man die Go-Standardbibliothek nicht forken will, muss man bei diesem Edge Case die schlechte UX in Kauf nehmen
1 Kommentare
Lobste.rs-Kommentare
TL;DR: Computer waren ein Fehlerfühlt sich für mich ein wenig so an, als würde man mit dem Bade das Kind ausschüttenIm Grunde ist das dieselbe Logik wie bei einem Anime-Bösewicht wie in Trigun: Weil Menschen schreckliche Verbrechen begehen können, solle man gleich alle Menschen abschaffen
Ich weiß schon, dass das scherzhaft gemeint ist, aber ich finde es interessant und werde es wohl zum Thema meines nächsten Vortrags machen
Trotzdem kann ich dem Kern zustimmen. Die ganze Situation ist ziemlich absurd
Allerdings ist es nicht gerade selten, dass mit Link-Local-IPv6-Adressen nicht richtig umgegangen wird
Denn jetzt hat
%nur noch im Host-Teil einer URL eine andere Bedeutung, und auch dort nur dann, wenn der Host eine IPv6-Adresse ist und in[...]stehtDie Grammatik muss deshalb nicht zwangsläufig mehrdeutig sein, aber das ist nicht der entscheidende Punkt. Je mehr solche Sonderfälle es gibt, desto größer ist die Wahrscheinlichkeit, dass ein URL-Parser einen bestimmten Spezialfall übersieht, und Unterschiede zwischen Parsern sind ein idealer Nährboden für unschöne Bugs oder Sicherheitsprobleme
Ich persönlich bevorzuge es zwar, IPv6-Zonen in URLs zu verarbeiten, aber nachdem es zwischenzeitlich die Vorgabe gab,
%URL-zu-encoden, entsteht bei einer nachträglichen Rücknahme tatsächlich echte Mehrdeutigkeit. Schadepct-encoded = "%" HEXDIG HEXDIGDabei ist
HEXDIGals[a-fA-F]definiert, sodass%etals ungültige Sequenz geparst wirdIn der Spezifikation steht außerdem, dass das Zeichen
%als Indikator für percent-encodete Oktette verwendet wird und daher als Daten innerhalb eines URI mit%25percent-encodet werden muss. Wenn eine bereits decodierte Zeichenfolge erneut decodiert wird, kann ein Prozent-Datenoktett fälschlich als Beginn einer Percent-Encoding-Sequenz missverstanden werden; wird eine bereits encodete Zeichenfolge erneut encodiert, entsteht das umgekehrte Problem. Deshalb sollen Implementierungen dieselbe Zeichenfolge nicht mehr als einmal encodieren oder decodierenDeshalb ist das zwar nervig, aber ich würde es in der Praxis kaum als Bug bezeichnen
%zu verwenden. Encodete Zeichen sind auch im Host-Teil erlaubt, und wenn man%in einer Zonenadresse unverändert stehen lässt, entsteht Mehrdeutigkeit%selbst ist kein unreserviertes Zeichen, also ist Percent-Encoding hier korrektDass
net/urlundnet/httpin Go mit den URL-RFCs kollidieren, ist nichts Neues. Vor allem die Existenz vonnet/url.URL.Pathund dessen Verwendung innet/httpsind ziemlich lästig, weil dadurch%2Fkaputtgeht. Auchnet/http.Redirectverwendetpath.Cleanund faltet deshalb//fälschlich zu/zusammenEs gibt viele Gründe, warum man die URL-bezogenen Teile der Go-Standardbibliothek forken oder so etwas wie
net/url/v2vorschlagen möchte. Aber nach allem, was dieser Beitrag zeigt, wirkt Gos Verarbeitung von IPv6-Zonenadressen plausibel und korrekt