ฤดูใบไม้ผลินี้ผมใช้เวลาเกือบทั้งเช้าวันหนึ่งไล่ตามคอนเทนเนอร์ที่แก้ชื่อภายในไม่ได้แม้แต่ชื่อเดียว ขณะที่เบราว์เซอร์ข้างๆ เปิดวิกิได้โดยไม่มีปัญหา อุโมงค์เชื่อมต่ออยู่ เบราว์เซอร์พิสูจน์แล้ว และคอนเทนเนอร์อยู่ในเนมสเปซเครือข่ายอื่นที่มีตารางเส้นทางอื่นและรีโซลเวอร์อื่น ต่อไปนี้คือลำดับที่ผมตรวจตอนนี้ ตารางเส้นทางก่อน การแก้ชื่อเป็นลำดับที่สอง การส่งต่อเป็นลำดับที่สาม และแพลตฟอร์มโฮสต์เป็นลำดับสุดท้าย หยุดที่ขั้นตอนแรกที่ล้มเหลวอยู่แล้ว เพราะการเพิ่มกฎในชั้นล่างลงไปจะซ่อนขอบเขตแทนที่จะย้ายมัน

เปรียบเทียบตารางเส้นทางทั้งสามก่อนเปลี่ยนกฎ

คอนเทนเนอร์บนบริดจ์เริ่มต้นของ Docker มีเส้นทางเริ่มต้นเส้นเดียวชี้ไปที่เกตเวย์ docker0 และไม่รู้ว่ามี tun0 อยู่ โฮสต์มี tun0 มีเส้นทางที่ VPN ดันเข้ามา และอาจมีกฎนโยบายที่ส่งทราฟฟิกทดสอบออกทางอินเทอร์เฟซอื่นจากที่คุณคาดไว้ WSL เพิ่มตารางที่สามไว้หลังอะแดปเตอร์เสมือนของตัวเอง พิมพ์ทั้งสามก่อนแก้ไขอันใด

ContextCommandWhat a wrong answer looks like
Hostip route get 10.8.0.20Leaves a physical interface instead of tun0
Containerip routeOnly 172.17.0.0/16 via 172.17.0.1
Container DNScat /etc/resolv.confA public resolver, or an upstream the tunnel never pushed
WSLip addr; ip routeOnly the WSL subnet, no Windows VPN adapter

ถ้าเส้นทางเดียวของคอนเทนเนอร์คือบริดจ์ และ tun0 ไม่เคยปรากฏในเนมสเปซของมัน จะไม่มีอะไรด้านล่างช่วยได้ การเปลี่ยนเนมเซิร์ฟเวอร์ไม่สร้างเส้นทาง และไม่มีเส้นทางใดสร้างตัวตนอุโมงค์ที่เพียร์จะยอมรับ ลำดับสำคัญเพราะความล้มเหลวของ DNS และความล้มเหลวของเส้นทางดูเหมือนกันเมื่อมองจากในเชลล์

ชี้รีโซลเวอร์ไปที่อุโมงค์แทนที่จะเป็นตัวสาธารณะ

บนโฮสต์ resolvectl status แสดงรายการเซิร์ฟเวอร์ DNS ต่อลิงก์ และ readlink -f /etc/resolv.conf บอกว่าไฟล์เป็นสตับของ systemd ไฟล์ NetworkManager หรือสิ่งที่ไคลเอนต์ของผู้จำหน่ายเขียนใหม่ ภายในคอนเทนเนอร์บนเครือข่ายที่ผู้ใช้กำหนด /etc/resolv.conf ระบุชื่อ 127.0.0.11 ซึ่งเป็นรีโซลเวอร์ในตัวของ Docker ที่ส่งต่อไปยังอัปสตรีมที่เดมอนถูกกำหนดไว้ ส่วนคอนเทนเนอร์บนบริดจ์เริ่มต้นกลับได้สำเนาไฟล์ของโฮสต์ตอนสร้างและเก็บไว้

สำเนานั้นคือสาเหตุที่อุโมงค์ซึ่งขึ้นหลังคอนเทนเนอร์เริ่มทำงานทำให้คอนเทนเนอร์ยังถามอัปสตรีมเก่า จะส่งรีโซลเวอร์ของ VPN อย่างชัดเจนด้วย docker run --dns 10.8.0.53 หรือตั้งค่า "dns": ["10.8.0.53"] ใน /etc/docker/daemon.json แล้วรีสตาร์ตเดมอน หรือสร้างคอนเทนเนอร์ใหม่เมื่ออุโมงค์ขึ้นแล้ว การสร้างใหม่มักถูกกว่าการดีบักรีโซลเวอร์ที่ถูกต้องเมื่อชั่วโมงก่อน

การเปลี่ยนไปใช้รีโซลเวอร์สาธารณะคือการซ่อมที่รักษาอาการไว้ มันแก้ชื่ออินเทอร์เน็ตได้แต่ล้มเหลวในทุกโซนส่วนตัว และอาจข้ามการกรองที่อุโมงค์มีไว้ได้อย่างเงียบๆ ถ้าคุณพยายามเขียนภายใน /etc/resolv.conf ของคอนเทนเนอร์ที่ทำงานอยู่ ให้คาดว่า Device or resource busy เนื่องจาก Docker ทำ bind-mount ไฟล์นั้น การแก้ไขจึงต้องถอดเมานต์ก่อน การตั้งค่า --dns ตอนสร้างคือลำดับที่อยู่รอดหลังรีสตาร์ต

ส่งต่อและแปลงเมื่อคอนเทนเนอร์ยืมอุโมงค์

ด้วยบริดจ์เริ่มต้น โฮสต์เป็นเราเตอร์ระหว่าง docker0 กับ tun0 และโดยปริยายไม่ได้ทำงานทั้งสองอย่าง เปิดการส่งต่อและแปลงแอดเดรสต้นทางเพื่อให้เพียร์เห็นแอดเดรสอุโมงค์ของโฮสต์แทนแอดเดรสที่ไม่มีเส้นทางกลับ (แทน tun0 ด้วยชื่อที่ไคลเอนต์ของคุณตั้งให้อินเทอร์เฟซ):

sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -o tun0 -j MASQUERADE
iptables -A FORWARD -i docker0 -o tun0 -j ACCEPT
iptables -A FORWARD -i tun0 -o docker0 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

กฎ FORWARD สำคัญบนโฮสต์ที่มีนโยบายส่งต่อเป็น DROP Docker แทรกเชนของตัวเอง ดังนั้นให้ทดสอบด้วยแพ็กเก็ตแทนการอ่าน iptables -S: จากคอนเทนเนอร์ ip route get 10.8.0.20 ควรระบุเกตเวย์ของบริดจ์ และ nc -vz 10.8.0.20 443 ควรเชื่อมต่อหรือหมดเวลาที่อุโมงค์ไม่ใช่ที่รีโซลเวอร์

ถ้าไม่มี MASQUERADE เพียร์ต้องมีเส้นทางกลับไปยัง 172.17.0.0/16 ซึ่ง VPN สำหรับการเข้าถึงระยะไกลส่วนใหญ่ไม่ดันเข้ามา นี่คือจุดที่ไคลเอนต์ที่อิงนโยบายทำให้แผนพัง หากมันดักจับเฉพาะทราฟฟิกจาก UID หรือ cgroups ที่ระบุ กฎส่งต่อของคุณจะไม่เห็นแพ็กเก็ตภายในอุโมงค์เลย และการแก้ไขอยู่ที่นโยบายการเราต์ของไคลเอนต์ไม่ใช่ iptables กฎที่เพิ่มด้วยมือจะหายเมื่อรีบูต ดังนั้นใส่ไว้ในยูนิต systemd หรือตาราง netfilter-persistent แล้วทดสอบใหม่หลังรีบูต เพราะอุโมงค์ที่ขึ้นช้าอาจกวาดกฎเหล่านั้นทิ้ง

วางอุโมงค์ไว้ในคอนเทนเนอร์เมื่อเป้าหมายคือการแยก

ถ้าคอนเทนเนอร์หนึ่งต้องออกผ่านโหนดที่ระบุ ให้รันไคลเอนต์ในคอนเทนเนอร์นั้น มันต้องใช้ --cap-add NET_ADMIN และ --device /dev/net/tun จากนั้นจึงมี tun0 เส้นทาง และไฟร์วอลล์ของตัวเอง ถ้าคอนเทนเนอร์อื่นผ่านมันไป มันก็ต้องมีการส่งต่อและกฎ MASQUERADE ภายในคอนเทนเนอร์ด้วย และเพียร์เหล่านั้นต้องมีเส้นทางหรือพร็อกซีที่ชี้มาที่มันอย่างชัดเจน บริดจ์เริ่มต้นของ Docker ไม่เพิ่มเส้นทางนั้นให้คุณ

เครือข่ายของโฮสต์ docker run --network host คือเวอร์ชันราคาถูก คอนเทนเนอร์เข้าร่วมเนมสเปซของโฮสต์และเห็นเส้นทางอุโมงค์โดยตรง แต่มันก็ทิ้งการแยกเครือข่าย ชวนให้พอร์ตชนกัน และในทางปฏิบัติเป็นตัวเลือกของ Linux พอร์ตที่เผยแพร่ไม่ใช่ตัวแทน -p 8080:80 ทำให้บริการเข้าถึงได้จากโฮสต์ มันไม่ได้ทำให้แอดเดรสภายใน 10.x เข้าถึงได้จากในคอนเทนเนอร์ และการสับสนสองอย่างนี้คือทางที่ปัญหาการเราต์กลายเป็นพอร์ตฐานข้อมูลที่เปิดเผย

แยกความล้มเหลวของชื่อออกจากซับเน็ตที่ทับซ้อน

บริดจ์เริ่มต้นของ Docker คือ 172.17.0.0/16 และพูล VPN องค์กรหลายแห่งอยู่ใน 172.16.0.0/12 เมื่ออุโมงค์ครอบช่วงนั้น ทราฟฟิกที่ตั้งใจให้คอนเทนเนอร์อาจถูกเราต์เข้าอุโมงค์แทน ชื่อล้มเหลว ข้อผิดพลาดดูเหมือน DNS และปัญหาอยู่ที่แอดเดรส ตรวจปลายทางไม่ใช่รีโซลเวอร์: ip route get 172.17.0.5 บนโฮสต์ แล้วเปรียบเทียบพรีฟิกซ์ของบริดจ์กับพูล VPN และกับ docker network inspect bridge --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'.

การซ่อมคือย้ายช่วงใดช่วงหนึ่ง ไม่ว่าจะเป็นพูลแอดเดรสเริ่มต้นอื่นสำหรับ Docker ใน daemon.json หรือพูลอื่นบนคอนเซนเทรเตอร์ การเพิ่มเนมเซิร์ฟเวอร์อีกตัวไม่ช่วยอะไรที่นี่ และ ssh -b ก็ไม่ช่วยเช่นกัน การผูกไคลเอนต์กับอินเทอร์เฟซเปลี่ยนแอดเดรสต้นทาง ไม่ใช่เส้นทางปลายทางที่ชี้เข้าอุโมงค์ การแบ่งงานเร็วๆ คือ ชื่อที่แก้ได้บนโฮสต์แต่ไม่ได้ในคอนเทนเนอร์เป็นปัญหาของรีโซลเวอร์ ขณะที่การเชื่อมต่อ IP ตรงที่ค้างในคอนเทนเนอร์แต่ทำงานบนโฮสต์เป็นปัญหาของเส้นทางหรือเชนส่งต่อ

WSL และ Docker Desktop ไม่ทำตามกฎของ Linux

ในโหมด NAT เริ่มต้น WSL อยู่หลังอะแดปเตอร์เสมือนของตัวเองและไม่ได้รับเส้นทางภายในหรือซัฟฟิกซ์ DNS ของ VPN Windows นั่นคือเหตุผลที่เบราว์เซอร์ทำงานได้ และ ping ภายในดิสทริบิวชันไม่ทำงาน เครือข่ายแบบกระจกที่มีในบิลด์ Windows 11 ล่าสุดจะแชร์อินเทอร์เฟซของโฮสต์แทน แต่ต้องเปิดใน .wslconfig และรีสตาร์ทดิสทริบิวชันด้วย wsl --shutdown ยืนยันโหมดที่ใช้งานด้วย wslinfo --networking-mode ก่อนใช้การซ่อม เพราะคำตอบของ NAT ผิดสำหรับโหมดกระจก และกลับกัน

Docker Desktop เพิ่มอีกชั้น คอนเทนเนอร์ Linux บน Windows อยู่หลังการทำงาน NAT ที่ในอดีตเรียกว่า vpnkit และเส้นทางต่างจากโฮสต์ Linux ปกติ คอนเทนเนอร์ Windows เชื่อมผ่านสวิตช์เสมือนของ Hyper-V และไคลเอนต์องค์กรอาจบล็อกทราฟฟิกจากสวิตช์นั้นได้ ดังนั้นคอนเทนเนอร์ Linux บนเครื่องเดียวกันอาจเข้าถึงเครือข่ายได้ขณะที่คอนเทนเนอร์ Windows ไม่มีอะไรเลย สวิตช์ภายนอกที่ผูกกับอะแดปเตอร์จริงเป็นวิธีแก้ที่ใช้กันทั่วไป และมันเปลี่ยนตัวตนเครือข่ายของคอนเทนเนอร์ในแบบที่นโยบายอาจไม่อนุญาต

ก่อนเขียนกฎอีกข้อ ให้เดินผ่านทั้งสี่ชั้นตามลำดับและหยุดตรงที่ชั้นใดชั้นหนึ่งผิด:

  • ในคอนเทนเนอร์: ip route ไปถึงพรีฟิกซ์ส่วนตัวหรือไม่ และ /etc/resolv.conf ระบุอัปสตรีมที่แก้ชื่อนั้นหรือไม่
  • บนโฮสต์: การส่งต่อเปิดอยู่หรือไม่ NAT ผูกกับอินเทอร์เฟซอุโมงค์หรือไม่ และเชนส่งต่อยอมรับจากบริดจ์ไปอุโมงค์หรือไม่
  • ที่เพียร์: มันรู้เส้นทางกลับหรือไม่ หรือคุณพึ่ง NAT เพื่อซ่อนซับเน็ตของคอนเทนเนอร์
  • บนแพลตฟอร์ม: WSL อยู่ในโหมด NAT หรือกระจก และคอนเทนเนอร์เป็น Linux หรือ Windows

รันทุกการทดสอบใหม่ในคอนเทนเนอร์หรือเชลล์ใหม่ กระบวนการที่รันนานซึ่งมีคำตอบแคชหรือการเชื่อมต่อที่ตั้งไว้แล้วจะยังล้มเหลวต่อไปหลังการตั้งค่าถูกต้อง และความล้มเหลวนั้นดูเหมือนการซ่อมที่ไม่ทำงานทุกประการ

บทความที่เกี่ยวข้อง