IPv6 link-local ใช้ไม่ได้ผ่าน VPN: ทำไม fe80 ถึงหยุดที่ลิงก์
ที่อยู่ fe80:: มีความหมายเฉพาะบนลิงก์เดียว ดูว่าทำไม VPN แบบกำหนดเส้นทางจึงพามันไปไม่ได้ ความล้มเหลวหน้าตาเป็นอย่างไร และควรใช้อะไรแทน
คุณเปิดทันเนลขึ้นมา แล้ว ping fe80::1 โดยใช้ทันเนลเป็นอินเทอร์เฟซต้นทาง แต่ไม่มีอะไรตอบกลับมา นี่ไม่ใช่กฎไฟร์วอลล์ที่คุณลืม ที่อยู่ link-local มีความหมายเฉพาะบนลิงก์เดียวที่มันถูกสร้างขึ้น และ VPN แบบกำหนดเส้นทางก็ไม่ใช่ลิงก์นั้น fe80::1 ของเราเตอร์ระยะไกลอยู่ในเซกเมนต์ LAN ระยะไกล คุณอยู่อีกหนึ่งฮอปบนเซกเมนต์อื่น และ IPv6 ไม่มีกลไกให้เราเตอร์ส่งต่อแพ็กเก็ต link-local เลย
คำนำหน้า fe80 อธิบายขอบเขต ไม่ใช่ช่วง
Link-local unicast คือ fe80::/10 และคุณสมบัติที่นิยามไม่ใช่ตัวเลขแต่คือขอบเขต ที่อยู่ใช้ได้บนลิงก์เดียวเท่านั้น และทุกอินเทอร์เฟซสร้างขึ้นจากตัวระบุของตัวเอง นี่คือเหตุผลที่เครื่องสองเครื่องที่ไม่เกี่ยวข้องกันบนเครือข่ายสองเครือข่ายที่ไม่เกี่ยวข้องกันตอบ fe80::1 ได้ทั้งคู่โดยไม่มีการชนกันแม้แต่น้อย คำนำหน้าจึงพูดเพียงว่า “บนลิงก์ใดลิงก์หนึ่ง” ส่วนลิงก์เองมาจากบริบท
บนโฮสต์ บริบทนั้นคือโซน หรือ scope ID ที่คุณต่อท้ายหลังเครื่องหมายเปอร์เซ็นต์: fe80::1%eth0 บน Linux, fe80::1%3 หรือ fe80::1%12 บน Windows และในเครื่องมือที่แสดงดัชนีอินเทอร์เฟซ เคอร์เนลจะแปลงที่อยู่พร้อมดัชนีอินเทอร์เฟซแล้วจึงส่ง ถ้าลบโซนออก ปลายทางจะกำกวม สแต็กจึงไม่มีทางเลือกเซกเมนต์
ทำไมทันเนลแบบกำหนดเส้นทางจึงส่งมันไม่ได้
VPN แบบกำหนดเส้นทางเป็นอินเทอร์เฟซแบบจุดต่อจุดที่พาที่อยู่ของตัวเอง โดยปกติเป็น /64 ของพื้นที่ IPv6 แบบ unique-local หรือส่วนตัวภายในทันเนล LAN ระยะไกลเป็นลิงก์แยกต่างหาก ซึ่งเข้าถึงได้โดยปลายทันเนลที่กำหนดเส้นทางและส่งต่อแทนคุณ ทั้งสองเป็นเซกเมนต์ที่ต่างกัน ที่อยู่จากอันหนึ่งจึงไม่ใช่ปลายทางที่ใช้ได้บนอีกอัน
กฎการส่งต่อระบุชัดในมาตรฐาน ไม่ใช่ความแปลกของไคลเอนต์คุณ: เราเตอร์ไม่ส่งต่อแพ็กเก็ตที่มีต้นทางหรือปลายทางเป็น link-local นี่คือเหตุผลที่แพ็กเก็ตตายแม้ตารางเส้นทางจะดูถูกต้อง ถ้าเพิ่มเส้นทางสำหรับ fe80::/64 ไปยังทันเนล สแต็กที่ทำตามมาตรฐานจะปฏิเสธหรือยอมรับแล้วทิ้งทุกแพ็กเก็ตที่ตรงกัน เพราะขอบเขตปลายทางบอกว่า “เฉพาะลิงก์นี้” และอินเทอร์เฟซขาออกไม่ใช่ลิงก์นั้น
การค้นหาเพื่อนบ้านตอกย้ำขอบเขตเดียวกัน โฮสต์แปลงฮอปถัดไปแบบ link-local ด้วยคำขอเพื่อนบ้านที่ส่งไปยัง multicast ขอบเขตลิงก์โดยมีขีดจำกัดฮอป 255 เราเตอร์จึงปฏิเสธสิ่งที่ถูกส่งต่อได้ การทำให้สิ่งนี้ทำงานข้ามทันเนลหมายถึงการพร็อกซีการค้นหาสำหรับเซกเมนต์ระยะไกล ซึ่งคือบริดจ์ Layer 2 ในหมวกอีกใบ
มีความล้มเหลวที่สองที่เงียบกว่า และมันจับคนที่ยังพยายามบังคับเส้นทาง: โซน โซนใดก็ตามที่คุณพิมพ์ในเครื่องจะระบุอินเทอร์เฟซของทันเนล ขณะที่ที่อยู่ที่คุณต้องการผูกกับอินเทอร์เฟซ LAN ระยะไกล ทั้งสองไม่เคยตรงกัน แพ็กเก็ตจึงถูกบังคับไปยังอินเทอร์เฟซที่ถูกต้องที่ปลายไกลไม่ได้
ความล้มเหลวหน้าตาเป็นอย่างไรในทางปฏิบัติ
เอาต์พุตของเครื่องมือเมื่อเกิดข้อผิดพลาดด้านขอบเขตอ่านเหมือนข้อผิดพลาดการกำหนดเส้นทางหรือไฟร์วอลล์ และนั่นคือเหตุผลที่ผู้คนใช้เวลาครึ่งวันผิดเลเยอร์
| What you run | What you see | What it means |
|---|---|---|
| ping fe80::1 with no zone | Invalid argument on Linux, or a transmit failure on Windows | The stack cannot pick an interface for an ambiguous destination |
| ping -6 -I tun0 fe80::1 | Destination unreachable, or a silent timeout | The address is not on the tunnel link, so there is nothing on-link to answer |
| ssh user@fe80::1 after the tunnel is up | Hangs, then no route to host | The SYN never left the machine in a deliverable form; the server logs nothing |
| ip -6 neigh show dev tun0 | Empty table | No neighbor entry can exist for a link-local host that is not on this link |
| fe80::/64 inside AllowedIPs or route-ipv6 | Config accepted, traffic still dropped | The tunnel carried the packet; the receiving stack refused to forward it |
แถวที่สามเสียเวลามากที่สุด เพราะไคลเอนต์ ssh ที่ไม่มีเอาต์พุตดูเหมือนปัญหาเซอร์วิสที่กำลังฟังอยู่ ถ้าคุณมีทางเข้าใด ๆ ให้ตรวจฝั่งไกลและยืนยันว่า sshd ผูกกับทุกที่อยู่หรือเพียงที่เดียว
ใช้ที่อยู่ที่ถูกออกแบบมาให้กำหนดเส้นทางได้
สำหรับทุกสิ่งที่คุณต้องเข้าถึงผ่านทันเนล ให้ใช้ที่อยู่ที่เซอร์วิสได้รับมาเพื่อจุดประสงค์นั้นโดยเฉพาะ: ที่อยู่ unicast ทั่วโลก หรือ ที่อยู่ unique-local จาก fc00::/7 ซึ่งเป็นช่วงที่สงวนไว้สำหรับกรณีภายในนี้โดยเฉพาะ ยืนยันว่าตัวรับฟังผูกมันจริง และตรวจเส้นทางขากลับกับกฎไฟร์วอลล์ของโฮสต์
ดูโฮสต์ระยะไกลแทนการเดา ip -6 addr show แสดงที่อยู่ที่มีอยู่ และ ss -6 -ltn แสดงว่าเดมอนฟังอะไรอยู่ เซอร์วิสที่ผูกกับ fe80:: เท่านั้นเข้าถึงได้จากเซกเมนต์ของตัวเองเท่านั้น ซึ่งมักเป็นค่าดีฟอลต์ที่ไม่มีใครเลือกโดยเจตนาและเปลี่ยนได้เมื่อผู้ดูแลเห็นชอบ
เมื่อเซอร์วิสผูกใหม่ไม่ได้จริง ๆ ให้ยกบริดจ์ขึ้นไปที่เลเยอร์แอปพลิเคชันแทนเลเยอร์เครือข่าย รีเลย์บนโฮสต์ระยะไกลที่ฟังบนที่อยู่ที่กำหนดเส้นทางได้และเชื่อมต่อไปยังที่อยู่ link-local แก้ปัญหาได้โดยไม่ต้องขยายเซกเมนต์ใด ๆ:
socat TCP6-LISTEN:8080,bind=[fd00:1:2::10] TCP6:[fe80::1%eth0]:80ที่นี่โซนอยู่ฝั่งที่ถูกต้อง บนโฮสต์ที่มี eth0 บนลิงก์นั้นจริง ไคลเอนต์ของคุณคุยกับ fd00:1:2::10 ซึ่งกำหนดเส้นทางได้ตามปกติ และรีเลย์ทำฮอป link-local ในเครื่อง กลอุบายเดียวกันใช้ได้กับเว็บ UI ของอุปกรณ์หรือ BMC เก่าที่ประกาศเฉพาะที่อยู่ link-local
เมื่อบริดจ์เป็นคำตอบจริง ๆ และสิ่งทีต้องตรวจ
บางการติดตั้งต้องการเซกเมนต์ร่วมกันจริง ๆ: อุปกรณ์ที่พูดเฉพาะการค้นหา link-local อุปกรณ์ที่พึ่งพา multicast ห้องแล็บที่จำลอง LAN แบนราบ นี่เป็นปัญหา Layer 2 และต้องใช้คำตอบ Layer 2 เช่นอุปกรณ์ tap ที่บริดจ์ไปยังอินเทอร์เฟซระยะไกล หรือผลิตภัณฑ์ไคลเอนต์ที่มีโหมดบริดจ์ชัดเจน สิ่งนี้มีต้นทุนเป็นทราฟฟิก broadcast และ multicast ที่ข้ามทันเนลบวกการค้นหาเพื่อนบ้านที่ต้องทำงานตั้งแต่ต้นจนจบ
อย่าเข้าใกล้ปัญหานี้ด้วยการเพิ่ม fe80::/64 ลงในโปรไฟล์แบบกำหนดเส้นทาง นั่นสร้างสภาวะที่แย่ที่สุด: การกำหนดค่าดูเสร็จแล้ว ตารางเส้นทางแสดงรายการ และทุกแพ็กเก็ตยังคงถูกทิ้งที่ใดที่หนึ่งระหว่างสแตกสองอันที่ทำงานถูกต้องทั้งคู่
ตรวจสอบด้วยอินเทอร์เฟซไม่ใช่ ping ip -6 addr show tun0 บอกว่าลิงก์ของทันเนลสร้างที่อยู่ link-local ของตัวเองหรือไม่ และ ip -6 route get fe80::1%tun0 บอกว่าเคอร์เนลจะทำอะไรจริงกับปลายทางที่มีขอบเขต เมื่อเคอร์เนลบอกว่าปลายทางเข้าถึงไม่ได้บนอินเทอร์เฟซนั้น ไม่มีการตั้งค่าทันเนลใดเปลี่ยนได้ และถึงเวลาหยุดแก้ไขโปรไฟล์
ลำดับสั้น ๆ ที่ยุติคำถาม:
- อ่านที่อยู่บนโฮสต์ระยะไกลและสังเกตว่ามันเป็น fe80:: หรือคำนำหน้าที่กำหนดเส้นทางได้
- ถ้าเป็น fe80:: ให้ผูกเซอร์วิสใหม่ ทำรีเลย์บนโฮสต์นั้น หรือบริดจ์เซกเมนต์ ถ้ากำหนดเส้นทางได้ ให้แก้เส้นทาง ไฟร์วอลล์ หรือที่อยู่ผูก
- ตรวจโซนใหม่ทุกครั้งที่ทดสอบซ้ำ เพราะคำสั่งที่คัดลอกมักคงอินเทอร์เฟซที่ผิดไว้
แหล่งข้อมูล: RFC 4291, สถาปัตยกรรมที่อยู่ IPv6.