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

เส้นทางเป็นตัวกำหนดว่าแพ็กเก็ตใดจะมาถึง

Builder.addRoute(prefix, prefixLength) ติดตั้งคำนำหน้าในตารางเส้นทางของเครือข่าย VPN ระบบถือว่าเครือข่ายนั้นเป็นค่าเริ่มต้นสำหรับแอปในขอบเขต ดังนั้นรายการของมันจะถูกตรวจสอบก่อนเส้นทางเริ่มต้นของเครือข่ายทางกายภาพ และคำนำหน้าที่ตรงกันก็เพียงพอที่จะเบี่ยงโฟลว์ ดังนั้นเส้นทางสำหรับ 0.0.0.0/0 จึงใช้ IPv4 ทั้งหมด รวมถึงเครื่องพิมพ์ เป้าหมาย cast และหน้าผู้ดูแลเส้นทางของเราเตอร์

ไคลเอนต์ที่ต้องการความครอบคลุมนั้นโดยไม่ลบรายการเริ่มต้นทางกายภาพจะใช้คู่ 0.0.0.0/1 และ 128.0.0.0/1 แทน เส้นทางที่เฉพาะเจาะจงกว่าสองเส้นชนะเส้นทางเริ่มต้น ด้วยกันทั้งคู่ครอบคลุมพื้นที่ที่อยู่ทั้งหมด และรายการเริ่มต้นของโทรศัพท์เองยังอยู่ในตารางสำหรับซ็อกเก็ตที่ยังผูกกับเครือข่ายทางกายภาพ

ที่อยู่และรายการ DNS มาจากผู้สร้างเดียวกันและตอบคำถามอีกข้อหนึ่ง addAddress ให้ที่อยู่กับอินเทอร์เฟซพร้อมความยาวคำนำหน้า และ addDnsServer ติดตั้งตัวแก้ชื่อที่แอปในขอบเขตจะสอบถามผ่านอินเทอร์เฟซ ทั้งสองตระกูลสามารถอยู่บนอินเทอร์เฟซเดียวได้ ดังนั้นชุดเส้นทางเฉพาะ IPv4 ข้างที่อยู่ IPv6 จึงเป็นการกำหนดค่าที่อ่านถูกต้องแต่ยังรั่ว ถ้าคุณกำหนดเส้นทางให้ตระกูลที่อินเทอร์เฟซไม่มีที่อยู่ เคอร์เนลจะไม่มีที่อยู่ต้นทางสำหรับแพ็กเก็ตเหล่านั้น และแพ็กเก็ตนั้นจะไม่ปรากฏที่ตัวอธิบายเลย

establish() คืนอะไร

establish() คืน ParcelFileDescriptor หรือ null เมื่อแอปไม่ได้รับอนุญาต null คือสถานะการยินยอม ไม่ใช่ข้อผิดพลาดของเครือข่าย: VpnService.prepare() ต้องคืน null ไปแล้ว หรือผู้ใช้ต้องอนุมัติกล่องโต้ตอบของระบบก่อนการเรียก การยินยอมสามารถถูกเพิกถอนได้ระหว่างที่บริการทำงาน และอินเทอร์เฟซจะปิดลงใต้เท้าคุณโดยไม่มีการเรียกกลับเข้าลูปอ่านของคุณ

ตัวอธิบายไม่บล็อกเว้นแต่คุณจะบอกเป็นอย่างอื่น ดังนั้นการอ่านจะกลับมาทันทีโดยไม่มีข้อมูลแทนที่จะหยุดเธรด ลูปที่เขียนสำหรับซ็อกเก็ตแบบบล็อกจะเผาแกนหนึ่งแกน และลูปที่ถือว่าการอ่านว่างเป็นเรื่องร้ายแรงจะฉีกอุโมงค์ในวินาทีที่ไม่มีงาน Builder.setBlocking(true) เปลี่ยนพฤติกรรมนั้น และราคาคือเธรดที่รออยู่ในการอ่าน

อาร์กิวเมนต์เส้นทางและที่อยู่ถูกตรวจสอบเป็นค่า LinkAddress และปลายทาง multicast เช่น 224.0.0.0/4 ถูกปฏิเสธทันที อินเทอร์เฟซ tun แบบจุดต่อจุดก็ไม่มีโดเมนกระจายสัญญาณเช่นกัน ดังนั้นการค้นหา multicast ผ่านอินเทอร์เฟซนั้นจึงต้องใช้การออกแบบอื่น ไม่ใช่เส้นทางเพิ่มอีกเส้น รายการที่ต้องตรวจสอบคือ VpnService.Builder ก่อนที่คุณจะสรุปว่าการเรียกหนึ่งถูกเพิกเฉย

ขอบเขตต่อแอปทำให้ชุดแคบลง ไม่ได้ทำให้อุโมงค์แคบลง

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

ไม่ว่าขอบเขตจะเป็นอย่างไร ซ็อกเก็ตที่แอปของคุณเปิดไปยังเซิร์ฟเวอร์ VPN ต้องไม่เดินทางผ่านอุโมงค์ที่มันกำลังสร้าง VpnService.protect(socket) ผูกซ็อกเก็ตนั้นกับเครือข่ายชั้นล่างเพื่อให้แพ็กเก็ตข้ามอุปกรณ์ tun ถ้าคุณข้ามการเรียกนั้น การจับมือจะถูกกลืนโดยอินเทอร์เฟซที่ควรจะยกมันขึ้นมา

VpnService.setUnderlyingNetworks ปิดวงโดยบอกระบบว่าเครือข่ายทางกายภาพใดเป็นตัวพาอุโมงค์ ค่านั้นกำหนดว่า VPN รายงานการเชื่อมต่อต่อแอปอื่นอย่างไร และค่า null หรือค่าที่เก่าคือสาเหตุปกติของอุโมงค์ที่เชื่อมต่ออยู่แต่โทรศัพท์ส่วนที่เหลือถือว่าออฟไลน์ อัปเดตค่าเมื่อเครือข่ายชั้นล่างเปลี่ยน ไม่ใช่เฉพาะตอนเริ่มต้น

เช็กซัมและ MTU: จุดที่เส้นทางที่ถูกต้องยังล้มเหลว

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

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

รอบตรวจสอบที่แยกชั้นต่าง ๆ ออกจากกัน

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

ถ้าไม่มีอะไรไปถึงตัวอธิบาย ผู้ต้องสงสัยคือชุดเส้นทาง ไม่ใช่ตัวอ่าน: ยืนยันคำนำหน้า ยืนยันว่ามีที่อยู่ในตระกูลเดียวกัน และยืนยันว่าแอปอยู่ในขอบเขตที่คุณตั้งไว้ ถ้าแพ็กเก็ตไปถึงตัวอธิบายแต่ไม่มีอะไรกลับมา ให้ตรวจ protect() และค่าของเครือข่ายชั้นล่างก่อนแตะเส้นทางอีกครั้ง ถ้าฝั่งไกลรายงานแพ็กเก็ตผิดรูปแบบ เช็กซัมและ MTU คือจุดที่ต้องดู การตรวจหนึ่งครั้งต่อชั้นช่วยประหยัดบ่ายครึ่งวันของการเดา

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