VPN

컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요.

Android에서는 개발자가 가상 사설망(VPN) 솔루션을 만들 수 있도록 API를 제공합니다. 이 가이드를 읽고 나면 Android 지원 기기를 위한 고유한 VPN 클라이언트를 개발하고 테스트하는 방법을 알 수 있습니다.

개요

VPN을 사용하면 네트워크에 실제로 연결되지 않은 기기가 네트워크에 안전하게 액세스할 수 있습니다.

Android에는 레거시 VPN이라고도 불리는 내장 VPN(PPTP 및 L2TP/IPSec) 클라이언트가 포함되어 있습니다. Android 4.0(API 레벨 14)에는 앱 개발자가 자체 VPN 솔루션을 제공할 수 있도록 API가 도입되었습니다. 따라서 개발자는 사용자가 기기에 설치하는 앱에 VPN 솔루션을 패키징할 수 있습니다. 개발자는 일반적으로 다음 중 한 가지 이유로 VPN 앱을 빌드합니다.

  • 내장 클라이언트에서 지원하지 않는 VPN 프로토콜을 제공하기 위해
  • 사용자가 복잡한 구성 없이 VPN 서비스에 연결할 수 있도록 지원하기 위해

이 가이드의 나머지 부분에서는 VPN 앱(연결 유지앱별 VPN)을 개발하는 방법을 설명하며 내장 VPN 클라이언트는 다루지 않습니다.

사용자 환경

Android는 사용자가 VPN 솔루션을 구성, 시작 및 중지할 수 있도록 사용자 인터페이스(UI)를 제공합니다. 또한 시스템 UI는 기기 사용자가 활성 VPN 연결을 인식할 수 있게 합니다. Android는 VPN 연결을 위한 다음 UI 구성요소를 표시합니다.

  • VPN 앱이 처음 활성화되기 전에 먼저 시스템에는 연결 요청 대화상자가 표시됩니다. 대화상자는 기기 사용자에게 VPN을 신뢰하고 요청을 수락하는지 확인하는 메시지를 표시합니다.
  • VPN 설정 화면(설정 > 네트워크 및 인터넷 > VPN)에는 사용자가 연결 요청을 수락한 VPN 앱이 표시됩니다. 시스템 옵션을 구성하거나 VPN을 삭제할 수 있는 버튼이 있습니다.
  • 연결이 활성화되면 빠른 설정 트레이에 정보 패널이 표시됩니다. 라벨을 탭하면 자세한 정보 및 설정 링크가 있는 대화상자가 표시됩니다.
  • 상태 표시줄에는 활성 연결을 나타내는 VPN(키) 아이콘이 있습니다.

또한 앱은 기기 사용자가 서비스 옵션을 구성할 수 있도록 UI를 제공해야 합니다. 예를 들어 솔루션에서 계정 인증 설정을 캡처해야 할 수 있습니다. 이때 앱에 다음 UI가 표시되어야 합니다.

  • 연결을 수동으로 시작하고 중지할 수 있는 컨트롤: 연결 유지 VPN으로 필요할 때 연결할 수 있지만 이 컨트롤을 통해 사용자는 VPN을 처음 사용할 때 연결을 구성할 수 있습니다.
  • 서비스가 활성 상태일 때 닫을 수 없는 알림: 알림은 연결 상태를 표시하거나 네트워크 통계와 같은 추가 정보를 제공할 수 있습니다. 알림을 탭하면 앱이 포그라운드로 전환됩니다. 알림은 서비스가 비활성화된 후에 삭제되어야 합니다.

VPN 서비스

앱은 사용자(또는 직장 프로필)의 시스템 네트워킹을 VPN 게이트웨이에 연결합니다. 각 사용자(또는 직장 프로필)는 서로 다른 VPN 앱을 실행할 수 있습니다. 개발자는 시스템이 VPN을 시작 및 중지하고 연결 상태를 추적하는 데 사용하는 VPN 서비스를 만들 수 있습니다. VPN 서비스는 VpnService에서 상속받습니다.

또한 서비스는 VPN 게이트웨이 연결 및 로컬 기기 인터페이스를 위한 컨테이너 역할도 합니다. 서비스 인스턴스는 VpnService.Builder 메서드를 호출하여 새 로컬 인터페이스를 설정합니다.

그림 1. VpnService가 Android 네트워킹을 VPN 게이트웨이에 연결하는 방식
VpnService가 시스템 네트워킹에서 로컬 TUN 인터페이스를 생성하는 방식을 보여주는 블록 아키텍처 다이어그램

앱은 다음 데이터를 전송하여 기기를 VPN 게이트웨이에 연결합니다.

  • 로컬 인터페이스의 파일 설명어에서 발신 IP 패킷을 읽고 암호화한 후 VPN 게이트웨이로 보냅니다.
  • 수신 패킷(VPN 게이트웨이에서 수신하여 복호화한 패킷)을 로컬 인터페이스의 파일 설명어에 씁니다.

사용자 또는 프로필당 하나의 활성 서비스만 가능합니다. 새 서비스를 시작하면 기존 서비스가 자동으로 중지됩니다.

서비스 추가

앱에 VPN 서비스를 추가하려면 VpnService에서 상속받는 Android 서비스를 생성하세요. 다음 추가사항을 통해 앱 manifest 파일에 VPN 서비스를 선언하세요.

  • 시스템만 서비스에 결합될 수 있도록 BIND_VPN_SERVICE 권한으로 서비스를 보호합니다.
  • 시스템이 서비스를 찾을 수 있도록 "android.net.VpnService" 인텐트 필터를 사용하여 서비스를 알립니다.

다음은 앱 manifest 파일에서 서비스를 선언하는 방법을 보여주는 예입니다.

<service android:name=".MyVpnService"
             android:permission="android.permission.BIND_VPN_SERVICE">
         <intent-filter>
             <action android:name="android.net.VpnService"/>
         </intent-filter>
    </service>
    

이제 앱에서 서비스를 선언했으므로 시스템에서 필요할 때 앱의 VPN 서비스를 자동으로 시작하고 중지할 수 있습니다. 예를 들어 시스템은 연결 유지 VPN을 실행할 때 서비스를 제어합니다.

서비스 준비

앱이 사용자의 현재 VPN 서비스가 되도록 준비하려면 VpnService.prepare()를 호출해야 합니다. 기기 사용자가 아직 앱의 권한을 부여하지 않았다면 메서드는 활동 인텐트를 반환합니다. 이 인텐트를 사용하여 권한을 요청하는 시스템 활동을 시작합니다. 시스템은 카메라 또는 연락처 액세스와 같은 다른 권한 대화상자와 유사한 대화상자를 표시합니다. 앱이 이미 준비되었다면 메서드는 null을 반환합니다.

하나의 앱만 현재 준비된 VPN 서비스가 될 수 있습니다. 앱이 마지막으로 메서드를 호출한 후 사용자가 VPN 서비스와 다른 앱을 설정했을 수 있으므로 항상 VpnService.prepare()를 호출해야 합니다. 자세히 알아보려면 서비스 수명 주기 섹션을 참조하세요.

서비스 연결

서비스가 실행 중이면 VPN 게이트웨이에 연결된 새 로컬 인터페이스를 설정할 수 있습니다. 권한을 요청하고 VPN 게이트웨이에 서비스를 연결하려면 다음 순서에 따라 단계를 완료해야 합니다.

  1. VpnService.prepare()를 호출하여 권한을 요청합니다(필요 시).
  2. VpnService.protect()를 호출하여 앱의 터널 소켓을 시스템 VPN 외부에 유지하고 순환 연결을 방지합니다.
  3. DatagramSocket.connect()를 호출하여 앱의 터널 소켓을 VPN 게이트웨이에 연결합니다.
  4. VpnService.Builder 메서드를 호출하여 VPN 트래픽에 맞춰 기기의 새 로컬 TUN 인터페이스를 구성합니다.
  5. 시스템이 로컬 TUN 인터페이스를 설정하고 인터페이스를 통해 트래픽 라우팅을 시작하도록 VpnService.Builder.establish()를 호출합니다.

VPN 게이트웨이는 일반적으로 핸드셰이크 동안에 로컬 TUN 인터페이스 관련 설정을 제안합니다. 앱은 다음 샘플에서와 같이 VpnService.Builder 메서드를 호출하여 서비스를 구성합니다.

Kotlin

// Configure a new interface from our VpnService instance. This must be done
    // from inside a VpnService.
    val builder = Builder()

    // Create a local TUN interface using predetermined addresses. In your app,
    // you typically use values returned from the VPN gateway during handshaking.
    val localTunnel = builder
            .addAddress("192.168.2.2", 24)
            .addRoute("0.0.0.0", 0)
            .addDnsServer("192.168.1.1")
            .establish()

자바

// Configure a new interface from our VpnService instance. This must be done
    // from inside a VpnService.
    VpnService.Builder builder = new VpnService.Builder();

    // Create a local TUN interface using predetermined addresses. In your app,
    // you typically use values returned from the VPN gateway during handshaking.
    ParcelFileDescriptor localTunnel = builder
        .addAddress("192.168.2.2", 24)
        .addRoute("0.0.0.0", 0)
        .addDnsServer("192.168.1.1")
        .establish();

앱별 VPN 섹션의 예에서는 추가 옵션이 포함된 IPv6 구성을 보여줍니다. 새 인터페이스를 설정하기 전에 다음 VpnService.Builder 값을 추가해야 합니다.

addAddress()
시스템이 로컬 TUN 인터페이스 주소로 할당하는 서브넷 마스크와 함께 하나 이상의 IPv4 또는 IPv6 주소를 추가하세요. 일반적으로 앱은 핸드셰이크 동안에 VPN 게이트웨이에서 IP 주소와 서브넷 마스크를 수신합니다.
addRoute()
시스템이 VPN 인터페이스를 통해 트래픽을 전송하도록 하려면 경로를 하나 이상 추가하세요. 목적지 주소별로 필터를 라우팅합니다. 전체 트래픽을 허용하려면 0.0.0.0/0 또는 ::/0과 같은 열린 경로를 설정하세요.

establish() 메서드는 앱이 인터페이스 버퍼에서 패킷을 읽고 쓰는 데 사용하는 ParcelFileDescriptor 인스턴스를 반환합니다. 앱이 준비되지 않았거나 누군가 권한을 취소했다면 establish() 메서드는 null을 반환합니다.

서비스 수명 주기

앱은 시스템에서 선택한 VPN 상태 및 활성 연결 상태를 추적해야 합니다. 기기 사용자가 변경사항을 계속 인식하도록 앱의 사용자 인터페이스(UI)를 업데이트합니다.

서비스 시작

VPN 서비스는 다음과 같은 방법으로 시작될 수 있습니다.

  • 일반적으로 사용자가 연결 버튼을 탭하여 앱이 서비스를 시작합니다.
  • 연결 유지 VPN이 켜지므로 시스템이 서비스를 시작합니다.

앱은 startService()에 인텐트를 전달하여 VPN 서비스를 시작합니다. 자세히 알아보려면 서비스 시작을 참조하세요.

시스템은 onStartCommand()를 호출하여 백그라운드에서 서비스를 시작합니다. 그러나 버전 8.0(API 레벨 26) 이상의 Android는 백그라운드 앱에 제한을 둡니다. 이러한 API 레벨을 지원한다면 Service.startForeground()를 호출함으로써 서비스를 포그라운드로 전환해야 합니다. 자세히 알아보려면 포그라운드에서 서비스 실행을 참조하세요.

서비스 중지

기기 사용자는 앱의 UI를 사용하여 서비스를 중지할 수 있습니다. 연결을 종료하는 대신 서비스를 중지할 수 있습니다. 또한 기기 사용자가 설정 앱의 VPN 화면에서 다음을 실행할 때 시스템이 활성 연결을 중지합니다.

  • VPN 앱 연결 해제 또는 삭제
  • 활성 연결을 위한 연결 유지 VPN 끄기

시스템에서 서비스의 onRevoke() 메서드를 호출하지만 이 호출은 기본 스레드에서 발생하지 않을 수 있습니다. 시스템이 이 메서드를 호출할 때 대체 네트워크 인터페이스는 이미 트래픽을 라우팅하고 있습니다. 다음 리소스를 안전하게 폐기할 수 있습니다.

연결 유지 VPN

Android는 기기가 부팅될 때 VPN 서비스를 시작하고 기기가 켜져 있는 동안 VPN 서비스 실행 상태를 유지할 수 있습니다. 이 기능을 연결 유지 VPN이라고 하며 Android 7.0(API 레벨 24) 이상에서 사용할 수 있습니다. Android가 서비스 수명 주기를 유지하지만 VPN 게이트웨이 연결을 담당하는 것은 VPN 서비스입니다. 또한 연결 유지 VPN은 VPN을 사용하지 않는 연결을 차단할 수 있습니다.

사용자 환경

Android 8.0 이상에서 시스템은 기기 사용자가 연결 유지 VPN을 인식할 수 있도록 다음 대화상자를 표시합니다.

  • 연결 유지 VPN 연결이 끊어지거나 불가능할 때 사용자에게 닫을 수 없는 알림이 표시됩니다. 알림을 탭하면 자세한 내용을 설명하는 대화상자가 표시됩니다. VPN이 다시 연결되거나 누군가 연결 유지 VPN 옵션을 끄면 알림이 사라집니다.
  • 연결 유지 VPN을 사용하면 기기 사용자가 VPN을 사용하지 않는 네트워크 연결을 차단할 수 있습니다. 이 옵션을 켜면 설정 앱은 VPN 연결 전에 인터넷에 연결되어 있지 않다는 경고를 사용자에게 표시합니다. 설정 앱은 기기 사용자에게 계속할지 또는 취소할지 묻는 메시지를 표시합니다.

사용자가 아닌 시스템이 연결 유지 옵션의 연결을 시작하고 중지하기 때문에 개발자는 다음과 같이 앱의 동작 및 사용자 인터페이스를 조정해야 합니다.

  1. 시스템 및 설정 앱이 연결을 제어하므로 연결을 끊는 UI를 사용 중지합니다.
  2. 각 앱 시작 사이에 구성을 저장하고 최신 설정으로 연결을 구성합니다. 요청 시 시스템에서 앱을 시작하므로 기기 사용자가 때로 연결을 구성하지 못할 수 있습니다.

또한 관리 구성을 사용하여 연결을 구성할 수 있습니다. IT 관리자는 관리 구성을 사용하여 VPN을 원격으로 구성할 수 있습니다.

연결 유지 감지

Android에는 시스템이 VPN 서비스를 시작했는지 확인하는 API가 포함되어 있지 않습니다. 그러나 앱이 서비스 인스턴스를 시작하고 이 인스턴스에 플래그를 지정하면, 개발자는 시스템이 연결 유지 VPN의 플래그 미지정 서비스를 시작했다고 추정할 수 있습니다. 예를 들면 다음과 같습니다.

  1. VPN 서비스를 시작하기 위한 Intent 인스턴스를 생성합니다.
  2. 인텐트에 엑스트라를 추가하여 VPN 서비스에 플래그를 지정합니다.
  3. 서비스의 onStartCommand() 메서드에서 intent 인수의 extra에 있는 플래그를 찾습니다.

차단된 연결

기기 사용자(또는 IT 관리자)는 전체 트래픽이 VPN을 사용하도록 할 수 있습니다. 시스템은 VPN을 사용하지 않는 네트워크 트래픽을 차단합니다. 기기 사용자는 설정의 VPN 옵션 패널에서 VPN 없는 연결 차단 스위치를 찾을 수 있습니다.

연결 유지 선택 해제

앱이 현재 연결 유지 VPN을 지원할 수 없다면 SERVICE_META_DATA_SUPPORTS_ALWAYS_ON 서비스 메타데이터를 false로 설정하여 연결 유지 VPN을 선택 해제할 수 있습니다(Android 8.1 이상). 다음 앱 manifest 예는 메타데이터 요소를 추가하는 방법을 보여줍니다.

<service android:name=".MyVpnService"
             android:permission="android.permission.BIND_VPN_SERVICE">
         <intent-filter>
             <action android:name="android.net.VpnService"/>
         </intent-filter>
         <meta-data android:name="android.net.VpnService.SUPPORTS_ALWAYS_ON"
                 android:value=false/>
    </service>
    

앱에서 연결 유지 VPN을 선택 해제하면 시스템은 설정에서 UI 컨트롤 옵션을 사용 중지합니다.

앱별 VPN

VPN 앱에서는 설치된 앱 중 어떤 앱이 VPN 연결을 통해 트래픽을 전송할지 필터링할 수 있습니다. 허용 목록과 허용되지 않는 목록 중 하나만 만들 수 있으며, 둘 다 만들 수는 없습니다. 허용 목록 또는 허용되지 않는 목록을 만들지 않으면 시스템은 VPN을 통해 모든 네트워크 트래픽을 전송합니다.

VPN 앱에서 연결을 설정하기 전에 먼저 목록을 설정해야 합니다. 목록을 변경해야 하면 새 VPN 연결을 설정합니다. 목록에 추가할 앱은 기기에 설치되어 있어야 합니다.

Kotlin

// The apps that will have access to the VPN.
    val appPackages = arrayOf(
            "com.android.chrome",
            "com.google.android.youtube",
            "com.example.a.missing.app")

    // Loop through the app packages in the array and confirm that the app is
    // installed before adding the app to the allowed list.
    val builder = Builder()
    for (appPackage in appPackages) {
        try {
            packageManager.getPackageInfo(appPackage, 0)
            builder.addAllowedApplication(appPackage)
        } catch (e: PackageManager.NameNotFoundException) {
            // The app isn't installed.
        }
    }

    // Complete the VPN interface config.
    val localTunnel = builder
            .addAddress("2001:db8::1", 64)
            .addRoute("::", 0)
            .establish()

자바

// The apps that will have access to the VPN.
    String[] appPackages = {
        "com.android.chrome",
        "com.google.android.youtube",
        "com.example.a.missing.app"};

    // Loop through the app packages in the array and confirm that the app is
    // installed before adding the app to the allowed list.
    VpnService.Builder builder = new VpnService.Builder();
    PackageManager packageManager = getPackageManager();
    for (String appPackage: appPackages) {
      try {
        packageManager.getPackageInfo(appPackage, 0);
        builder.addAllowedApplication(appPackage);
      } catch (PackageManager.NameNotFoundException e) {
        // The app isn't installed.
      }
    }

    // Complete the VPN interface config.
    ParcelFileDescriptor localTunnel = builder
        .addAddress("2001:db8::1", 64)
        .addRoute("::", 0)
        .establish();

허용되는 앱

앱을 허용 목록에 추가하려면 VpnService.Builder.addAllowedApplication()을 호출하세요. 목록에 앱을 하나 이상 포함하면 목록의 앱만 VPN을 사용하게 됩니다. 목록에 없는 다른 모든 앱은 VPN이 실행되지 않는 것처럼 시스템 네트워크를 사용합니다. 허용 목록이 비어 있으면 모든 앱이 VPN을 사용합니다.

허용되지 않는 앱

앱을 허용되지 않는 목록에 추가하려면 VpnService.Builder.addDisallowedApplication()을 호출하세요. 허용되지 않는 앱은 VPN이 실행되지 않는 것처럼 시스템 네트워크를 사용하며 다른 모든 앱은 VPN을 사용합니다.

VPN 우회

VPN에서는 앱이 VPN을 우회하고 자체 네트워크를 선택하도록 허용할 수 있습니다. VPN을 우회하려면 VPN 인터페이스를 설정할 때 VpnService.Builder.allowBypass()를 호출해야 합니다. VPN 서비스를 시작한 후에는 이 값을 변경할 수 없습니다. 앱이 프로세스 또는 소켓을 특정 네트워크에 결합하지 않으면 앱의 네트워크 트래픽은 VPN을 통해 계속 전송됩니다.

누군가가 VPN을 통과하지 않는 트래픽을 차단하면 특정 네트워크에 결합된 앱은 연결되지 않습니다. 앱이 특정 네트워크를 통해 트래픽을 전송하려면 소켓을 연결하기 전에 먼저 ConnectivityManager.bindProcessToNetwork() 또는 Network.bindSocket()과 같은 메서드를 호출해야 합니다.

샘플 코드

Android 오픈소스 프로젝트에는 ToyVPN이라는 샘플 앱이 포함되어 있습니다. 이 앱은 VPN 서비스를 설정하고 연결하는 방법을 보여줍니다.