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

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

ความเชื่อผิด: แอปเปิด VPN ของโทรศัพท์

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

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

การบังคับหยุดแอปปรากฏในแผงการแจ้งเตือนด้วยเหตุผลเดียวกัน: ระบบรายงานสิ่งที่มอบให้ ไม่ใช่สิ่งที่แอปเลือกจะแสดง

ความเชื่อผิด: ความยินยอมให้ครั้งเดียว

เมื่อระบบส่งมอบอุโมงค์ให้ไคลเอนต์อื่น มันจะส่งธุรกรรมเพิกถอนบน binder ที่คลาสฐานส่งคืนจาก onBind() แอปที่แทนที่ binder นั้นด้วยของตัวเองต้องส่งต่อธุรกรรมเพิกถอนเอง มิฉะนั้น VpnService.onRevoke() จะไม่ถูกกระตุ้นเลย และบริการจะเขียนลงในตัวบ่งชี้ที่ไม่มีอยู่แล้วต่อไป ผู้ใช้ก็เพิกถอนการอนุมัติได้เช่นกัน และระบบสามารถหยุดบริการระหว่างการรีสตาร์ทหรือการเชื่อมต่อใหม่แบบเปิดตลอดเวลา

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

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

ความเชื่อผิด: อินเทอร์เฟซกรองทราฟฟิกให้คุณ

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

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

นั่นคือเหตุผลที่คำถามเรื่องสถานะของแอปอื่นได้คำตอบที่ขัดแย้งกัน ข้อมูลนั้นไม่ได้ถูกเปิดเผยต่อโปรเซสของคุณเลย

สิ่งที่วงจรชีวิตต้องการจริง ๆ

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

บันทึกการเปลี่ยนผ่านไม่ใช่แค่สถานะสุดท้าย และเก็บเครื่องสถานะให้เล็กพอที่จะทดสอบทุกเส้นทาง สถานะที่คุณไปไม่ถึงในการทดสอบคือสถานะที่คุณจะพบในภาคสนามโดยไม่มีบันทึก

ทดสอบวงจรชีวิตบนอุปกรณ์

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

adb shell dumpsys connectivity | grep -i -A3 vpn
adb shell dumpsys activity services | grep -i -A2 vpnservice
adb logcat -s VpnService:V AndroidRuntime:E
adb shell am force-stop com.example.vpnapp

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

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

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