1 ポイント 投稿者 GN⁺ 4 시간 전 | 1件のコメント | WhatsAppで共有
  • 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
  • zone まで付けると次の形式になる
[fe80::4%eth0]:80
  • これを URL ホスト名に直接入れた http://[fe80::4%eth0]:80 は、Go の net/url では %et を不正な URL エスケープと解釈して失敗する
panic: parse "http://[fe80::4%eth0]:80";: invalid URL escape "%et"

標準上の解決策と残る問題

  • URL 構文に適合しない値には percent-encoding を使う必要があり、URL の %20 は URL で有効でない ASCII 空白をエンコードした例
  • IPv6 zone の % 自体もエンコードする必要があるため、Go では http://[fe80::4%25eth0]:80 のように書くと Hostname() の結果が fe80::4%eth0 になる
  • RFC 9844 はユーザーインターフェースで IPv6 zone を扱うための指針を提供し、RFC 6874 は URL における zone 付き IPv6 リテラルの構文を次のように定義している
IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture  ) "]"

ZoneID = 1*( unreserved / pct-encoded )

IPv6addrz = IPv6address "%25" ZoneID
  • 同じエッジケースは nginx チケットRequests issueHTTP link-local URI BCP draft にも関係している
  • ブラウザーは現在 IPv6 zone をサポートしておらず、その理由は多くの微妙な動作で使われる「origin」という概念を壊してしまうからであり、この draft はブラウザーで使えるように IPv6 の zone origin を定義しようとする試み
  • Anubis が IPv6 zone アドレスを指すには % を percent-encoding する必要があり、Go 標準ライブラリを fork しない方針のもとでは、このエッジケースの悪い UX を受け入れるしかない

1件のコメント

 
GN⁺ 4 시간 전
Lobste.rs の意見
  • TL;DR: コンピュータは間違いだったみたいな結論は、風呂の水を捨てるついでに赤ん坊まで捨てる感じではないかと思う
    文字どおり Trigun みたいなアニメの悪役の論理のように、人間が恐ろしい犯罪を犯しうるから全人類を消そう、という話に見える
    冗談交じりの表現なのは分かるが面白く、今準備中の次の発表テーマにする予定だ
    • タイトルがかなり劇的で釣り気味なのは確かだ
      それでも核心には共感する。こういう状況自体がかなり滑稽ではないかと思う
  • URL で リンクローカルスコープの IPv6 アドレス を正しく処理できないのは間違いだ
    ただし、リンクローカル IPv6 アドレスをきちんと扱えないケースは珍しくない
    • 一方で、URL 内の IPv6 アドレスで ゾーン(zone) を扱おうとすると複雑さはかなり増す
      これで % は URL のホスト部分でのみ、しかもホストが IPv6 アドレスで [...] の中にある場合に限って別の意味を持つことになるからだ
      文法が依然として曖昧ではありえない、という話ではないが、要点はそこではない。こうした例外ケースが増えるほど、URL パーサーが特定の例外を見落とす可能性が高まり、パーサー間の差異に汚いバグやセキュリティ問題が紛れ込みやすくなる
      個人的には URL で IPv6 ゾーンを扱えるほうを好むが、かつて % を URL エンコードすべきという指針があった以上、今からひっくり返すと実際に曖昧さが生じる。残念なことだ
  • RFC 3986 と互換性のあるライブラリや実装なら、この問題に遭遇することになる。仕様がパーセントエンコードされたシーケンスを次のように定義しているからだ
    pct-encoded = "%" HEXDIG HEXDIG
    ここで HEXDIG[a-fA-F] として定義されているので、%et を不正なシーケンスとしてパースする
    仕様ではさらに、% 文字はパーセントエンコードされたオクテットの指示子として使われるため、URI 内でデータとして使うには %25 にパーセントエンコードしなければならないとしている。すでにデコード済みの文字列を再度デコードすると、パーセントデータオクテットをパーセントエンコードの開始だと誤解する可能性があり、すでにエンコード済みの文字列を再度エンコードしても逆の問題が起きるので、実装は同じ文字列を二度以上エンコードまたはデコードしてはならないとしている
    なので厄介なのは確かだが、実際には バグとは言いがたい と思う
  • ゾーン付きアドレスで % を直接使うのが、なぜそれほどひどいことなのかと思う。エンコードされた文字はホスト部分でも許可されており、ゾーンアドレスで % をそのまま使うと 曖昧さ が生じる
    % 自体は予約されていない文字ではないので、パーセントエンコードするのが正しい
    Go の net/urlnet/http が URL RFC と衝突するのは今に始まったことではない。特に net/url.URL.Path の存在と net/http におけるその利用はかなり厄介で、%2F を壊してしまうからだ。net/http.Redirectpath.Clean を使うため、/// に誤って畳み込んでしまう
    Go 標準ライブラリの URL を触る部分をフォークしたい、あるいは net/url/v2 のようなものを提案したい理由はたくさんある。しかしこの記事で見える限り、Go の IPv6 ゾーンアドレス処理 は妥当で正しいほうに見える