- La zone de IPv6 es una notación que permite distinguir la interfaz de destino, como en
fe80::4%eth0, cuando varias interfaces usan el mismo alcance link-local fe80::
- En una URL, una dirección IPv6 se encierra como
[fe80::4]:80 para distinguir el puerto de los dos puntos, y si además incluye zone, la notación entre corchetes queda como [fe80::4%eth0]:80
net/url de Go interpreta ]:80 como un escape de URL inválido %et y lanza el error invalid URL escape
- RFC 6874 define un literal IPv6 con zone como
IPv6address "%25" ZoneID, así que en una URL el % debe codificarse con percent-encoding como %25
- Si Anubis necesita apuntar a una dirección IPv6 con zone, debe usar esa notación, y sigue siendo un caso borde que además involucra compatibilidad de origin y de bibliotecas, como en los problemas relacionados con navegadores, nginx y Requests
Conflicto entre la zone de IPv6 y la sintaxis de URL
- Las direcciones link-local de IPv6 pueden existir en el rango
fe80::whatever en cada interfaz, así que si hay dos interfaces de red, para distinguir el destino fe80::4 hace falta usar IPv6 scope/zone)
- El formato del valor de zone depende del sistema operativo; en Linux se usa el nombre de la interfaz y en Windows el ID de la interfaz
- En el ejemplo,
eth0 es el nombre del dispositivo Ethernet, y la dirección se expresa así
fe80::4%eth0
- Normalmente host y puerto se separan con dos puntos, pero como las direcciones IPv6 también usan dos puntos para separar grupos hexadecimales,
fe80::4 con el puerto 80 debe ir entre corchetes de esta forma
[fe80::4]:80
- Si además se agrega la zone, queda con este formato
[fe80::4%eth0]:80
- Si eso se pone directamente en el host de la URL como
http://[fe80::4%eth0]:80, en Go net/url falla porque interpreta %et como un escape de URL inválido
panic: parse "http://[fe80::4%eth0]:80": invalid URL escape "%et"
La solución según el estándar y los problemas que siguen pendientes
- Los valores que no encajan con la sintaxis de URL deben usar percent-encoding; por ejemplo,
%20 en una URL codifica un espacio ASCII, que no es válido directamente en una URL
- Como el
% de la zone de IPv6 también debe codificarse, en Go hay que escribir http://[fe80::4%25eth0]:80 para que el resultado de Hostname() sea fe80::4%eth0
- RFC 9844 da lineamientos para manejar zones de IPv6 en interfaces de usuario, y RFC 6874 define así la sintaxis de un literal IPv6 con zone dentro de una URL
IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]"
ZoneID = 1*( unreserved / pct-encoded )
IPv6addrz = IPv6address "%25" ZoneID
- El mismo caso borde también aparece en el ticket de nginx, el issue de Requests y el borrador BCP de URI link-local para HTTP
- Actualmente los navegadores no soportan zones de IPv6 porque rompen el concepto de “origin”, que se usa en muchos comportamientos sutiles; ese borrador intenta definir el origin de una zone de IPv6 para que los navegadores puedan usarlo
- Si Anubis necesita apuntar a una dirección IPv6 con zone, debe aplicar percent-encoding al
%, y bajo la política de no hacer fork de la biblioteca estándar de Go, hay que aceptar la mala UX de este caso borde
1 comentarios
Comentarios de Lobste.rs
TL;DR: las computadoras fueron un errorme da la impresión de tirar al bebé junto con el agua suciaLiteralmente suena a la lógica de un villano de anime como Trigun: como los humanos pueden cometer crímenes horribles, entonces habría que eliminar a toda la humanidad
Sé que es una forma de decirlo en broma, pero me pareció interesante, y planeo usarlo como tema para mi próxima charla
Aun así, coincido con el punto central. Esta situación en sí misma me parece bastante ridícula
Dicho eso, no es raro que no se manejen bien las direcciones IPv6 link-local
Ahora
%adquiere un significado distinto solo en la parte de host de la URL, y solo cuando el host es una dirección IPv6 y está dentro de[...]Puede que la gramática siga sin ser ambigua, pero ese no es el punto. Mientras más casos excepcionales de este tipo haya, mayor es la probabilidad de que un parser de URL pase por alto una excepción específica, y las diferencias entre parsers son terreno fértil para bugs feos o problemas de seguridad
Personalmente prefiero que las URL sí manejen zonas IPv6, pero como en algún momento existió la directriz de codificar
%en la URL, revertir eso ahora sí genera una ambigüedad real. Es una lástimapct-encoded = "%" HEXDIG HEXDIGAquí
HEXDIGse define como[a-fA-F], así que%etse parsea como una secuencia inválidaLa especificación también dice que el carácter
%se usa como indicador de un octeto codificado con percent-encoding, así que para usar%como dato dentro de un URI hay que codificarlo como%25. Si vuelves a decodificar una cadena ya decodificada, puedes malinterpretar un octeto de datos con%como el inicio de un percent-encoding; y si vuelves a codificar una cadena ya codificada, pasa el problema inverso. Por eso la implementación no debe codificar ni decodificar la misma cadena más de una vezAsí que sí, es molesto, pero en la práctica me cuesta verlo realmente como un bug
%directamente en una dirección con zona. Los caracteres codificados también están permitidos en la parte de host, y usar%tal cual en una dirección con zona sí crea ambigüedad%en sí no es un carácter no reservado, así que corresponde codificarlo con percent-encodingQue
net/urlynet/httpde Go entren en conflicto con los RFC de URL no es nada nuevo. En particular, la existencia denet/url.URL.Pathy su uso ennet/httpson bastante molestos, porque rompen%2F.net/http.Redirecttambién usapath.Clean, así que colapsa//a/cuando no deberíaHay muchas razones para querer hacer un fork de las partes de la biblioteca estándar de Go que manipulan URL, o para proponer algo como
net/url/v2. Pero por lo que se ve en este post, el manejo de direcciones IPv6 con zona en Go parece razonable y correcto