В Android 10 и новее приложение, открывающее /proc/net/tcp, получает отказ в доступе вместо таблицы сокетов. Интерфейсу VPN это безразлично: пакеты продолжают приходить, потому что туннель — это решение о маршрутизации, а не файловое разрешение. Изменился ответ на более узкий вопрос: «какое приложение владеет этим соединением?» Замена — ConnectivityManager.getConnectionOwnerUid, поддерживаемый запрос для каждого соединения, а выбор между старой привычкой и новым API зависит от того, какая из трёх задач у вас действительно есть: приём пакетов, атрибуция потока или суммирование расхода.

Два способа ответить на вопрос о владельце

До API 29 привилегированный процесс или root мог читать таблицы сокетов ядра, сопоставлять четвёрку и узнавать UID владельца. Это давало инвентарь всего устройства в одном файле. С API 29 представление файловой системы ограничено, а поддерживаемый путь — запрос для каждого соединения с протоколом, локальным адресом и удалённым адресом.

QuestionLegacy socket tablesgetConnectionOwnerUid
Who owns this flow?Scan and match the tupleAsk once per tuple
Device-wide inventory?Yes, when readableNo, one flow at a time
Historical usage?NoNo; that is NetworkStatsManager
Works without root?No on modern releasesYes, 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 на телефоне или глобально читаемых таблиц ядра. Ограничение и есть суть: платформа перенесла атрибуцию за контракт, чтобы ответ был доступен компоненту, которому он законно нужен, и никому больше.

Похожие статьи