VPN

Android 提供的 API 可讓開發人員建立虛擬私人網路 (VPN) 解決方案閱讀本指南後,您將瞭解如何開發及測試 Android 裝置專用的 VPN 用戶端

總覽

VPN 可讓未實際連上網路的裝置安全存取 更是如此

Android 內建 (PPTP 和 L2TP/IPSec) VPN 用戶端, 稱為舊版 VPNAndroid 4.0 (API 級別 14) 導入了 API,讓該應用程式 開發人員可以自行提供 VPN 解決方案將 VPN 解決方案封裝成套件 吸引使用者安裝到裝置上的應用程式。開發人員通常會建構 VPN 出現以下任一原因:

  • 為了提供內建用戶端不支援的 VPN 通訊協定。
  • 為了協助其他使用者不必進行繁複設定程序,就能連線至 VPN 服務。

本指南的其餘部分會說明如何開發 VPN 應用程式 (包括 永久連線個別應用程式 VPN) 未涵蓋 內建 VPN 用戶端

使用者體驗

Android 提供使用者介面 (UI) 可協助他人設定、啟動 並停止 VPN 解決方案系統 UI 也會讓使用者使用裝置 且用戶端會接收到有效的 VPN 連線Android 會顯示下列應用程式的 UI 元件: VPN 連線:

  • VPN 應用程式首次啟用前,系統會先顯示 「連線要求」對話方塊。對話方塊會提示使用該裝置的使用者 確認他們信任 VPN 並接受要求。
  • VPN 設定畫面 (設定 > 網路和網際網路 > VPN) 顯示 VPN 應用程式收到連線要求。這裡有可供設定的按鈕 或清除 VPN
  • 連線處於下列狀態時,「快速設定」匣會顯示資訊面板 有效。輕觸標籤即可顯示對話方塊,當中含有更多資訊和連結 然後前往「設定」頁面
  • 狀態列會顯示 VPN (金鑰) 圖示,表示連線中。

應用程式還必須提供 UI,讓裝置使用者 設定服務選項。舉例來說,您的解決方案可能需要 擷取帳戶驗證設定。應用程式應會顯示以下 UI:

  • 用來手動啟動及停止連線的控制選項。永久連線 VPN 可在需要時連線,但使用者可以先設定連線 取得相關資訊
  • 服務啟用時,無法關閉的通知。通知 顯示連線狀態或提供更多資訊,例如網路統計資料。 使用者輕觸通知後,即可在前景執行應用程式。移除 通知。

VPN 服務

您的應用程式為使用者連結系統網路 (或利用工作 設定檔) 連線至 VPN 閘道。每位使用者 (或工作資料夾) 都能執行 不同的 VPN 應用程式您會建立一個 VPN 服務,供系統用於啟動 停止 VPN,並追蹤連線狀態。你的 VPN 服務沿用自 VpnService

這項服務也是 VPN 閘道連線的容器 本機裝置介面服務執行個體呼叫 VpnService.Builder 方法,用於建立新的本機介面。

圖 1.VpnService 如何連結 Android 至 VPN 閘道的網路
顯示 VpnService 如何建立本機 TUN 的區塊架構圖
         系統網路中的介面

您的應用程式會轉移以下資料,將裝置連線至 VPN 閘道:

  • 這個外掛程式能從本機介面的檔案描述元讀取傳出 IP 封包,並加密 並傳送至 VPN 閘道
  • 這個外掛程式能將傳入的封包 (從 VPN 閘道接收及解密) 寫入 本機介面的檔案描述元。
,瞭解如何調查及移除這項存取權。

每位使用者或每個設定檔只能啟用一項服務。啟動新服務 可自動停止現有的服務

新增服務

如要為應用程式新增 VPN 服務,請建立 VpnService。在應用程式中宣告 VPN 服務 加入以下程式碼的資訊清單檔案:

  • 使用 BIND_VPN_SERVICE 保護服務 權限,確保只有系統能繫結至您的服務。
  • 使用 "android.net.VpnService" 意圖篩選器來宣傳服務,以便 系統就能找到您的服務

以下範例說明如何在應用程式資訊清單檔案中宣告服務:

<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 服務。一律撥打電話 VpnService.prepare(),因為使用者可能有不同設定 設為 VPN 服務。詳情請參閱: 「服務生命週期」部分。

連結服務

當服務開始執行後,您可以建立新的本機介面 連線至 VPN 閘道要求權限並連線至服務 VPN 閘道,您必須按照下列順序完成步驟:

  1. 呼叫 VpnService.prepare() 要求權限 (當 這類事件)。
  2. 呼叫 VpnService.protect() 以保留應用程式的通道通訊端 避免循環連線
  3. 呼叫 DatagramSocket.connect() 以連接應用程式的通道 VPN 閘道的一個通訊端
  4. 呼叫 VpnService.Builder 方法來設定新的本機 上的 TUN 介面 VPN 流量
  5. 呼叫 VpnService.Builder.establish(),讓系統順利評估 建立本機 TUN 介面,並開始將流量 存取 API

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()

Java

// 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()
新增至少一個 IPv4 或 IPv6 位址,以及系統的子網路遮罩 指派為本機 TUN 介面位址。應用程式通常會接收 握手期間來自 VPN 閘道的位址和子網路遮罩。
addRoute()
如果您希望系統透過 VPN 傳送流量,請至少新增一條路徑 存取 API依據目的地地址篩選路徑。如要接受所有流量,請設定 開啟路線,例如 0.0.0.0/0::/0

establish() 方法會傳回 應用程式用來讀取及寫入的 ParcelFileDescriptor 例項 傳入及傳出介面緩衝區的封包。establish() 方法會傳回 null。 權限。

服務生命週期

您的應用程式應追蹤系統所選 VPN 的狀態和任何啟用中狀態 連線狀態。更新應用程式的使用者介面 (UI),讓使用者繼續使用 清楚掌握任何變更

啟動服務

你可以透過以下幾種方式啟動 VPN 服務:

  • 您的應用程式啟動服務,通常是因為使用者輕觸了連線按鈕。
  • 系統因為永久連線的 VPN 已開啟,所以系統會啟動服務。

您的應用程式會將意圖傳遞至 startService()。詳情請參閱這篇文章 服務

系統會在背景啟動您的服務 onStartCommand()。不過,Android 設下限制 8.0 (API 級別 26) 以上版本中的背景應用程式。如果支援這些 API 級別,您必須呼叫 Service.startForeground()。如需瞭解更多資訊,請參閱執行 啟動服務

停止服務

裝置的使用者可以透過應用程式的 UI 停止服務。停止 服務,而非只是關閉連線。同時,也會停止執行中的 連線時,裝置使用者在 VPN 畫面中執行下列操作時連線 然後關閉裝置的「設定」應用程式:

  • 中斷或清除 VPN 應用程式
  • 關閉有效連線的永久連線 VPN。

系統會呼叫服務的 onRevoke() 方法,但這場呼叫 而不是在主執行緒上發生當系統呼叫此方法時, 替代網路介面已負責轉送流量。您可以放心丟棄 以下資源:

永久連線 VPN

Android 可以在裝置啟動時開啟 VPN 服務,並在啟動後保持運作 。這項功能稱為「永久連線 VPN」, Android 7.0 (API 級別 24) 以上版本。Android 負責維護服務 也就是私人 VPN 閘道 以獲得最佳效能和最安全的連線此外,永久連線 VPN 也可能會封鎖未使用 VPN 的連線。

使用者體驗

在 Android 8.0 以上版本中,系統會顯示下列對話方塊, 裝置的使用者是否知道永久連線 VPN:

  • 永久連線 VPN 連線中斷或無法連線時,使用者會看到 此通知不得關閉。輕觸通知會顯示對話方塊 。VPN 重新連線或他人時,通知就會消失 關閉永久連線的 VPN 選項。
  • 永久連線 VPN 可讓使用者封鎖任何網路 未使用 VPN 連線啟用這個選項後 應用程式在 VPN 開啟前,警告使用者未連上網際網路 以獲得最佳效能和最安全的連線「設定」應用程式會提示使用裝置的使用者繼續操作,或是 cancel。

因為系統 (而非使用者) 會啟動及停止全天候連線, 請調整應用程式的行為和使用者介面:

  1. 停用所有因為系統和設定而中斷連線的 UI 應用程式控制連結。
  2. 儲存每個應用程式啟動之間的任何設定,並設定與 最新的設定。系統會按照需求啟動應用程式,因此使用者 使用裝置有時可能不想設定連線。

您也可以使用受管理的設定來設定 以獲得最佳效能和最安全的連線受管理設定可協助 IT 管理員從遠端設定 VPN。

偵測螢幕長亮模式

Android 不包含用於確認系統是否啟動 VPN 的 API 課程中也會快速介紹 Memorystore 這是 Google Cloud 的全代管 Redis 服務不過,當應用程式標記啟動的任何服務執行個體時,您可以假設 系統針對永久連線 VPN 啟動了未標記的服務。範例如下:

  1. 建立 Intent 執行個體來啟動 VPN 服務。
  2. 在意圖中加入額外項目,藉此標記 VPN 服務。
  3. 在服務的 onStartCommand() 方法中,找出 加上旗標intent

已封鎖的連線

使用裝置的使用者或 IT 管理員可強制所有流量使用 VPN。 系統會封鎖所有未使用 VPN 的網路流量。使用 裝置可以在 VPN 選項中找到「封鎖沒有 VPN 的連線」切換鈕 面板。

停用「一律開啟」功能

如果應用程式目前不支援永久連線 VPN,你可以選擇停用 (適用於 Android 裝置) 8.1 以上版本),方法是將 SERVICE_META_DATA_SUPPORTS_ALWAYS_ON敬上 傳送至 false。以下應用程式資訊清單範例說明如何新增 中繼資料元素:

<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()

Java

// 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,在下列情況下呼叫 VpnService.Builder.allowBypass() 建立 VPN 介面開始評估後即無法變更這個值 以及 VPN 服務如果應用程式未將程序或通訊端繫結至特定的 網路,應用程式的網路流量仍會透過 VPN 繼續。

繫結特定網路的應用程式在有人時沒有連線 會封鎖未經過 VPN 的流量。為了透過特定廣告活動 網路、應用程式呼叫方法,例如 ConnectivityManager.bindProcessToNetwork()Network.bindSocket() 後再連結通訊端。

程式碼範例

Android 開放原始碼計畫包含名為 ToyVPN 的範例應用程式。 這個應用程式說明如何設定及連結 VPN 服務。