1 คะแนน โดย GN⁺ 4 시간 전 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • IPv6 zone คือรูปแบบที่ใช้ระบุอินเทอร์เฟซปลายทาง เช่น fe80::4%eth0 เมื่อหลายอินเทอร์เฟซใช้ช่วงลิงก์โลคัล fe80:: เดียวกัน
  • ใน URL ที่อยู่ IPv6 ต้องครอบด้วยวงเล็บเหลี่ยมแบบ [fe80::4]:80 เพื่อแยกพอร์ตออกจากโคลอนในที่อยู่ และเมื่อมี zone ด้วย รูปแบบ วงเล็บเหลี่ยม จะเป็น [fe80::4%eth0]:80
  • net/url ของ Go ตีความ ]:80 ผิดเป็น URL escape %et ที่ไม่ถูกต้อง จึงเกิดข้อผิดพลาด invalid URL escape
  • RFC 6874 นิยาม IPv6 literal ที่มี zone เป็น IPv6address "%25" ZoneID ดังนั้นใน URL ต้องทำ percent-encode ให้ % เป็น %25
  • หาก Anubis ต้องการชี้ไปยังที่อยู่ IPv6 แบบมี zone ก็ต้องใช้รูปแบบนี้ และยังคงเป็น edge case ที่พัวพันถึงความเข้ากันได้ของ origin และไลบรารี เช่นปัญหาที่พบในเบราว์เซอร์, nginx และ Requests

การชนกันระหว่าง IPv6 zone กับไวยากรณ์ของ URL

  • ที่อยู่ link-local ของ IPv6 สามารถอยู่ในช่วง fe80::whatever ได้ในแต่ละอินเทอร์เฟซ ดังนั้นเมื่อมีสองอินเทอร์เฟซเครือข่ายและต้องแยกปลายทาง fe80::4 จึงจำเป็นต้องใช้ IPv6 scope/zone)
  • รูปแบบของค่า zone แตกต่างกันไปตามระบบปฏิบัติการ โดย Linux ใช้ชื่ออินเทอร์เฟซ ส่วน Windows ใช้ ID ของอินเทอร์เฟซ
  • ในตัวอย่าง eth0 คือชื่ออุปกรณ์อีเธอร์เน็ต และที่อยู่จะแสดงดังนี้
fe80::4%eth0
  • ปกติโฮสต์กับพอร์ตจะคั่นกันด้วยโคลอน แต่ที่อยู่ IPv6 ก็ใช้โคลอนคั่นกลุ่มเลขฐานสิบหกด้วย ดังนั้น fe80::4 ที่พอร์ต 80 จึงต้องครอบด้วยวงเล็บเหลี่ยมดังนี้
[fe80::4]:80
  • เมื่อรวม zone เข้าไปด้วย จะได้รูปแบบดังนี้
[fe80::4%eth0]:80
  • หากใส่ลงในชื่อโฮสต์ของ URL ตรง ๆ เป็น http://[fe80::4%eth0]:80 จะล้มเหลวใน Go net/url เพราะตีความ %et เป็น URL escape ที่ไม่ถูกต้อง
panic: parse "http://[fe80::4%eth0]:80";: invalid URL escape "%et"

วิธีแก้ตามมาตรฐานและปัญหาที่ยังเหลือ

  • ค่าที่ไม่ตรงกับไวยากรณ์ของ URL ต้องใช้ percent-encoding เช่น %20 ใน URL คือการเข้ารหัสช่องว่าง ASCII ซึ่งใช้ตรง ๆ ใน URL ไม่ได้
  • เนื่องจาก % ของ IPv6 zone เองก็ต้องถูกเข้ารหัสเช่นกัน ใน Go จึงต้องเขียนเป็น http://[fe80::4%25eth0]:80 จึงจะได้ผลลัพธ์จาก Hostname() เป็น fe80::4%eth0
  • RFC 9844 ให้แนวทางสำหรับการจัดการ IPv6 zone ในส่วนติดต่อผู้ใช้ และ RFC 6874 นิยามไวยากรณ์ของ IPv6 literal ที่มี zone ใน URL ไว้ดังนี้
IP-literal = "[" ( IPv6address / IPv6addrz / IPvFuture  ) "]"

ZoneID = 1*( unreserved / pct-encoded )

IPv6addrz = IPv6address "%25" ZoneID
  • edge case เดียวกันนี้ยังปรากฏใน ตั๋วของ nginx, อิสชูของ Requests และ ร่าง BCP สำหรับ HTTP link-local URI
  • ปัจจุบันเบราว์เซอร์ยังไม่รองรับ IPv6 zone เพราะมันทำให้แนวคิดเรื่อง “origin” ซึ่งถูกใช้กับพฤติกรรมละเอียดอ่อนหลายอย่างพังลง และร่างดังกล่าวก็เป็นความพยายามที่จะนิยาม zone origin ของ IPv6 เพื่อให้เบราว์เซอร์ใช้งานได้
  • หาก Anubis ต้องการชี้ไปยังที่อยู่ IPv6 แบบมี zone ก็จำเป็นต้องทำ percent-encoding ให้ % และภายใต้นโยบายที่ไม่ fork standard library ของ Go ก็ต้องยอมรับ UX ที่ไม่ดีของ edge case นี้

1 ความคิดเห็น

 
GN⁺ 4 시간 전
ความเห็นจาก Lobste.rs
  • สรุปแบบ TL;DR: คอมพิวเตอร์คือความผิดพลาด มันให้ความรู้สึกแบบ เทน้ำทิ้งทั้งอ่างพาลูกทิ้งไปด้วย ไหม
    พูดตรงตัวก็คือเป็นตรรกะแบบตัวร้ายในอนิเมะอย่าง Trigun ที่บอกว่ามนุษย์สามารถก่ออาชญากรรมอันน่าสยดสยองได้ งั้นก็ต้องกำจัดมนุษย์ทั้งหมด
    ถึงจะรู้ว่าเป็นการพูดติดตลก แต่ก็น่าสนใจดี และผมตั้งใจจะเอาไปเป็นหัวข้อของงานพูดครั้งถัดไปที่กำลังเตรียมอยู่
    • ยอมรับว่าชื่อมันดราม่ามากและ จงใจล่อคลิก
      แต่ก็เห็นด้วยกับใจความหลักอยู่ดี รู้สึกว่าสถานการณ์แบบนี้มันค่อนข้างน่าขันเหมือนกัน
  • การจัดการ ที่อยู่ IPv6 แบบ link-local scope ใน URL ได้ไม่ถูกต้องนั้นเป็นความผิดพลาด
    แต่ในอีกด้านหนึ่ง กรณีที่จัดการที่อยู่ IPv6 แบบ link-local ได้ไม่ถูกต้องก็ไม่ได้หายากนัก
    • ในทางกลับกัน ถ้าจะรองรับ zone ในที่อยู่ IPv6 ภายใน URL ความซับซ้อนก็เพิ่มขึ้นพอสมควร
      เพราะตอนนี้ % จะมีความหมายพิเศษเฉพาะในส่วน host ของ URL เท่านั้น และเฉพาะเมื่อ host เป็นที่อยู่ IPv6 และอยู่ภายใน [...]
      ไวยากรณ์อาจยังทำให้ไม่กำกวมได้ก็จริง แต่ประเด็นไม่ได้อยู่ตรงนั้น ยิ่งมีกรณียกเว้นแบบนี้มากขึ้น parser ของ URL ก็ยิ่งมีโอกาสพลาดข้อยกเว้นเฉพาะทาง และความต่างระหว่าง parser แต่ละตัวก็ยิ่งเปิดช่องให้บั๊กสกปรก ๆ หรือปัญหาด้านความปลอดภัยแฝงตัวเข้ามาได้ง่าย
      ส่วนตัวผมเอนเอียงไปทางรองรับ IPv6 zone ใน URL แต่เมื่อก่อนเคยมีข้อแนะนำว่าต้อง URL-encode % ดังนั้นถ้าจะย้อนกลับตอนนี้ก็จะเกิดความกำกวมจริง ๆ น่าเสียดายเหมือนกัน
  • ถ้าเป็นไลบรารีหรือ implementation ที่เข้ากันได้กับ RFC 3986 ก็จะเจอปัญหานี้ เพราะสเปกนิยามลำดับที่ถูก percent-encode ไว้แบบนี้
    pct-encoded = "%" HEXDIG HEXDIG
    โดย HEXDIG ถูกนิยามเป็น [a-fA-F] จึง parse %et เป็นลำดับที่ไม่ถูกต้อง
    สเปกยังบอกอีกว่าอักขระ % ถูกใช้เป็นตัวบ่งชี้ octet ที่ถูก percent-encode ดังนั้นถ้าจะใช้เป็นข้อมูลภายใน URI ก็ต้อง percent-encode เป็น %25 การ decode สตริงที่ถูก decode มาแล้วซ้ำอีกครั้งอาจทำให้ octet ข้อมูลที่เป็นเปอร์เซ็นต์ถูกเข้าใจผิดว่าเป็นจุดเริ่มต้นของ percent-encoding และถ้า encode สตริงที่ถูก encode มาแล้วซ้ำอีกครั้งก็จะเกิดปัญหากลับด้าน สเปกจึงบอกว่า implementation ไม่ควร encode หรือ decode สตริงเดียวกันมากกว่าหนึ่งครั้ง
    เพราะงั้นมันน่าหงุดหงิดก็จริง แต่ในทางปฏิบัติผมมองว่า เรียกว่าเป็นบั๊กได้ยาก
  • สงสัยว่าทำไมการใช้ % ตรง ๆ ในที่อยู่ที่มี zone ถึงถูกมองว่าน่ากลัวขนาดนั้น อักขระที่ถูก encode อนุญาตให้ใช้ในส่วน host ได้อยู่แล้ว และการใช้ % ตรง ๆ ในที่อยู่ zone ก็ทำให้เกิด ความกำกวม
    % เองไม่ใช่อักขระ unreserved ดังนั้นการ percent-encode มันจึงถูกต้องแล้ว
    ที่ net/url และ net/http ของ Go ขัดกับ URL RFC ก็ไม่ใช่เรื่องใหม่ โดยเฉพาะการมีอยู่ของ net/url.URL.Path และการใช้งานมันใน net/http ที่ค่อนข้างน่าปวดหัว เพราะมันทำให้ %2F พังได้ net/http.Redirect ก็ใช้ path.Clean จนไปยุบ // ให้เหลือ / แบบผิด ๆ
    มีเหตุผลมากมายที่ทำให้คนอยาก fork ส่วนที่จัดการ URL ใน Go standard library หรือเสนออะไรอย่าง net/url/v2 แต่เท่าที่เห็นจากบทความนี้ การจัดการ ที่อยู่ IPv6 zone ของ Go ดูสมเหตุสมผลและค่อนข้างถูกต้อง