Quy gán VPN trên Android: getConnectionOwnerUid so với /proc/net
Android 10 ẩn /proc/net khỏi ứng dụng. VPN vẫn nhận gói tin, nhưng quy gán chuyển sang getConnectionOwnerUid, với quy tắc tuple và quyền riêng.
Trên Android 10 và mới hơn, ứng dụng mở /proc/net/tcp nhận bị từ chối truy cập thay vì bảng socket. Giao diện VPN không bận tâm: gói tin vẫn đến, vì đường hầm là quyết định định tuyến, không phải quyền tệp. Điều đã thay đổi là câu trả lời cho một câu hỏi hẹp hơn: «ứng dụng nào sở hữu kết nối này?» Thay thế là ConnectivityManager.getConnectionOwnerUid, truy vấn được hỗ trợ cho từng kết nối, và việc chọn giữa thói quen cũ và API mới phụ thuộc vào việc bạn thực sự có công việc nào trong ba: nhận gói tin, quy gán luồng hay tổng hợp mức dùng.
Hai cách trả lời câu hỏi chủ sở hữu
Trước API 29, một tiến trình đặc quyền hoặc root có thể đọc bảng socket của nhân, khớp bộ bốn và biết UID chủ sở hữu. Điều đó cho một bản kiểm kê toàn thiết bị trong một tệp. Từ API 29, góc nhìn hệ thống tệp bị hạn chế, và con đường được hỗ trợ là truy vấn cho từng kết nối với giao thức, địa chỉ cục bộ và địa chỉ từ xa.
| Question | Legacy socket tables | getConnectionOwnerUid |
|---|---|---|
| Who owns this flow? | Scan and match the tuple | Ask once per tuple |
| Device-wide inventory? | Yes, when readable | No, one flow at a time |
| Historical usage? | No | No; that is NetworkStatsManager |
| Works without root? | No on modern releases | Yes, for authorized callers |
So sánh không phải «cũ so với mới». Đó là ba công việc khác nhau mà trước đây một tệp đọc được đáp ứng và nay ba giao diện khác nhau với ba hợp đồng khác nhau đáp ứng.
Địa chỉ cục bộ là đầu của ứng dụng
Chữ ký phương thức nhận một giao thức, một địa chỉ socket cục bộ và một địa chỉ socket từ xa. «Cục bộ» nghĩa là phía ứng dụng trong kết nối, không phải đầu nào tình cờ là nguồn gói tin tại thời điểm bạn kiểm tra. Gói tin đi từ máy chủ từ xa trở lại ứng dụng có nguồn và đích đảo ngược so với định nghĩa đó.
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);Lỗi hướng này là lý do phổ biến nhất khiến một truy vấn đúng trả về câu trả lời sai, hoặc không có. Ghi nhật ký cả hai đầu trước khi gọi và xác nhận cặp đôi với kết nối mà ứng dụng thực sự đã mở. Họ địa chỉ cũng quan trọng: địa chỉ ánh xạ IPv4 và địa chỉ IPv6 gốc là các tuple khác nhau, cũng như luồng TCP và UDP trên cùng cổng.
Ủy quyền và kết quả không rõ là phần của hợp đồng
Truy vấn bị giới hạn cho người gọi được ủy quyền, bao gồm ứng dụng VPN đang hoạt động của người dùng liên quan. Một ứng dụng giám sát không liên quan không thể giả định rằng nó liệt kê được kết nối của hồ sơ khác, và một lệnh gọi không được ủy quyền có thể ném ngoại lệ bảo mật thay vì trả lỗi thân thiện. Xử lý cả hai kết quả một cách rõ ràng.
Chủ sở hữu thiếu không phải là tên gói. Luồng có thể đã đóng giữa gói tin và truy vấn, tuple có thể chưa đầy đủ, hoặc đơn giản là người gọi không được phép biết. Lưu câu trả lời thành công đầu tiên và dùng lại sau đó gán kết nối mới cho ứng dụng cũ, tệ hơn là báo không rõ.
Khi quy gán thất bại, lưu lượng vẫn cần một quyết định: chặn ở nơi chính sách yêu cầu, chỉ cho phép phạm vi bạn hiểu, hoặc phơi bày khoảng trống. Phương án dự phòng đó thuộc về thiết kế, không phải chú thích.
Kế toán là câu hỏi khác
NetworkStatsManager báo cáo mức dùng theo quy tắc truy cập riêng. Nó có thể cho biết một UID đã truyền bao nhiêu trong một khoảng; nó không thể cho biết chủ sở hữu của một gói tin sống tùy ý. Khả năng mạng mô tả thuộc tính của mạng, không phải danh mục socket. Trộn ba thứ tạo ra bảng điều khiển trông nhất quán mà không phải vậy.
Với trường hợp đơn giản là quyết định ứng dụng nào dùng đường hầm, danh sách ứng dụng được phép và bị cấm được hỗ trợ của Android đã thể hiện chính sách mà không cần thu thập gì. Dành quy gán theo luồng cho những trường hợp thực sự cần, và dự trù cho một triển khai được duy trì xử lý hướng, quyền và trạng thái không rõ cùng nhau.
Chọn công cụ theo công việc, rồi kiểm tra các cạnh
Nếu yêu cầu là chọn đường hầm, dùng danh sách của nền tảng. Nếu là quy gán trực tiếp, dùng truy vấn chủ sở hữu và coi mọi tuple không trả lời được là không rõ. Nếu là tổng hợp lịch sử, dùng stats API. Sau đó kiểm tra ba cạnh phá vỡ triển khai ngây thơ: gói tin ngược hướng, luồng đóng trước khi bạn hỏi, và người gọi không phải VPN đang hoạt động.
Không điều nào trong số này đòi hỏi tắt SELinux, root điện thoại hay làm bảng nhân đọc được toàn cầu. Ràng buộc chính là điểm mấu chốt: nền tảng đã chuyển quy gán ra sau một hợp đồng để câu trả lời chỉ đến được thành phần thực sự cần nó một cách hợp pháp, và không ai khác.