การข้าม VPN สำหรับพอร์ตเดียวต้องใช้ policy routing
กฎ NAT เขียนที่อยู่ใหม่ แต่ไม่ได้เลือกเส้นทางขาออก นี่คือกฎ ตาราง และมาร์กที่เก็บพอร์ต TCP หนึ่งพอร์ตไว้นอกอุโมงค์จริง ๆ
คำขอเป็นรูปแบบเดียวกันเสมอ: เก็บ VPN ไว้สำหรับท่องเว็บ แต่ ให้ SSH หรืองานสำรองข้อมูลใช้การเชื่อมต่อปกติ. คำแนะนำที่แพร่หลายเรื่องนี้ คือการส่งต่อพอร์ตด้วยกฎ NAT ของ iptables ไม่ได้ทำอย่างนั้น กฎ NAT เขียนที่อยู่และพอร์ตใหม่ภายในโฟลว์ มันไม่เคยเลือกว่าอินเทอร์เฟซใดที่แพ็กเก็ตออกไป การตัดสินใจนั้นเป็นของการค้นหาเส้นทาง และการค้นหาเส้นทางต้องได้รับความช่วยเหลือเพื่อให้เห็นพอร์ต
ทำไมเส้นทางธรรมดาจับคู่พอร์ตไม่ได้
ลินุกซ์เลือกเส้นทางโดยค้นหาที่อยู่ปลายทางในตาราง หลังจากกรองตารางที่เป็นตัวเลือกด้วยกฎนโยบาย กฎเหล่านั้นจับคู่ได้กับมาร์กไฟร์วอลล์ ที่อยู่ต้นทาง อินเทอร์เฟซขาเข้าหรือ UID รายการเส้นทางเองไม่มีฟิลด์พอร์ต เพราะเส้นทางถูกเลือกก่อนที่ส่วนหัวของทรานสปอร์ตจะมีความหมาย
iproute2 และเคอร์เนลปัจจุบันขยายตัวเลือกกฎ: ipproto, sport และ dport ได้รับการยอมรับ ดังนั้น ip rule add ipproto tcp dport 22 table 100 แสดงเจตนาได้ในบรรทัดเดียว รัน ip rule add help บนโฮสต์จริงเพื่อดูว่าบิลด์ของคุณระบุ dport หรือไม่ ถ้าระบุ ก็จบด้วยคำสั่งเดียว ถ้าไม่ระบุ เส้นทางผ่านมาร์กด้านล่างคือสิ่งที่เทียบเท่า
ไม่ว่าอย่างไร กฎเป็นเพียงตัวชี้ ตาราง 100 ยังต้องมีเส้นทาง และนั่นคือส่วนที่คนลืม
สามชิ้น หรือหนึ่ง
รูปแบบสมัยใหม่คือกฎเดียวข้างต้นบวกเส้นทางในตารางทางเลือก รูปแบบคลาสสิกเพิ่ม netfilter ไว้ตรงกลาง: ทำมาร์กแพ็กเก็ต แล้วจับคู่มาร์กในกฎ ทั้งสองจบที่การค้นหาเดียวกัน ต่างกันที่จุดที่อ่านพอร์ต
วิธีทำมาร์กมีอยู่เพราะ netfilter เห็นส่วนหัวของทรานสปอร์ต และโค้ดจัดเส้นทางในอดีตไม่เห็น นั่นคือกลไกทั้งหมด และอธิบายว่าทำไมทางสำรองจึงเป็นสามชิ้นที่ทำงานร่วมกัน ไม่ใช่คำสั่งเดียวที่ฉลาด
สามชิ้นตามลำดับ
ก่อนอื่น ทำมาร์กโฟลว์ที่ต้องการเบี่ยง ในเชนที่ทำงานเร็วพอให้มาร์กมีอยู่ก่อนการตัดสินใจเรื่องเส้นทาง:
nft add table inet vpnexempt
nft 'add chain inet vpnexempt out { type route hook output priority mangle; }'
nft add rule inet vpnexempt out tcp dport 22 meta mark set 0x1ที่สอง ส่งแพ็กเก็ตที่ทำมาร์กแล้วไปยังตารางแยกต่างหาก:
ip rule add fwmark 0x1/0x1 table 100ที่สาม ให้ตารางนั้นมีเส้นทางเริ่มต้นผ่านลิงก์กายภาพ ที่อยู่เป็นเพียงตัวอย่าง แทนที่ด้วยของคุณเอง:
ip route add default via 192.168.1.1 dev wlan0 table 100ข้ามขั้นที่สามแล้วแพ็กเก็ตที่ทำมาร์กจะหาเส้นทางในตาราง 100 ไม่พบ จึงล้มเหลว อาการดูเหมือนกฎทำให้เครือข่ายพัง ทั้งที่จริงมันลบเส้นทางเดียวที่แพ็กเก็ตเหลืออยู่
อะไรทำให้ยังทำงานได้หลังแพ็กเก็ตแรก
มาร์กใช้กับแพ็กเก็ตขาออก คำตอบมาถึงอินเทอร์เฟซกายภาพพร้อมที่อยู่ที่ลิงก์กำหนด และตารางหลักอาจยังชอบอุโมงค์สำหรับปลายทางนั้น กลไกสองอย่างทำให้เส้นทางตอบกลับสอดคล้องกัน: กฎอิงต้นทางสำหรับที่อยู่ลิงก์ และ connection tracking ที่รักษาการตัดสินใจไว้ตลอดอายุของโฟลว์ การบันทึกมาร์กลง conntrack และคืนค่าเมื่อตอบกลับเป็นวิธีปกติที่ทำให้ข้อยกเว้นแบบมีสถานะคงที่
การกรองเส้นทางย้อนกลับคืออีกหนึ่งเพชฌฆาตเงียบ ถ้าเคอร์เนลตรวจสอบเส้นทางตอบกลับอย่างเข้มงวดและคำตอบมาถึงอินเทอร์เฟซที่ตารางหลักไม่เลือก แพ็กเก็ตจะถูกทิ้งโดยไม่มีข้อความ rp_filter ค่าเริ่มต้นแตกต่างกันตามดิสทริบิวชันและตามว่าอินเทอร์เฟซถูกส่งต่อหรือไม่ ดังนั้นอ่านค่าที่มีผลจริงแทนการสมมติว่าปิดอยู่
อย่าทำมาร์กทรานสปอร์ตของอุโมงค์เอง อุโมงค์ WireGuard หรือ UDP OpenVPN ไม่ใช่ TCP 22 สูตรข้างต้นจึงปลอดภัย แต่ข้อยกเว้นที่เขียนว่าทุกอย่างยกเว้นปลายทางอุโมงค์อาจจับแพ็กเก็ตที่เข้ารหัสและทำให้อุโมงค์พังในแบบที่ดูเหมือนลูปเส้นทาง
ตรวจสอบแทนการสมมติ
ถามเคอร์เนลว่าจะใช้เส้นทางใด สำหรับปลายทางและสำหรับพอร์ต แล้วจึงรันการถ่ายโอนจริง:
ip route get 203.0.113.10
ip rule show
nft list rulesetถ้ามาร์กมีอยู่แต่ตัวนับไม่ขยับเลย กฎไม่ได้จับคู่ ตัวนับคือความต่างระหว่างคอนฟิกที่คุณเขียนกับนโยบายที่เคอร์เนลใช้จริง
เมื่อนี่เป็นเครื่องมือผิด
ข้อยกเว้นตามพอร์ตเปราะบางโดยธรรมชาติ บริการย้ายพอร์ต HTTP/3 มาเป็น UDP 443 และแอปเดียวอาจเปิดหลายโฟลว์ต่างชนิด ถ้าความต้องการจริงคือโปรแกรมนี้ใช้ลิงก์ปกติ ชั้นจัดเส้นทางคือที่ผิด เนมสเปซเครือข่ายหรือพร็อกซีต่อแอปเหมาะกว่า
ถ้าความต้องการคือซับเน็ตนี้ใช้ลิงก์ปกติ ข้ามมาร์กทั้งหมดและเพิ่มเส้นทางที่เฉพาะเจาะจงกว่า ง่ายกว่า มองเห็นได้ในตารางเดียว และคนถัดไปตรวจสอบได้โดยไม่ต้องอ่านชุดกฎ
เขียนว่าคุณมีความต้องการข้อใดในสามข้อจริง ๆ ก่อนแตะไฟร์วอลล์ กลไกข้างต้นแม่นยำ และความแม่นยำที่ใช้กับชั้นผิดก็แค่ดาวน์ไทม์ที่ยาวขึ้น