- IPv6 zone は、複数のインターフェースが同じ
fe80:: のリンクローカル範囲を使うときに、fe80::4%eth0 のように対象インターフェースを区別するための表記
- URL では IPv6 アドレスはポートとのコロンを区別するため
[fe80::4]:80 のように囲み、zone まで付けると 角括弧表記 は [fe80::4%eth0]:80 という形式になる
- Go の
net/url は ]:80 を %et という誤った URL エスケープとして解釈し、invalid URL escape エラーを出す
- RFC 6874 は zone 付き IPv6 リテラルを
IPv6address "%25" ZoneID と定義しているため、URL では % を %25 として percent-encode しなければならない
- Anubis が IPv6 zone アドレスを指すにはこの表記を使う必要があり、ブラウザー・nginx・Requests 関連の問題のように、origin とライブラリ互換性まで絡む エッジケース のまま残っている
IPv6 zone と URL 構文の衝突
- IPv6 のリンクローカルアドレスはインターフェースごとに
fe80::whatever の範囲に置かれるため、2 つのネットワークインターフェースがあるときに fe80::4 の宛先を区別するには IPv6 scope/zone) の利用が必要になる
- zone 値の形式は OS ごとに異なり、Linux ではインターフェース名、Windows ではインターフェース ID を使う
- 例では
eth0 はイーサネットデバイス名で、アドレスは次のように表される
fe80::4%eth0
- ホストとポートは通常コロンで区切るが、IPv6 アドレスも 16 進グループの区切りにコロンを使うため、ポート 80 の
fe80::4 は次のように角括弧で囲う必要がある
[fe80::4]:80
[fe80::4%eth0]:80
panic: parse "http://[fe80::4%eth0]:80": invalid URL escape "%et"
標準上の解決策と残る問題
IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture ) "]"
ZoneID = 1*( unreserved / pct-encoded )
IPv6addrz = IPv6address "%25" ZoneID
- 同じエッジケースは nginx チケット、Requests issue、HTTP link-local URI BCP draft にも関係している
- ブラウザーは現在 IPv6 zone をサポートしておらず、その理由は多くの微妙な動作で使われる「origin」という概念を壊してしまうからであり、この draft はブラウザーで使えるように IPv6 の zone origin を定義しようとする試み
- Anubis が IPv6 zone アドレスを指すには
% を percent-encoding する必要があり、Go 標準ライブラリを fork しない方針のもとでは、このエッジケースの悪い UX を受け入れるしかない
1件のコメント
Lobste.rs の意見
TL;DR: コンピュータは間違いだったみたいな結論は、風呂の水を捨てるついでに赤ん坊まで捨てる感じではないかと思う文字どおり Trigun みたいなアニメの悪役の論理のように、人間が恐ろしい犯罪を犯しうるから全人類を消そう、という話に見える
冗談交じりの表現なのは分かるが面白く、今準備中の次の発表テーマにする予定だ
それでも核心には共感する。こういう状況自体がかなり滑稽ではないかと思う
ただし、リンクローカル IPv6 アドレスをきちんと扱えないケースは珍しくない
これで
%は URL のホスト部分でのみ、しかもホストが IPv6 アドレスで[...]の中にある場合に限って別の意味を持つことになるからだ文法が依然として曖昧ではありえない、という話ではないが、要点はそこではない。こうした例外ケースが増えるほど、URL パーサーが特定の例外を見落とす可能性が高まり、パーサー間の差異に汚いバグやセキュリティ問題が紛れ込みやすくなる
個人的には URL で IPv6 ゾーンを扱えるほうを好むが、かつて
%を URL エンコードすべきという指針があった以上、今からひっくり返すと実際に曖昧さが生じる。残念なことだpct-encoded = "%" HEXDIG HEXDIGここで
HEXDIGは[a-fA-F]として定義されているので、%etを不正なシーケンスとしてパースする仕様ではさらに、
%文字はパーセントエンコードされたオクテットの指示子として使われるため、URI 内でデータとして使うには%25にパーセントエンコードしなければならないとしている。すでにデコード済みの文字列を再度デコードすると、パーセントデータオクテットをパーセントエンコードの開始だと誤解する可能性があり、すでにエンコード済みの文字列を再度エンコードしても逆の問題が起きるので、実装は同じ文字列を二度以上エンコードまたはデコードしてはならないとしているなので厄介なのは確かだが、実際には バグとは言いがたい と思う
%を直接使うのが、なぜそれほどひどいことなのかと思う。エンコードされた文字はホスト部分でも許可されており、ゾーンアドレスで%をそのまま使うと 曖昧さ が生じる%自体は予約されていない文字ではないので、パーセントエンコードするのが正しいGo の
net/urlとnet/httpが URL RFC と衝突するのは今に始まったことではない。特にnet/url.URL.Pathの存在とnet/httpにおけるその利用はかなり厄介で、%2Fを壊してしまうからだ。net/http.Redirectもpath.Cleanを使うため、//を/に誤って畳み込んでしまうGo 標準ライブラリの URL を触る部分をフォークしたい、あるいは
net/url/v2のようなものを提案したい理由はたくさんある。しかしこの記事で見える限り、Go の IPv6 ゾーンアドレス処理 は妥当で正しいほうに見える