แพ็กเก็ตของคอนเทนเนอร์ไม่เคยไปถึง tun0 และความสามารถเพิ่มเติมบนคอนเทนเนอร์จะไม่เปลี่ยนสิ่งนั้น ระหว่างเกสต์กับอุโมงค์มีการค้นหาเส้นทางสองครั้ง ครั้งหนึ่งในเนมสเปซเครือข่ายของเกสต์ อีกครั้งบนโฮสต์หลังจากเฟรมลงบนบริดจ์ และครั้งที่สองสอบถามตารางอีกตารางที่มีกฎการจับคู่ต่างกัน โครงอ้างอิงของผมคือโฮสต์ Linux ที่รัน OpenVPN ไคลเอนต์ซึ่งถือ 10.8.0.10 บน tun0 มีบริดจ์ Docker บน 172.18.0.0/16 และที่อยู่ส่วนตัวที่เข้าถึงได้เฉพาะฝั่งอุโมงค์ เป้าหมายยังคงแคบ ซับเน็ตของเกสต์นั้นออกผ่านอุโมงค์ ในขณะที่โฮสต์เก็บอัปลิงก์ปกติไว้

การรันไคลเอนต์ VPN ภายในคอนเทนเนอร์เป็นการออกแบบอีกแบบ ไม่ใช่ทางลัด ตอนนั้นคอนเทนเนอร์เป็นเจ้าของอุโมงค์และข้อมูลรับรองของตัวเอง และงานย้ายไปยัง Dockerfile แทนที่จะอยู่ใน iptables

การส่งต่อเลือกเส้นทางตามปลายทาง จากนั้น NAT เขียนต้นทางใหม่

เมื่อตั้งค่า net.ipv4.ip_forward=1 ไว้ แพ็กเก็ตที่ถูกส่งต่อจะถูกกำหนดเส้นทางตามปลายทางเท่านั้น ในตารางหลัก ต้นทางไม่มีบทบาทในการตัดสินใจนั้น หลังจากการเลือกเส้นทางแล้ว เคอร์เนลจึงรัน POSTROUTING และที่นั่นคือกฎของ Docker เอง: -s 172.18.0.0/16 ! -o br-appnet -j MASQUERADE ทุกสิ่งที่ออกจากบริดจ์ไปยังอินเทอร์เฟซอื่นจะถูกแปล และที่อยู่ที่มันกลายเป็นขึ้นอยู่กับอินเทอร์เฟซที่เส้นทางส่งมันไป

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

ขอการค้นหาที่ระบุต้นทางของเกสต์ หากไม่มี from และ iif โฮสต์จะตอบสำหรับซ็อกเก็ตของตัวเองและรายงานเส้นทางที่แพ็กเก็ตที่ถูกส่งต่อจะไม่ใช้เลย:

ip route get 192.168.11.222 from 172.18.0.5 iif br-appnet

ถ้าคำตอบนั้นระบุอัปลิงก์ อุโมงค์ไม่อยู่ในภาพและการตั้งค่าฝั่งอุโมงค์ใด ๆ ก็ไม่ช่วย

ย้ายเกสต์ไปยังที่อยู่ของอุโมงค์เอง

การเปลี่ยนแปลงที่เล็กที่สุดที่ได้ผลคือการเขียนต้นทางใหม่อย่างชัดเจนตอนออก ใส่เข้าไปแทนที่จะต่อท้าย Docker นั่งอยู่ที่หัวของ POSTROUTING แล้ว ดังนั้นกฎที่เพิ่มด้วย -A จะถูกประเมินหลังจากกฎของ Docker และไม่เคยทำงาน

iptables -t nat -I POSTROUTING 1 -s 172.18.0.0/16 -o tun0 -j SNAT --to-source 10.8.0.10

สองเงื่อนไขตัดสินว่าสิ่งนี้จะอยู่ได้หรือไม่ ที่อยู่ต้องเป็นที่อยู่ที่ไคลเอนต์ถืออยู่จริง ดังนั้นปักหมุดด้วยรายการต่อไคลเอนต์ที่มี ifconfig-push 10.8.0.10 255.255.255.0 กฎที่ระบุที่อยู่ที่ไม่เคยถูกกำหนดจะทำให้เกิดการทิ้งอย่างเงียบ ๆ ไม่ใช่ข้อผิดพลาด และฝั่งไกลต้องยอมรับต้นทางนั้นและส่งเส้นทางตอบกลับ นั่นคือเหตุผลที่รายการอนุญาตที่นั่นระบุที่อยู่ของอุโมงค์ไม่ใช่ซับเน็ตของเกสต์ เมื่อที่อยู่ของไคลเอนต์เป็นแบบไดนามิก -j MASQUERADE กับ -o tun0 จะตามสิ่งที่อินเทอร์เฟซมี ด้วยต้นทุนของนโยบายระยะไกลที่คุณไม่สามารถเขียนค่าคงที่ต้านได้

การกรองเป็นอีกครึ่งหนึ่ง Docker ตั้งนโยบาย FORWARD เป็น drop และล้างเชนที่มันเป็นเจ้าของทุกครั้งที่เดมอนรีสตาร์ต ดังนั้นใส่นโยบายท้องถิ่นใน DOCKER-USER ซึ่งอยู่รอด:

iptables -I DOCKER-USER -i br-appnet -o tun0 -j ACCEPT
iptables -I DOCKER-USER -i tun0 -o br-appnet -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

กฎขากลับสำคัญ คำตอบที่มาถึง tun0 สำหรับการเชื่อมต่อที่บริดจ์เปิดไว้ไม่ได้รับการยอมรับเพียงเพราะทิศทางออกได้รับการยอมรับ

ให้โฮสต์ตรงด้วยกฎต้นทาง

เมื่อบริดจ์ทั้งหมดควรใช้อุโมงค์แต่ไม่มีอย่างอื่นบนโฮสต์ควรใช้ ให้ข้าม NAT และให้ตารางของตัวเองกับต้นทางนั้น ที่อยู่ของเกสต์จะยังมองเห็นได้ตั้งแต่ต้นจนจบ

echo "200 vpnbox" >> /etc/iproute2/rt_tables
ip rule add from 172.18.0.0/16 lookup vpnbox pref 1000
ip route add default dev tun0 table vpnbox

กฎถูกสอบถามก่อนตารางหลัก และ from ตรงกับต้นทาง แพ็กเก็ตภายนอกของอุโมงค์เองพาต้นทางทางกายภาพของโฮสต์ไป ดังนั้นจึงไม่เคยตรงกับ from 172.18.0.0/16 และอุโมงค์ไม่สามารถกำหนดเส้นทางกลับเข้าตัวเองได้ การยกเว้นตัวเองนี้ทำให้โครงสร้างเสถียร

เส้นทางขากลับตอนนี้ต้องการความช่วยเหลือ ฝั่งไกลเห็น 172.18.0.x ที่ยังไม่แปลและไม่มีเหตุผลที่จะรู้ว่ามันอยู่ที่ไหน บนเซิร์ฟเวอร์ ไฟล์ต่อไคลเอนต์ต้องมี iroute 172.18.0.0 255.255.0.0 เพื่อให้ตารางภายในของเซิร์ฟเวอร์มอบคำนำหน้านั้นให้ไคลเอนต์นี้ และ route 172.18.0.0 255.255.0.0 ที่ถูกผลักไปเพื่อให้ไคลเอนต์อื่นรู้ว่ามันมีอยู่

สองการตรวจก่อนจะเชื่อถือ รัน ip route show default : ด้วย redirect-gateway def1 ตารางมี 0.0.0.0/1 และ 128.0.0.0/1 ผ่าน tun0 และโฮสต์ไม่ตรงเลย เพราะค่าเริ่มต้นครึ่งหนึ่งเหล่านั้นชนะโดยไม่แทนที่ default กฎที่เพิ่มด้วย ip rule add ก็หายไปเมื่อรีบูต ดังนั้นพกไว้ในสคริปต์ up

ขนาดแพ็กเก็ตคือความล้มเหลวที่สองที่คุณพบ

อินเทอร์เฟซบริดจ์ที่ 1500 ไบต์ให้อุโมงค์มากกว่าที่มันรับได้เมื่อเพิ่มหัว UDP และ IP ภายนอก การแลกเปลี่ยนเล็ก ๆ ผ่านไป ดังนั้น DNS จึงแก้ได้และการจับมือ TLS เสร็จสิ้น จากนั้นการอัปโหลดที่ใหญ่กว่าจะค้างโดยไม่มีข้อผิดพลาดที่ไหนเลย ผู้ส่งยังคงลองขนาดเซกเมนต์เดิมเพราะข้อความ ICMP เกี่ยวกับความจำเป็นในการแบ่งส่วนซึ่งจะบอกให้ลดขนาดไม่เคยมาถึง

แก้ที่อินเทอร์เฟซ ที่ MSS หรือทั้งสอง การหนีบไม่ต้องอาศัยความร่วมมือจากเกสต์:

ip link set dev tun0 mtu 1400
iptables -t mangle -A FORWARD -o tun0 -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --clamp-mss-to-pmtu

การหนีบเขียนใหม่เฉพาะ TCP ส่วน UDP รวมถึง QUIC ต้องลดอินเทอร์เฟซของเกสต์แทน ด้วย --opt com.docker.network.driver.mtu=1400 ตอนสร้างเครือข่ายหรือคีย์ compose ที่ตรงกัน

DNS และการตรวจที่คุ้มค่าจะทำซ้ำ

การแก้ชื่อตามเส้นทางเดียวกัน รีโซลเวอร์ที่ฝังใน Docker ที่ 127.0.0.11 ส่งต่อไปยังเซิร์ฟเวอร์ที่กำหนดค่าไว้ของโฮสต์ ดังนั้นคอนเทนเนอร์จึงสืบทอดสิ่งที่โฮสต์ใช้ เมื่อโซนส่วนตัวถูกตอบโดยรีโซลเวอร์ที่เข้าถึงได้ผ่านอุโมงค์เท่านั้น ให้ชี้เกสต์ไปที่ที่อยู่นั้น และทดสอบด้วย getent hosts ภายในคอนเทนเนอร์ไม่ใช่บนโฮสต์

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

ไล่ตามรายการนั้นและหยุดที่ความล้มเหลวแรก ชั้นนั้นคือสิ่งที่ต้องแก้ สิ่งใดที่เพิ่มลงไปด้านล่างเพียงซ่อนขอบเขตที่แตก

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