การผูกโปรแกรมเดียวเข้ากับ VPN บน Linux เป็นปัญหาการกำหนดเส้นทางเป็นหลัก และกฎไฟร์วอลล์ตัดสินเพียงว่าเกิดอะไรขึ้นหลังเลือกเส้นทางแล้ว หากคุณจับคู่ UID ของโปรแกรมใน OUTPUT ก่อนที่อุโมงค์จะถือเส้นทางเริ่มต้น กฎจะทิ้งทราฟฟิกที่คุณต้องการปกป้องไปเลย กฎเดียวกันยังมองไม่เห็นโปรเซสตัวแก้ชื่อที่ตอบคำถาม DNS ของโปรแกรม จัดลำดับให้ถูก แล้วบัญชีเดียวที่ไม่มีสิทธิพิเศษจะกลายเป็นตัวเลือกอุโมงค์แยกที่น่าเชื่อถือ

ให้เส้นทางแก่บัญชีก่อนตัดสิน

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

ให้บัญชีผ่านตารางของตัวเองก่อน แล้วปล่อยให้ไฟร์วอลล์ยืนยันผลลัพธ์ วิธีนี้ใช้ได้บนโฮสต์ที่ iproute2 เข้าใจ uidrange ซึ่ง ip rule help รายงานในบรรทัดเดียว ตาราง 100 เก็บเส้นทางเริ่มต้นผ่านอุโมงค์ และกฎส่งเฉพาะ UID 1000 ไปยังตารางนั้น ทุกอย่างอื่นยังอยู่ในตารางหลักและใช้ลิงก์จริงต่อไป

ip rule add uidrange 1000-1000 table 100
ip route add default dev tun0 table 100

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

ทำไมข้อยกเว้น loopback จึงฆ่าอุโมงค์

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

เมื่อ OUTPUT ทำงาน เคอร์เนลได้เลือกอินเทอร์เฟซขาออกแล้ว ดังนั้น -o จึงถูกนำไปเทียบกับคำตอบของเส้นทาง ไม่ใช่หัวแพ็กเก็ต การจับคู่เจ้าของ ถูกพิจารณาเฉพาะใน OUTPUT และ POSTROUTING และแพ็กเก็ตที่ส่งต่อไม่มีซ็อกเก็ตให้อ่าน การรวมกันนี้หมายความว่าข้อยกเว้นต้องระบุอินเทอร์เฟซของอุโมงค์และปฏิเสธการจับคู่นั้น ไม่ใช่ปฏิเสธ loopback

loopback ยังคงสมควรได้รับ ACCEPT ของตัวเองเหนือ DROP เพราะแพ็กเก็ต DNS แรกจากโปรแกรมไปยัง stub ภายในผ่าน lo วางกฎสองข้อตามลำดับนี้ แล้วบัญชีจะไปถึง stub จากนั้นจึงไปถึงอุโมงค์ และไม่มีที่อื่น

iptables -I OUTPUT 1 -o lo -m owner --uid-owner vpnapp -j ACCEPT
iptables -A OUTPUT -m owner --uid-owner vpnapp ! -o tun0 -j DROP

IPv6 เป็นชุดกฎแยก และคู่ที่ทำเพื่อ v4 เท่านั้นจะปล่อย ip6tables ไว้ไม่แตะต้อง โปรแกรมที่ชอบระเบียน AAAA จะออกผ่านลิงก์จริงโดยไม่เคยไปถึง DROP สะท้อนทั้งสองกฎใน ip6tables หรือปิด IPv6 บนโฮสต์เมื่ออุโมงค์ไม่ได้พาแอดเดรส v6 ใด ๆ กฎที่เพิ่มด้วยมือก็หายไปเมื่อรีบูตเช่นกัน ดังนั้นบันทึกด้วย netfilter-persistent save หรือสร้างตรรกะเดิมใหม่เป็นตาราง nftables ที่ยูนิตบูตโหลด

คำตอบ DNS มาจากอีกโปรเซสหนึ่ง

คำถามของโปรแกรมไปยัง 127.0.0.53 ผ่าน ACCEPT ของ loopback และตรงนั้นการจับคู่ UID ก็หยุด ตัวแก้ชื่อ stub ส่งต่อขึ้นบนจากบัญชีบริการของตัวเอง ดังนั้นกฎที่ผูกกับ vpnapp จะไม่เห็นคำถามที่สองเลย หากตัวแก้ชื่อเลือกเซิร์ฟเวอร์ LAN สำหรับลิงก์ คำตอบจะกลับมาผ่าน NIC จริง ขณะที่แพ็กเก็ตของโปรแกรมเองยังอยู่ในอุโมงค์ ผลลัพธ์ที่มองเห็นคืออุโมงค์ที่ทำงานได้แต่ชื่อเสีย หรือชื่อที่ตอบโดยเซิร์ฟเวอร์ที่คุณไม่ได้เลือก

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

ตรวจสอบกฎจากบัญชีเอง

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

ip route get 1.1.1.1 uid 1000
sudo -u vpnapp curl -s https://ifconfig.co

ตัวนับแสดงว่ากฎเคยถูกกระตุ้นหรือไม่ เรียกใช้ iptables -vnL OUTPUT อ่านจำนวนแพ็กเก็ตข้างกฎแต่ละข้อ และถือว่าศูนย์ข้าง DROP หลังทราฟฟิกจริงเป็นความผิดพลาดของการจับคู่

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

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