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

ยืนยันว่า builder มอบอะไรให้แพลตฟอร์ม

VpnService.Builder คือรายการคำประกาศ establish() คืน ParcelFileDescriptor สำหรับอินเทอร์เฟซเสมือน และแพลตฟอร์มส่งเฉพาะทราฟฟิกที่ตรงกับพรีฟิกซ์ของ addAddress และ addRoute เข้า descriptor นั้น addDnsServer บอกไคลเอนต์ว่าจะถาม resolver ใด ปลายทางที่อยู่นอกพรีฟิกซ์ทั้งหมดที่คุณเพิ่มจะไม่ถึงแอปของคุณเลย ไม่ว่าอินเทอร์เฟซจะสัญญาอะไร ข้อเท็จจริงข้อเดียวนี้ อธิบายรายงานการแยกทันเนลที่เสียส่วนใหญ่ได้ก่อนจะแก้โค้ด

บันทึกรายการที่อยู่ เส้นทาง และ DNS ที่ builder ประกอบขึ้น แล้วเทียบกับปลายทางที่ล้มเหลว /32 ที่หายไปสำหรับโฮสต์จัดการพบได้บ่อยกว่าทันเนลที่เสียมาก หากรายการถูกต้องให้เดินหน้าต่อ หากผิด ส่วนที่เหลือของรายการตรวจสอบก็เสียแรงเปล่า

excludeRoute มีตั้งแต่ API 33 เท่านั้น

การยกเว้นตามปลายทางคือคำขอที่มักกลายเป็นกระทู้ช่วยเหลือบ่อยที่สุด builder.excludeRoute(IpPrefix) มาถึงใน API 33 ก่อนหน้านั้นวิธีประมาณเดียวที่รองรับคือเพิ่มเส้นทางให้ทุกอย่างยกเว้นพรีฟิกซ์ที่ยกเว้น ซึ่งหมายความว่าคุณต้องคำนวณพรีฟิกซ์เสริมเอง การเรียกทั้งสองไม่ใช่ภาพสะท้อนกัน: ตัวหนึ่งลบออกจากชุดเส้นทางของอินเทอร์เฟซ อีกตัวสร้างชุดจากศูนย์ และอุปกรณ์รุ่นเก่าจะโยนหรือเพิกเฉยต่อการเรียกที่ไม่รู้จัก แยกสาขาตาม Build.VERSION.SDK_INT และทดสอบทั้งสองทางบนฮาร์ดแวร์จริง

เมื่อคำนวณส่วนเสริม ให้ระวังการจัดแนวพรีฟิกซ์ การยกเว้น /24 ภายในเส้นทาง /16 ต้องใช้พรีฟิกซ์ที่ครอบคลุมซึ่งอธิบายส่วนที่เหลือจริง ไม่ใช่ /8 ที่บิดเบี้ยวเพียงตัวเดียว บันทึกรายการสุดท้ายและยืนยันว่าโฮสต์ที่ถูกยกเว้นยังแก้ชื่อผ่านเส้นทางนอกทันเนลได้

ข้อมูลรับรองพร็อกซีแสดงออกไม่ได้

setHttpProxy(ProxyInfo) ชี้ทิศอินเทอร์เฟซไปยังพร็อกซี HTTP หรือ SOCKS และ URL แบบ PAC เลือกระหว่างสองแบบได้ตามปลายทาง ทั้ง ProxyInfo และรูปแบบ PAC ไม่มีชื่อผู้ใช้และรหัสผ่าน พร็อกซีที่ต้องยืนยันตัวตนจึงอยู่นอก API ของแพลตฟอร์ม แอปที่ต้องการมักฝังสแตกผู้ใช้ โปรแกรม tun2socks หรือเอนจินทำนองเดียวกัน และตอบการท้าทาย 407 ด้วยตัวเอง

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

รายการต่อแอปและ DNS ก็เป็นคำประกาศเช่นกัน

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

DNS ก็ตามรูปแบบเดียวกัน addDnsServer ประกาศที่อยู่ resolver มันไม่ได้ให้แอปของคุณตอบคำถาม ถ้าต้องการแก้ชื่อเอง ให้รัน resolver และส่งเส้นทางที่อยู่ของมันเข้าทันเนล แอปที่ใช้ DoT หรือ DoH ไปยังชื่อโฮสต์คงที่สามารถเลี่ยงที่อยู่ที่คุณประกาศได้ จึงควรถือการแก้ชื่อเป็นความพยายามที่ดีที่สุด เว้นแต่คุณจะดักเส้นทางคำถามด้วย

Manifest, always-on และการทดสอบสี่ขั้น

คุณสมบัติถูกประกาศใน manifest: BIND_VPN_SERVICE, ตัวกรอง intent android.net.VpnService และบริการเบื้องหน้าหากทันเนลต้องอยู่รอดเบื้องหลัง Always-on VPN ยังต้องมี onRevoke ที่สะอาด เพราะระบบเรียกมันเมื่อผู้ใช้เปลี่ยนการตั้งค่าหรือแอปอื่นเข้าควบคุม

ทำการตรวจตามลำดับและหยุดที่ความล้มเหลวแรก ข้อแรก dump รายการที่อยู่ เส้นทาง และ DNS ที่ให้ builder ข้อสอง ยืนยันว่า establish() คืน descriptor และซ็อกเก็ตการรับส่งได้รับการปกป้อง ข้อสาม ขอปลายทางหนึ่งภายในพรีฟิกซ์ที่รวมอยู่ และอีกหนึ่งภายในพรีฟิกซ์ที่ยกเว้น ข้อสี่ ตรวจว่า resolver ใดตอบจริงโดยถามชื่อโฮสต์ที่ resolver ของคุณเท่านั้นแก้ได้

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

เอกสารอ้างอิง VpnService.Builder ไล่พื้นผิวทั้งหมดของ builder สิ่งที่ต้องการเกินกว่านั้นคือ API อื่น แอปอื่น หรือสแตกผู้ใช้ของคุณเอง และนั่นเป็นการตัดสินใจออกแบบ ไม่ใช่บั๊กของการแยกทันเนล

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