Android 10 이상에서는 /proc/net/tcp을 여는 앱이 socket 테이블 대신 접근 거부를 받습니다. VPN 인터페이스는 상관하지 않습니다. 패킷은 계속 도착합니다. 터널은 라우팅 결정이지 파일 권한이 아니기 때문입니다. 달라진 것은 더 좁은 질문에 대한 답입니다. “어느 앱이 이 연결을 소유하는가?” 대체물은 ConnectivityManager.getConnectionOwnerUid, 지원되는 연결별 조회이며, 오래된 습관과 새 API 중 무엇을 택할지는 실제로 가진 세 가지 작업 중 어느 것인지에 달려 있습니다. 패킷 수신, 흐름 귀속, 사용량 합산입니다.

소유자 질문에 답하는 두 가지 방법

API 29 이전에는 권한 있는 프로세스나 root가 커널의 socket 테이블을 읽고 네 개의 조합을 맞춰 소유 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

비교는 “옛것 대 새것”이 아닙니다. 한때 읽을 수 있는 파일 하나가 충족하던 세 가지 다른 작업을 이제 세 가지 다른 계약을 가진 세 가지 인터페이스가 충족합니다.

로컬 주소가 앱의 끝점

메서드 시그니처는 프로토콜, 로컬 socket 주소, 원격 socket 주소를 받습니다. “로컬”은 연결에서 앱 쪽을 뜻하며, 검사하는 순간 우연히 패킷의 출발지가 된 끝점이 아닙니다. 원격 서버에서 앱으로 돌아오는 패킷은 이 정의에 비추어 출발지와 목적지가 뒤바뀝니다.

ConnectivityManager cm = getSystemService(ConnectivityManager.class);
int uid = cm.getConnectionOwnerUid(
    OsConstants.IPPROTO_TCP, appLocalAddress, appRemoteAddress);

이 방향 실수가 작동하는 조회가 잘못된 답을, 또는 아무 답도 주지 않는 가장 흔한 이유입니다. 호출 전에 양쪽 끝을 기록하고 앱이 실제로 연 연결과 쌍을 대조하십시오. 주소 체계도 중요합니다. IPv4로 매핑된 주소와 네이티브 IPv6 주소는 서로 다른 tuple이며, 같은 포트의 TCP와 UDP 흐름도 마찬가지입니다.

권한 부여와 알 수 없는 결과는 계약의 일부

조회는 권한 있는 호출자로 제한되며, 해당 사용자의 활성 VPN 앱도 포함됩니다. 무관한 모니터링 앱은 다른 프로필의 연결을 열거할 수 있다고 가정할 수 없고, 권한 없는 호출은 친절한 오류 대신 보안 예외를 던질 수 있습니다. 두 결과를 모두 명시적으로 처리하십시오.

소유자가 없다는 것은 패키지 이름이 아닙니다. 패킷과 조회 사이에 흐름이 닫혔을 수 있고, tuple이 불완전할 수 있으며, 호출자에게 알 권한이 없을 수도 있습니다. 첫 성공 응답을 캐시해 나중에 재사용하면 새 연결을 오래된 앱에 귀속시키게 되어, 알 수 없음으로 보고하는 것보다 나쁩니다.

귀속이 실패해도 트래픽에는 결정이 필요합니다. 정책이 요구하는 곳에서 차단하고, 이해하는 범위만 허용하거나, 공백을 드러내십시오. 그 폴백은 설계에 속하며 주석에 속하지 않습니다.

회계는 다른 질문

NetworkStatsManager는 자체 접근 규칙에 따라 사용량을 보고합니다. 어떤 UID가 일정 기간 얼마나 전송했는지는 알려줄 수 있지만, 임의의 살아 있는 패킷의 소유자는 알려줄 수 없습니다. 네트워크 기능은 네트워크의 속성을 설명하지 socket 목록이 아닙니다. 셋을 섞으면 일관돼 보이지만 그렇지 않은 대시보드가 나옵니다.

어느 앱이 터널을 쓰는지 정하는 단순한 경우에는 Android의 지원되는 허용·차단 앱 목록이 아무것도 긁지 않고 정책을 표현합니다. 흐름별 귀속은 정말 필요한 경우를 위해 남겨 두고, 방향과 권한, 알 수 없는 상태를 함께 다루는 유지 관리되는 구현에 예산을 배정하십시오.

작업으로 도구를 고르고 경계를 시험하십시오

요구가 터널 선택이라면 플랫폼 목록을 쓰십시오. 라이브 귀속이라면 소유자 조회를 쓰고 답할 수 없는 tuple은 모두 알 수 없음으로 처리하십시오. 과거 합계라면 stats API를 쓰십시오. 그다음 순진한 구현을 깨뜨리는 세 가지 경계를 시험하십시오. 역방향 패킷, 조회 전에 닫히는 흐름, 활성 VPN이 아닌 호출자입니다.

이 중 어느 것도 SELinux를 끄거나, 휴대폰을 root하거나, 커널 테이블을 전 세계에서 읽을 수 있게 만들 필요가 없습니다. 제약이 핵심입니다. 플랫폼은 귀속을 계약 뒤로 옮겨, 정당하게 필요한 구성 요소만 답을 얻도록 했습니다.

관련 글