ไคลเอนต์ OpenVPN หลัง NAT กลายเป็นทางออกสู่อินเทอร์เน็ตได้อย่างไร
ทางออกเป็นฝ่ายเรียกออกไปเอง เราเตอร์จึงไม่ต้องมีพอร์ตขาเข้า ติดตามว่าฮับกำหนดเส้นทางอะไร ทางออกส่งต่ออะไร และเส้นทางขากลับมาจากไหน
โฮสต์ Linux สามเครื่อง ดีไซน์เดียว: ฮับ OpenVPN ที่เข้าถึงได้จากสาธารณะ แล็ปท็อปของผู้ใช้ และเครื่องทางออกที่อยู่หลังเราเตอร์บ้านธรรมดาซึ่งไม่ส่งต่อสิ่งใดเข้ามา ทางออกเป็นฝ่ายเรียกไปหาฮับเอง จากนั้นทราฟฟิกอินเทอร์เน็ตของผู้ใช้จะเข้าสู่ทันเนลที่แล็ปท็อปและออกผ่านลิงก์อัปสตรีมของทางออกเอง ทุกสิ่งที่น่าประหลาดใจในการจัดวางนี้มาจากรายละเอียดเดียว: ทางออกเป็นฝ่ายเริ่มการเชื่อมต่อ เส้นทางขาเข้าจึงมีอยู่แล้วและไม่ต้องทำพอร์ตฟอร์เวิร์ดเพื่อสร้างมันขึ้นมา
ส่วนต่อไปนี้ติดตามเส้นทางของแพ็กเก็ต ไม่ใช่ลำดับการกำหนดค่า เพราะการกำหนดค่าจะมีความหมายก็ต่อเมื่อรู้ว่าองค์ประกอบใดเป็นเจ้าของการตัดสินใจใด แต่ละชั้นเป็นเจ้าของการตัดสินใจเพียงหนึ่งอย่าง และข้อผิดพลาดจะรวมตัวกันตรงจุดที่สองชั้นถูกมองเป็นชั้นเดียว
สองเซสชันทราบสปอร์ต หนึ่งเส้นทางภายใน
แต่ละโฮสต์เก็บการเชื่อมต่อภายนอกของตนเองไปยังฮับ และไม่มีอันใดรู้จักอีกอัน ทราฟฟิกของผู้ใช้ไปยังอินเทอร์เน็ตเป็นกระแสภายในแยกต่างหากที่บังเอิญถูกบรรจุอยู่ในทันเนลของผู้ใช้แล้วถูกปล่อยออกจากทันเนลของทางออกอีกครั้ง ฮับต้องกำหนดเส้นทางให้กระแสภายในนี้ และทางออกต้องส่งต่อมัน
outer: consumer --TLS--> hub
outer: exit --TLS--> hub
inner: consumer --tun--> hub --internal--> exit --tun--> uplink
เนื่องจากการเชื่อมต่อภายนอกทั้งสองออกจากฝั่งไคลเอนต์ เราเตอร์ของทางออกจึงไม่เคยเห็น SYN ขาเข้าและไม่ต้องใช้กฎ NAT ปลายทางเลย มันเพียงต้องรักษาทราบสปอร์ตที่สร้างไว้ให้คงอยู่ ซึ่งมันทำอยู่แล้วสำหรับการเชื่อมต่ออายุยาวใด ๆ ไม่มีสิ่งใดบนอินเทอร์เน็ตสาธารณะต้องไปถึงทางออกก่อน นั่นคือเหตุผลที่ที่อยู่แบบไดนามิกบนลิงก์อัปสตรีมนั้นไม่เป็นปัญหา เราเตอร์ยังต้องอนุญาตเซสชันขาออก แต่นั่นก็เป็นการอนุญาตแบบเดียวกับที่เบราว์เซอร์ใช้อยู่แล้ว
iroute ติดตั้งอะไรจริง ๆ ภายในเซิร์ฟเวอร์
ฮับเป็นอุปกรณ์เดียวที่ตัดสินว่าเพียร์ใดเป็นเจ้าของจุดหมายใด และตัดสินในตารางเส้นทางพื้นที่ผู้ใช้ของตัวเอง ไม่ใช่ของเคอร์เนล รายการ client-config-dir สำหรับทางออกชื่อ exit-node สามารถอ้างคำนำหน้าเริ่มต้นได้ที่นั่น:
# /etc/openvpn/server/ccd/exit-node
iroute 0.0.0.0 0.0.0.0
บรรทัดนั้นบอกกระบวนการเซิร์ฟเวอร์ว่า "แพ็กเก็ตภายในที่มีจุดหมายใดก็ตามเป็นของไคลเอนต์รายนี้" มันไม่ได้เพิ่มเส้นทางเริ่มต้นให้ระบบปฏิบัติการของฮับ และไม่ได้ทำให้ฮับเป็นเกตเวย์อินเทอร์เน็ตเอกสารอ้างอิง OpenVPN 2.6 รักษาการกำหนดเส้นทางภายในเฉพาะไคลเอนต์และการกำหนดเส้นทางของเคอร์เนลไว้เป็นกลไกแยกกัน และการแยกนี้คือเหตุผลที่ดีไซน์นี้ทำงานได้โดยไม่ต้องแตะเส้นทางเริ่มต้นของฮับ
ผู้ใช้ต้องการคำสั่งแบบกระจกที่ส่งมาเป็น push เพื่อให้ทราฟฟิกของตนเข้าสู่ทันเนลแทนที่จะออกผ่านเกตเวย์ท้องถิ่น ด้วยโทโพโลยีซับเน็ต พูลเป็นเส้นทางที่เชื่อมต่ออยู่บนอุปกรณ์ tun ของทางออก ทิศทางขากลับจึงไม่ต้องใช้ push เพิ่มเติม สิ่งที่ทางออกต้องไม่ได้รับคือการเปลี่ยนเส้นทางด้านล่าง ซึ่งจะส่งทราฟฟิกอัปสตรีมของมันเองกลับเข้าทันเนลที่มันพยายามใช้ ส่งไปยังผู้ใช้เท่านั้น และอ่านเส้นทางที่ใช้งานบนไคลเอนต์แทนที่จะเชื่อว่าไฟล์ CCD ถูกเลือกแล้ว
# /etc/openvpn/server/ccd/consumer-01
push "redirect-gateway def1"
เคอร์เนลของทางออกเป็นผู้ส่งต่อ
ภายในทางออก แพ็กเก็ตจากผู้ใช้มาถึงอินเทอร์เฟซ tun โดยมีที่อยู่ไปยังโฮสต์อินเทอร์เน็ตบางแห่ง เคอร์เนลตรวจสอบตารางเส้นทางของตน พบลิงก์อัปสตรีมจริงเป็นรายการที่ตรงที่สุด แล้วใช้สองนโยบายที่เป็นอิสระต่อกัน: แพ็กเก็ตนี้ส่งต่อได้เลยหรือไม่ และออกจากอินเทอร์เฟซนั้นด้วยที่อยู่ต้นทางนั้นได้หรือไม่ การส่งต่อเป็น sysctl และในการกระจายจำนวนมากถูกปิดไว้โดยค่าเริ่มต้น
ดังนั้นทางออกจึงต้องเปิดการส่งต่อ IPv4 อย่างถาวร มีกฎที่อนุญาตต้นทางของทันเนลผู้ใช้ไปยังลิงก์อัปสตรีม และมีเส้นทางตอบกลับผ่าน tun การเปิดการส่งต่อสำหรับโฮสต์ทั้งหมดยังเปลี่ยนพฤติกรรมของทุกสิ่งอื่นที่กำหนดเส้นทางผ่านมัน กฎจึงควรระบุต้นทางและอินเทอร์เฟซแทนที่จะยอมรับทุกอย่าง
เส้นทางขากลับได้มาจากการแปลที่อยู่ต้นทาง
โฮสต์อินเทอร์เน็ตไม่มีเส้นทางไปยังที่อยู่ 10.90.0.x คำตอบจึงถูกทิ้งก่อนจะถึงทางออก การเขียนที่อยู่ต้นทางใหม่ขณะแพ็กเก็ตออกจากลิงก์อัปสตรีมแก้ปัญหานี้ และการติดตามการเชื่อมต่อจะย้อนการเขียนใหม่นั้นขากลับ
sysctl -w net.ipv4.ip_forward=1
iptables -t nat -A POSTROUTING -s 10.90.0.0/24 -o enp1s0 -j MASQUERADE
ip -4 route get 10.90.0.9 # run on the exit: must point at the tun
กฎข้างต้นเป็นจุดเริ่มต้นสำหรับอินเทอร์เฟซอัปสตรีมหนึ่งอัน ไม่ใช่นโยบายไฟร์วอลล์ จำกัดต้นทางให้แคบลงถึงการกำหนดที่คุณอนุญาตจริง และแยกนโยบายขาเข้าของทางออกไว้ต่างหาก เพื่อให้การมาส์กเกอเรดสำหรับทราฟฟิกทันเนลไม่เปิดเผยบริการภายในอย่างเงียบ ๆ บนโฮสต์ที่มีที่อยู่ลิงก์อัปสตรีมแบบไดนามิก การมาส์กยังช่วยให้คุณไม่ต้องติดตามที่อยู่ด้วยมือ
ฮับบังคับใช้ได้ที่ไหนและไม่ได้ที่ไหน
ด้วย client-to-client และเส้นทางข้อมูลพื้นที่ผู้ใช้แบบดั้งเดิม ฮับส่งแพ็กเก็ตภายในระหว่างเพียร์ภายในกระบวนการเซิร์ฟเวอร์ แพ็กเก็ตเหล่านั้นไม่เคยผ่านเชน FORWARD ของเคอร์เนลฮับ กฎเคอร์เนลที่นั่นจึงจำกัดการผ่านระหว่างเพียร์ไม่ได้ ไม่ว่าจะเขียนอย่างระมัดระวังเพียงใด การบังคับใช้ต้องอยู่ในนโยบายของเซิร์ฟเวอร์เอง: ใครได้รับอนุญาตให้เชื่อมต่อ ได้ไฟล์ CCD ใด และให้สิทธิ์ความเป็นเจ้าของเริ่มต้นของทางออกหรือไม่
นั่นคือเหตุผลที่ iroute เริ่มต้นเป็นการตัดสินใจเรื่องความไว้วางใจที่กว้าง ทางออกต้องยอมรับทราฟฟิกขากลับจากที่อยู่อินเทอร์เน็ตใด ๆ อย่างชอบธรรม และต้องเป็นเครื่องที่คุณควบคุม แพตช์ และเฝ้าติดตาม การให้สิทธิ์ความเป็นเจ้าของเดียวกันกับ เพียร์ที่คุณจะไม่มอบเราเตอร์ให้ คือข้อผิดพลาดที่ดีไซน์นี้เชิญชวน
การเป็นสมาชิกคือการควบคุมที่แท้จริง ไคลเอนต์ใดที่กำหนดเส้นทางของตัวเองได้ก็อาจพยายามใช้ทางออก ดังนั้นการจำกัดว่าใครเชื่อมต่อได้ หรือทางออกจะให้บริการเพียร์ใด จึงเป็นส่วนหนึ่งของดีไซน์ ไม่ใช่ความคิดภายหลัง
ล้มเหลวอย่างปลอดภัยเมื่อทางออกหายไป
ปิดทางออก แล้วความเป็นเจ้าของเริ่มต้นภายในจะหายไปพร้อมเซสชัน สิ่งที่ไม่ควรเกิดขึ้นคือฮับกลายเป็นทางออกทดแทนอย่างเงียบ ๆ ไม่มีเส้นทางเริ่มต้นจากพูล VPN ไปยังลิงก์อัปสตรีมของฮับ และไม่มีกฎมาส์กกว้างบนฮับที่จะส่งต่อทราฟฟิกเดียวกันอย่างเต็มใจ ทดสอบความล้มเหลวนั้นโดยเจตนา แล้วทดสอบการกู้คืนด้วยกระแสใหม่ ไม่ใช่หน้าที่ยังเปิดอยู่
# on the consumer, with the exit up and then down
ip -4 route get 1.1.1.1
curl -sS -o /dev/null -w '%{http_code}\n' https://example.com
หากทราฟฟิกของผู้ใช้ยังไปถึงอินเทอร์เน็ตหลังจากทางออกจากไป แสดงว่ามีอย่างอื่นพามันไปและขอบเขตที่คุณตั้งใจไม่มีอยู่จริง ปฏิบัติกับ DNS และ IPv6 เป็นการตัดสินใจแยกเช่นกัน เส้นทาง IPv4 บวกการมาส์กไม่ได้บอกอะไรเกี่ยวกับทราฟฟิก AAAA และตัวแก้ชื่อที่ฮับผลักไปจะตอบจากที่ที่คำถามออกไปจริง ๆ