Android VPN 귀속: getConnectionOwnerUid 대 /proc/net
Android 10은 앱에서 /proc/net을 숨깁니다. VPN은 여전히 패킷을 받지만 귀속은 자체 tuple과 권한 규칙을 가진 getConnectionOwnerUid로 옮겨갑니다.
Android 10 이상에서는 /proc/net/tcp을 여는 앱이 socket 테이블 대신 접근 거부를 받습니다. VPN 인터페이스는 상관하지 않습니다. 패킷은 계속 도착합니다. 터널은 라우팅 결정이지 파일 권한이 아니기 때문입니다. 달라진 것은 더 좁은 질문에 대한 답입니다. “어느 앱이 이 연결을 소유하는가?” 대체물은 ConnectivityManager.getConnectionOwnerUid, 지원되는 연결별 조회이며, 오래된 습관과 새 API 중 무엇을 택할지는 실제로 가진 세 가지 작업 중 어느 것인지에 달려 있습니다. 패킷 수신, 흐름 귀속, 사용량 합산입니다.
소유자 질문에 답하는 두 가지 방법
API 29 이전에는 권한 있는 프로세스나 root가 커널의 socket 테이블을 읽고 네 개의 조합을 맞춰 소유 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 |
비교는 “옛것 대 새것”이 아닙니다. 한때 읽을 수 있는 파일 하나가 충족하던 세 가지 다른 작업을 이제 세 가지 다른 계약을 가진 세 가지 인터페이스가 충족합니다.
로컬 주소가 앱의 끝점
메서드 시그니처는 프로토콜, 로컬 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하거나, 커널 테이블을 전 세계에서 읽을 수 있게 만들 필요가 없습니다. 제약이 핵심입니다. 플랫폼은 귀속을 계약 뒤로 옮겨, 정당하게 필요한 구성 요소만 답을 얻도록 했습니다.