วิธีตรวจสอบในโค้ดว่า VPN เชื่อมต่ออยู่บน Android หรือไม่
Android ไม่มี getter สถานะการเชื่อมต่อ VPN จงถามความสามารถของเครือข่าย ติดตามการเปลี่ยนแทรนสปอร์ต และแยกความยินยอมของแอป สถานะอุปกรณ์ และหลักฐานฝั่งเซิร์ฟเวอร์ออกจากกัน
คุณถาม Android ไม่ได้ว่า VPN เชื่อมต่ออยู่หรือไม่ VpnService ไม่มี getter สำหรับสถานะการเชื่อมต่อ และไอคอนกุญแจบนแถบสถานะนั้นระบบเป็นผู้วาด ไม่ใช่สิ่งที่แอปของคุณอ่านได้ สิ่งที่ทำได้คือถามสแต็กการเชื่อมต่อด้วยคำถามที่แคบลง: แอปนี้ใช้เครือข่ายใดอยู่ และเครือข่ายนั้นพาแทรนสปอร์ต VPN มาด้วยหรือไม่ คู่มือนี้ไล่การตรวจตามลำดับที่หลีกเลี่ยงผลบวกลวง ตั้งแต่คำสั่ง shell บนอุปกรณ์ไปจนถึง callback ที่คุณปล่อยให้ทำงานในแอป
ตรวจอุปกรณ์จาก shell ก่อนแตะโค้ดของแอป
ก่อนอื่นให้เก็บค่าฐานก่อน adb shell dumpsys connectivity พิมพ์ทุกเครือข่ายที่ระบบรู้จัก และแต่ละบล็อกจะระบุแทรนสปอร์ตของตน ดังนั้นเครือข่าย VPN จะปรากฏพร้อม VPN ในรายการนั้น อ่านบล็อกเครือข่ายเริ่มต้นก่อนสิ่งอื่นใด แล้วจึงมองหาเครือข่ายอื่นที่พาแทรนสปอร์ตเดียวกัน
เอาต์พุตสองแบบตอบคำถามคนละเรื่อง การมีอยู่ของเครือข่าย VPN ไม่เหมือนกับการที่แอปของคุณใช้มัน และบนอุปกรณ์ที่มีโปรไฟล์งานหรือผู้ใช้คนที่สอง ทั้งสองอย่างอาจเป็นจริงพร้อมกันขณะที่โปรเซสของคุณคุยกับอินเทอร์เน็ตโดยตรง บรรทัดเครือข่ายที่ใช้งานอยู่คือสิ่งที่กำหนดว่าโซเก็ตของคุณทำอะไร
ถามถึงเครือข่ายที่แอปใช้อยู่
ในโค้ด การทดสอบความสามารถมีสี่บรรทัด ขอ ACCESS_NETWORK_STATE ในแมนิเฟสต์ แล้วเปรียบเทียบความสามารถของเครือข่ายที่ใช้งานอยู่กับแทรนสปอร์ต VPN:
ConnectivityManager cm = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
Network active = cm.getActiveNetwork();
NetworkCapabilities caps = cm.getNetworkCapabilities(active);
boolean vpnInUse = caps != null
&& caps.hasTransport(NetworkCapabilities.TRANSPORT_VPN);
คำตอบถูกจำกัดอย่างตั้งใจ: เครือข่ายเริ่มต้นของแอปเป็น VPN อยู่หรือไม่ มันไม่บอกว่าเป็น VPN ตัวใด เป็นของคุณหรือไม่ หรืออุโมงค์ไปถึงปลายอีกด้านหรือเปล่า ให้ถือว่า true หมายถึง “ทราฟฟิกออกผ่านอุโมงค์บางอย่าง” และแยกบันทึกของคุณเองไว้ต่างหาก บูลีนที่หมายถึงสามสิ่งพร้อมกันอย่างเงียบ ๆ คือเหตุผลที่หน้าจอตั้งค่ารายงานว่า VPN ปิดอยู่ขณะผู้ใช้ท่องผ่านมัน
ในทางกลับกัน เมื่อต้องรู้ว่ามีเครือข่าย VPN ใด ๆ อยู่หรือไม่ ให้วนซ้ำบน cm.getAllNetworks() และทดสอบชุดความสามารถแต่ละชุด คำตอบทั้งสองแตกต่างกันบนอุปกรณ์ที่รัน VPN เฉพาะแอปซึ่งครอบคลุมแอปอื่นแต่ไม่ครอบคลุมของคุณ และความแตกต่างนั้นคือที่มาของรายงานบั๊กส่วนใหญ่ที่บอกว่าแอปมองไม่เห็น VPN ที่เปิดอยู่ชัด ๆ
ติดตามการเปลี่ยนแปลงแทนการโพล
การโพลด้วยตัวจับเวลาคือรูปแบบที่ผิดสำหรับคำถามนี้ เพราะการเปลี่ยนสถานะมาในรูปเหตุการณ์ ลงทะเบียนคำขอที่ตรงกับแทรนสปอร์ต VPN แล้วปล่อยให้เฟรมเวิร์กบอกคุณเมื่อเครือข่ายที่ตรงกันปรากฏ หายไป หรือเปลี่ยน:
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_VPN)
.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
.build();
cm.registerNetworkCallback(request, callback);
เครือข่าย VPN ไม่เคยพา NET_CAPABILITY_NOT_VPN ดังนั้นการลบออกอย่างชัดเจนจึงบันทึกเจตนา ถ้าไม่มีก็ยังตรงกัน แต่คนถัดไปที่อ่านโค้ดต้องคิดออกเอง
การลงทะเบียนเป็นของขอบเขตที่ต้องใช้การสังเกต โดยปกติเป็นหน้าจอหรือเซอร์วิสเบื้องหน้า ยกเลิกการลงทะเบียนใน onStop หรือ onDestroy และอย่าทิ้ง callback ไว้กับกิจกรรมที่จากไปแล้ว แอปที่ไม่มีองค์ประกอบที่มองเห็นเป็นเจ้าบ้านที่ไม่ดีสำหรับผู้สังเกตถาวร และ callback ที่คุณลืมคือสิ่งที่พังหลังการเปลี่ยนคอนฟิกุเรชัน
แยกคำถามสามข้อออกจากกัน
ความสับสนส่วนใหญ่ในเรื่องนี้มาจากการมองการตรวจที่แตกต่างกันสามอย่างเป็นหนึ่งเดียว พวกมันใช้ API ต่างกัน และแต่ละอย่างเป็นจริงได้ขณะที่อย่างอื่นเป็นเท็จ
| Question | How you answer it | What a yes means |
|---|---|---|
| Did the user grant VPN consent to my app? | VpnService.prepare() returns null | Consent exists; the tunnel may still be down |
| Is the device sending default traffic through a VPN? | TRANSPORT_VPN on the active network | Something is tunneling, not necessarily your app |
| Did this request arrive through the VPN? | Source address seen by the server | Proof for that connection, nothing wider |
แถวที่สามคือสิ่งที่ผู้คนพยายามตอบบนไคลเอนต์ และตอบที่นั่นไม่ได้ ให้พูลแอดเดรสของตัวเองแก่ไคลเอนต์ VPN และอ่านแอดเดรสต้นทางบนเซิร์ฟเวอร์ นั่นคือหลักฐานเดียวที่อยู่รอดเมื่อผู้ใช้สลับอุโมงค์กลางเซสชัน
ลำดับการตรวจสอบที่ตัดผลบวกลวงออก
โค้ดตัวอย่างเก่าที่ยังแพร่หลายเรียก getNetworkInfo(ConnectivityManager.TYPE_VPN) การเรียกนั้นเลิกใช้แล้ว และในหลายรีลีสคืนค่า null แม้ขณะ VPN เชื่อมต่ออยู่ นั่นคือเหตุผลที่ตัวอย่างที่คัดลอกกันมามากมายดูพังบนอุปกรณ์ที่ใช้งานได้ การตรวจความสามารถหลีกเลี่ยงกับดักนั้นและทำงานเหมือนกันตั้งแต่ Android 5.0 เป็นต้นมา หากคุณดูแลโค้ดเบสที่ยังเรียกมันอยู่ ให้แทนที่การเรียกแทนที่จะห่อด้วยทางสำรอง เพราะทางสำรองนั่นเองที่สร้างคำตอบผิดตั้งแต่แรก
ทางลัดหนึ่งอย่างสมควรได้รับคำเตือน การนำแอดเดรสสาธารณะที่เซอร์วิสภายนอกรายงานมาเทียบกับแอดเดรสทางออกของ VPN พิสูจน์อะไรบางอย่างเกี่ยวกับเส้นทางได้ แต่เพิ่มคำขอจากบุคคลที่สามในการตรวจทุกครั้ง ขึ้นกับการที่เซอร์วิสนั้นยังทำงานอยู่ และอาจถูกแคชหรือบล็อกได้ เก็บไว้เป็นเครื่องมือวินิจฉัยด้วยมือ ไม่ใช่การตรวจสถานะในแอปเด็ดขาด
รันการตรวจตามลำดับนี้และหยุดที่รายการแรกที่ไม่ตรงกับรายงานของผู้ใช้: รายการแทรนสปอร์ตจาก shell ความสามารถของเครือข่ายที่ใช้งานอยู่ เหตุการณ์ callback ขณะคุณเปิดและปิดอุโมงค์ แล้วจึงเป็นแอดเดรสต้นทางฝั่งเซิร์ฟเวอร์ หากทั้งสี่ตรงกันและแอปยังทำงานผิดพลาด บั๊กอยู่ที่เครื่องสถานะของคุณเอง ไม่ใช่มุมมองเครือข่ายของ Android