Атрибуція VPN в Android: getConnectionOwnerUid проти /proc/net
Android 10 ховає /proc/net від застосунків. VPN і далі отримує пакети, але атрибуція переходить до getConnectionOwnerUid з власними правилами кортежу й дозволів.
У Android 10 і новіших застосунок, що відкриває /proc/net/tcp, отримує відмову в доступі замість таблиці сокетів. Інтерфейсу VPN це байдуже: пакети продовжують надходити, бо тунель — це рішення про маршрутизацію, а не дозвіл на файл. Змінилася відповідь на вужче питання: «який застосунок володіє цим з'єднанням?» Заміна — ConnectivityManager.getConnectionOwnerUid, підтримуваний запит для кожного з'єднання, а вибір між старою звичкою і новим API залежить від того, яке з трьох завдань у вас справді є: приймання пакетів, атрибуція потоку чи підсумовування використання.
Два способи відповісти на питання про власника
До API 29 привілейований процес або root міг читати таблиці сокетів ядра, зіставляти четвірку й дізнаватися UID власника. Це давало інвентар усього пристрою в одному файлі. Від API 29 подання файлової системи обмежене, а підтримуваний шлях — запит для кожного з'єднання з протоколом, локальною адресою та віддаленою адресою.
| 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 |
Порівняння не «старе проти нового». Це три різні завдання, які раніше задовольняв один читабельний файл, а тепер задовольняють три різні інтерфейси з трьома різними контрактами.
Локальна адреса — це кінець застосунку
Сигнатура методу приймає протокол, локальну адресу сокета й віддалену адресу сокета. «Локальна» означає бік застосунку в з'єднанні, а не той кінець, який у момент перевірки випадково є джерелом пакета. У пакета, що йде від віддаленого сервера назад до застосунку, джерело й призначення перевернуті щодо цього визначення.
ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);Ця помилка напрямку — найчастіша причина, чому робочий запит повертає неправильну відповідь або жодної. Записуйте обидва кінці перед викликом і звіряйте пару із з'єднанням, яке застосунок справді відкрив. Сімейство адрес теж важливе: адреса, відображена через IPv4, і нативна адреса IPv6 — різні кортежі, як і потоки TCP та UDP на тих самих портах.
Авторизація й невідомі результати — частина контракту
Запит обмежений авторизованими викликами, зокрема активним VPN-застосунком відповідного користувача. Сторонній застосунок моніторингу не може припускати, що перелічить з'єднання іншого профілю, а неавторизований виклик може викинути виняток безпеки замість дружньої помилки. Обробляйте обидва результати явно.
Відсутній власник — це не назва пакета. Потік міг закритися між пакетом і запитом, кортеж може бути неповним, або викликачу просто не дозволено знати. Кешування першої успішної відповіді та її повторне використання пізніше приписує нове з'єднання старому застосунку, що гірше, ніж повідомити про невідомість.
Коли атрибуція не вдається, трафік усе одно потребує рішення: заблокувати там, де цього вимагає політика, дозволити лише зрозумілу вам область або показати прогалину. Цей запасний шлях належить дизайну, а не коментарю.
Облік — це інше питання
NetworkStatsManager звітує про використання за власними правилами доступу. Він може сказати, скільки UID передав за період; він не може сказати власника довільного живого пакета. Можливості мережі описують властивості мережі, а не каталог сокетів. Змішування трьох дає панелі, які виглядають узгодженими й такими не є.
Для простого випадку — визначити, які застосунки використовують тунель, — підтримувані списки дозволених і заборонених застосунків Android уже виражають політику без жодного скрапінгу. Залиште атрибуцію за потоком для випадків, яким вона справді потрібна, і закладіть бюджет на підтримувану реалізацію, яка разом обробляє напрямок, дозволи й невідомі стани.
Виберіть інструмент за завданням, потім перевірте краї
Якщо вимога — вибір тунелю, використовуйте список платформи. Якщо це жива атрибуція, використовуйте запит власника й вважайте кожну невідповідну четвірку невідомою. Якщо це історичні суми, використовуйте stats API. Потім перевірте три краї, які ламають наївні реалізації: пакет у зворотному напрямку, потік, що закривається до запиту, і викликач, який не є активним VPN.
Ніщо з цього не потребує вимкнення SELinux, отримання root на телефоні або глобально читабельних таблиць ядра. Обмеження і є суть: платформа перенесла атрибуцію за контракт, щоб відповідь була доступна компоненту, якому вона законно потрібна, і нікому іншому.