차단한 Android 앱이 여전히 네트워크 사용량을 보여 주는 이유
사용량 카운터는 하나의 경계에서 바이트를 측정할 뿐, 성공한 요청을 측정하지 않는다. 시간 창, UID 귀속, VPN 서비스를 소유한 앱이 무엇인지 확인하라.
Android VPN 방화벽에서 앱을 차단하면 앱은 작동을 멈추지만 사용량 행은 계속 올라간다. 그럴듯한 결론은 차단이 새어 나갔거나 카운터가 고장 났다는 것이다. 둘 다 따라오지 않는다. 사용량 기록은 어느 관측 지점에서 그 식별자에 바이트가 귀속되었다고 말할 뿐, 원격 서비스가 요청을 받아들였는지는 말하지 않는다. 어느 경계가 측정되었고 어느 식별자에 부과되었는지 알면, 두 관측은 더 이상 서로 모순되지 않는다.
카운터는 바이트를 측정하지, 전달을 측정하지 않는다
Android는 트래픽을 UID에 귀속시키고 양방향으로 계산하며, 그 계산은 앱의 요청이 성공하는 순간이 아니라 인터페이스 경계에서 이루어진다.
거부된 연결 시도도 패킷을 움직인다. SYN, 리셋 또는 뒤따르는 재전송, 그리고 둘 모두에 앞선 DNS 질의다. 아무것도 전달되지 않아도 그 바이트는 존재하며, 일부 경로에서는 어떤 필터가 판단하기 전에 이미 계산된다.
그래서 모든 요청이 실패해도 차단된 앱의 행은 늘어날 수 있다. 카운터가 답하는 것은 ‘이 식별자에 몇 바이트가 귀속되었는가’이지 ‘이 식별자가 인터넷에 도달했는가’가 아니다. 그래프는 수락된 스트림과 정중히 거부된 핸드셰이크를 구별하지 못하며, 양방향을 더한 총계도 마찬가지다.
귀속은 아이콘이 아니라 UID를 따른다
행은 UID별이며, 하나의 UID를 여러 패키지가 공유할 수 있고, 보이는 앱이 자신의 식별자로 실행되는 별도 구성 요소에 작업을 넘길 수도 있다. 공유 WebView 프로세스가 그리는 브라우저 탭, 백그라운드 동기화 서비스, 또는 기본 앱과 함께 설치된 동반 패키지는 모두 사용량 화면이 예상하지 못한 이름으로, 혹은 차단한 앱 아래에 표시할 수 있는 트래픽을 만든다. 차단된 패키지와 그 요청을 나르는 도우미는 서로 다른 두 주체이며, 규칙은 그중 하나만 다루었다.
숫자가 잘못 보이면 레이블이 아니라 UID를 비교하라. 사용량 API는 UID로 질의하며, adb shell dumpsys package <package> | grep userId=이 비교할 숫자를 출력하므로 같은 UID를 가진 두 항목은 카운터 관점에서 같은 식별자다.
시간 창이 규칙보다 앞설 수 있다
사용량 데이터는 실시간 스트리밍이 아니라 버킷으로 모여 주기적으로 질의된다. 일일 합계에는 차단을 켜기 전의 몇 분, 이전 시간의 백그라운드 동기화, 또는 이미 진행 중이던 트래픽이 포함될 수 있다. 누적 합계를 거부된 요청 하나와 비교해 그 차이를 유출이라 부르는 것은 측정 오류이지 방화벽 발견이 아니다.
더 짧은 창과 깨끗한 시작을 사용하라. 규칙을 바꾼 시각을 기록하고 수집 간격 하나를 기다린 뒤 그 이후 데이터만 보라. 규칙 이후에 시작되는 창에서 합계가 계속 오르면 조사할 것이 있고, 오르기를 멈추면 이전 증가분은 표시되던 이력이었다.
VPN 서비스가 설정한 것과 다를 수 있다
VPN 방화벽은 자기 서비스가 나르는 트래픽만 본다. 다른 VPN 앱이 시작해 활성 연결을 가져가면, 그 인터페이스가 설정에 남아 있어도 필터는 더 이상 경로에 없다. 앱별 우회 목록도 의도적으로 같은 일을 하며, 앱을 제외한 프로필은 그 앱이 이후에 작동한다면 유출이 아니다.
IPv4, IPv6, DNS도 갈릴 수 있다. IPv4를 처리하는 필터가 IPv6나 사설 DNS 리졸버를 밖에 둘 수 있어, 앱 트래픽의 일부가 규칙이 설명한 적 없는 경로를 따른다. 무언가를 다시 쓰기 전에 지금 VPN을 소유한 앱과 제외 목록의 내용을 확인하라:
adb shell settings get secure always_on_vpn_app
adb shell dumpsys connectivity
adb shell dumpsys netstats detail
새 요청 하나를 시험하고 네 가지 사실을 적어라
무해한 요청을 하나 골라 한 번 실행하고 네 가지 사실을 적어라. 유효한 규칙, 앱의 UID, 네트워크 상태, 시각이다. 그다음 필터가 그 흐름을 기록했는지, 목적지나 게이트웨이가 같은 연결을 보았는지 확인하라. 양쪽에서 동일하게 확인된 하나의 흐름이 증거이며, 집계된 총계는 증거가 아니다.
방어 가능한 결론은 무엇이 거부되었고 어떤 관측이 그것을 확인했는지를 밝힌다. ‘카운터가 올랐다’는 발견이 아니며, ‘모든 사용량 카운터는 가짜다’도 발견이 아니다. OSI 계층 구호로는 결론이 나지 않는다. 정직한 답은 카운터와 차단이 서로 다른 것을 설명하면서 둘 다 옳을 수 있다는 것이다. Android의 NetworkStatsManager 참조 문서는 그 버킷이 어떻게 질의되고 귀속되는지 기술한다.