Giả định sai lầm là ứng dụng VpnService gạt một công tắc hệ thống và từ đó điện thoại được bảo vệ. Điều thực sự xảy ra hẹp hơn: ứng dụng xin sự đồng ý, dựng một giao diện, đọc và ghi gói tin qua một bộ mô tả tệp, và trả lại giao diện khi dừng. Chỉ một đường hầm như vậy có thể tồn tại tại một thời điểm, và hệ thống có thể lấy nó đi mà không hỏi. Phần lớn hành vi khó hiểu mà người ta đổ lỗi cho giao thức đến từ mô hình sở hữu đó.

Hãy coi mỗi chuyển trạng thái của vòng đời là một phần của thiết kế chứ không phải trường hợp ngoại lệ.

Ngộ nhận: ứng dụng bật VPN của điện thoại

Luồng bắt đầu bằng VpnService.prepare(), trả về một Intent mà người dùng phải chấp nhận, và tiếp tục bằng establish(), vốn vẫn có thể thất bại sau đó vì những lý do ứng dụng không kiểm soát. Việc thiết lập đường hầm thứ hai vô hiệu hóa giao diện đầu tiên và thay thế thông báo hệ thống. Không nơi nào trong trình tự đó ứng dụng sở hữu một công tắc cho toàn bộ thiết bị; nó sở hữu bộ mô tả được cấp và các gói tin rơi vào đó. Ngay cả thông báo thường trực cũng thuộc về hệ thống chứ không phải thiết kế của ứng dụng.

Vì giao diện được ban cho chứ không bị chiếm đoạt, một ứng dụng khác có thể chiếm chỗ bất cứ lúc nào. Ứng dụng biết điều đó qua một lệnh gọi lại, không phải qua giá trị trả về.

Việc ứng dụng bị buộc dừng hiện ra trong bảng thông báo cũng vì lý do đó: hệ thống báo cáo những gì nó đã ban, không phải những gì ứng dụng chọn hiển thị.

Ngộ nhận: sự đồng ý chỉ được cấp một lần

Khi hệ thống giao đường hầm cho một ứng dụng khác, nó gửi một giao dịch thu hồi trên binder mà lớp cơ sở trả về từ onBind(). Ứng dụng thay binder đó bằng của mình phải tự chuyển tiếp giao dịch thu hồi, nếu không VpnService.onRevoke() sẽ không bao giờ kích hoạt và dịch vụ tiếp tục ghi vào một bộ mô tả không còn tồn tại. Người dùng cũng có thể thu hồi sự phê duyệt, và hệ thống có thể dừng dịch vụ trong lúc khởi động lại hoặc kết nối lại ở chế độ luôn bật.

Xử lý việc này là công việc cụ thể chứ không phải một dòng nhật ký: ngừng đọc bộ mô tả, đóng nó, xóa cờ đã kết nối và nói cho người dùng biết họ đang ở trạng thái nào. Sau đó quyết định ứng dụng nên thiết lập lại, hỏi lại hay chờ, vì trong cấu hình luôn bật hệ thống có thể tự mang dịch vụ trở lại.

Một ứng dụng coi việc thu hồi là ngoại lệ chứ không phải trạng thái sẽ báo là đã kết nối trong khi mọi gói tin nó ghi đều bị bỏ, và đó là chế độ hỏng tệ nhất có thể đối với một công cụ riêng tư.

Ngộ nhận: giao diện lọc lưu lượng giúp bạn

Thứ đến là IP thô. Bắt hay lọc nghĩa là phân tích các tiêu đề IP, TCP, UDP và IPv6, theo dõi luồng và ghi phản hồi trở lại bộ mô tả, điều này biến một ứng dụng bắt gói thành một ngăn xếp mạng nhỏ chứ không phải một tiện ích hệ thống. protect() tồn tại để socket vận chuyển của chính ứng dụng đi vòng qua đường hầm của nó thay vì tự bắt chính mình, và nó trả về một boolean đáng được kiểm tra trên mọi socket được tạo sau khi đường hầm hoạt động.

Các danh sách ứng dụng được phép và bị cấm trong trình dựng quyết định lưu lượng của ai đi vào giao diện. Chúng không cấp quyền truy cập vào tải trọng ngoài các gói IP mà giao diện mang, và không API nào phơi bày đường hầm của ứng dụng khác hay liệt kê các hồ sơ VPN của hệ thống. Khả năng mạng có thể nói một mạng là vận chuyển VPN, nhưng không nói đường hầm của ai.

Đó là lý do các câu hỏi về trạng thái của ứng dụng khác nhận được câu trả lời mâu thuẫn: thông tin đơn giản là không được phơi bày cho tiến trình của bạn.

Vòng đời thực sự yêu cầu gì

Theo dõi các trạng thái bạn kiểm soát: đã chuẩn bị, đã thiết lập, đã thu hồi, đã dừng. Kết nối lại nên là tháo dỡ rồi thiết lập, với một chủ sở hữu duy nhất cho bộ mô tả và socket vận chuyển, vì hai thành phần tranh nhau thiết lập sẽ đánh nhau vì chỗ trống duy nhất. Việc khởi động nền bị hạn chế trên Android hiện đại, nên một bản phát sóng khi khởi động không phải là bảo đảm; hãy dùng dịch vụ nền trước với thông báo của nó ở nơi mức API cho phép.

Ghi lại chuyển trạng thái chứ không chỉ trạng thái cuối, và giữ máy trạng thái đủ nhỏ để kiểm thử mọi đường đi. Một trạng thái bạn không thể chạm tới trong kiểm thử là trạng thái bạn sẽ gặp ngoài thực địa mà không có nhật ký.

Kiểm thử vòng đời trên thiết bị

Trình giả lập có thể làm giả kết nối nhưng không làm giả đường đi của sự đồng ý và thu hồi một cách thuyết phục, nên hãy chạy những bài này trên thiết bị thật có cài một ứng dụng VPN thứ hai để giành chỗ.

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

Khởi động đường hầm, xác nhận thông báo và giao diện, rồi kết nối ứng dụng kia và xem lệnh gọi lại thu hồi có chạy không và trạng thái có được xóa không. Buộc dừng ứng dụng và kiểm tra xem còn gì báo cáo một bộ mô tả còn sống không.

Lặp lại trình tự sau một lần khởi động lại ở chế độ luôn bật, vì đường đi đó khác với một kết nối do người dùng khởi tạo. Thứ bạn tìm không phải một biểu tượng xanh mà là bằng chứng rằng ứng dụng đã ngừng dùng một bộ mô tả mà nó không còn sở hữu.

Bài viết liên quan