Tunnel chia tách trên Android VpnService: thực tế có thể định tuyến những gì
VpnService chỉ chuyển các gói mà builder đặt vào bộ mô tả của nó. Hãy kiểm tra tiền tố, xác thực proxy, DNS và danh sách ứng dụng trước khi đổ lỗi cho tunnel chia tách.
Tunnel chia tách trong một ứng dụng VpnService hỏng ở vài điểm cụ thể, và cách sửa mỗi điểm lại khác nhau. Ứng dụng khách chỉ hiển thị một đích đến bị lỗi, nên việc đầu tiên là xác định bạn đang chạm ranh giới nào: các tuyến mà builder nhận được, proxy bạn không xác thực được, resolver bạn chỉ mới công bố, hay danh sách ứng dụng mà nền tảng áp dụng cho từng người dùng. Chính vì thế tôi giữ một bước kiểm tra bốn phần. Nó chạy theo thứ tự dưới đây và dừng ở lỗi đầu tiên.
Xác nhận builder đã trao gì cho nền tảng
VpnService.Builder là một danh sách khai báo. establish() trả về một ParcelFileDescriptor cho giao diện ảo, và nền tảng chỉ gửi vào bộ mô tả đó lưu lượng khớp với các tiền tố của addAddress và addRoute; addDnsServer cho ứng dụng khách biết nên hỏi resolver nào. Một đích đến nằm ngoài mọi tiền tố bạn đã thêm sẽ không bao giờ tới được ứng dụng của bạn, bất kể giao diện hứa gì. Chỉ riêng sự thật này đã giải thích phần lớn báo cáo về tunnel chia tách hỏng trước khi đổi mã.
Ghi lại các danh sách địa chỉ, tuyến và DNS mà builder đã lắp, rồi so với những đích đến bị lỗi. Một /32 thiếu cho máy chủ quản trị phổ biến hơn nhiều so với một tunnel hỏng. Nếu danh sách đúng, hãy đi tiếp; nếu sai, phần còn lại của danh sách kiểm tra là công sức bỏ đi.
excludeRoute chỉ có từ API 33
Loại trừ theo đích đến là yêu cầu thường trở thành một luồng hỗ trợ nhất. builder.excludeRoute(IpPrefix) xuất hiện từ API 33; trước đó, cách xấp xỉ duy nhất được hỗ trợ là thêm tuyến cho mọi thứ trừ tiền tố bị loại, nghĩa là bạn phải tự tính các tiền tố bù. Hai lệnh gọi không phải ảnh gương của nhau: một lệnh trừ khỏi tập tuyến của giao diện, lệnh kia dựng tập từ đầu, và thiết bị cũ hơn sẽ ném hoặc lặng lẽ bỏ qua một lệnh gọi mà nó không nhận ra. Phân nhánh theo Build.VERSION.SDK_INT và thử cả hai đường trên phần cứng thật.
Khi tính phần bù, hãy để ý căn chỉnh tiền tố. Một loại trừ /24 bên trong tuyến /16 cần các tiền tố bao phủ thực sự mô tả phần còn lại, không phải một /8 méo mó. Ghi lại danh sách cuối và xác nhận máy chủ bị loại vẫn được phân giải qua một đường ngoài tunnel.
Thông tin đăng nhập proxy không thể biểu đạt
setHttpProxy(ProxyInfo) trỏ giao diện tới proxy HTTP hoặc SOCKS, và URL PAC có thể chọn giữa hai loại theo từng đích. Cả ProxyInfo lẫn định dạng PAC đều không mang tên người dùng và mật khẩu, nên proxy yêu cầu xác thực nằm ngoài API của nền tảng. Những ứng dụng cần điều đó thường nhúng một ngăn xếp không gian người dùng, tun2socks hoặc một engine tương tự, và tự trả lời thử thách 407.
Lựa chọn đó có hệ quả. Ngăn xếp không gian người dùng chỉ thấy lưu lượng bạn định tuyến tới nó, nên nó phải sống trong cùng ranh giới bộ mô tả, và nó thừa hưởng yêu cầu protect() y hệt cho socket đường lên của chính nó. Nếu bỏ protect(), tunnel sẽ bắt lấy chính phương tiện vận chuyển của nó, điều này hiện ra như một lần bắt tay bị treo sau gói đầu tiên. Hãy thử với proxy đòi hỏi thông tin đăng nhập, vì proxy mở đi qua đường của nền tảng và không chứng minh gì.
Danh sách theo ứng dụng và DNS cũng là khai báo
Builder có cả addAllowedApplication và addDisallowedApplication, và chúng loại trừ nhau: gọi cả hai sẽ ném ngoại lệ. Dù sao, danh sách được áp dụng cho từng người dùng khi thiết lập giao diện, nên một ứng dụng trong hồ sơ công việc chịu sự quản lý của tunnel hồ sơ công việc chứ không phải tunnel của bạn. Điều không API nào cung cấp là kiểm soát quyết định của ứng dụng khác: bạn không thể đẩy một gói vào tunnel của mình từ bên ngoài, và không thể đóng hay cấu hình lại một VPN đang hoạt động.
DNS theo cùng khuôn mẫu. addDnsServer công bố một địa chỉ resolver; nó không cho ứng dụng của bạn trả lời truy vấn. Nếu muốn tự phân giải tên, hãy chạy một resolver và định tuyến địa chỉ của nó vào tunnel. Ứng dụng dùng DoT hoặc DoH tới một tên máy chủ cố định có thể vượt qua địa chỉ bạn công bố, nên hãy coi việc phân giải tên là nỗ lực tốt nhất trừ khi bạn cũng chặn đường truy vấn.
Manifest, always-on và bài kiểm tra bốn bước
Tính hợp lệ được khai báo trong manifest: BIND_VPN_SERVICE, bộ lọc intent android.net.VpnService và dịch vụ tiền cảnh nếu tunnel phải sống sót ở chế độ nền. Always-on VPN còn đòi một onRevoke sạch, vì hệ thống gọi nó khi người dùng đổi cài đặt hoặc ứng dụng khác tiếp quản.
Chạy các bước kiểm tra theo thứ tự và dừng ở lỗi đầu tiên. Thứ nhất, xuất các danh sách địa chỉ, tuyến và DNS đã trao cho builder. Thứ hai, xác nhận establish() đã trả về một bộ mô tả và socket vận chuyển được bảo vệ. Thứ ba, yêu cầu một đích bên trong tiền tố được bao gồm và một đích bên trong tiền tố bị loại. Thứ tư, kiểm tra resolver nào thực sự trả lời bằng cách truy vấn một tên máy chủ mà chỉ resolver của bạn phân giải được.
Một lần bắt tay hoàn tất không chứng minh bất kỳ bước nào sau đó. Mỗi bước kiểm tra cô lập một lớp, nên thành công ở bước hai không nói gì về bước bốn.
Tài liệu tham chiếu VpnService.Builder liệt kê toàn bộ bề mặt của builder. Mọi thứ bạn cần vượt ra ngoài đó là một API khác, một ứng dụng khác hoặc ngăn xếp không gian người dùng của riêng bạn, và đó là quyết định thiết kế chứ không phải lỗi tunnel chia tách.