Android 提供 API,可讓開發人員建立虛擬私人網路 (VPN) 解決方案。閱讀本指南後,您將瞭解如何開發及測試 Android 裝置專用的 VPN 用戶端。
總覽
VPN 可讓不在網路上的裝置安全存取網路。
Android 內建 (PPTP 和 L2TP/IPSec) VPN 用戶端,有時稱為「舊版 VPN」。Android 4.0 (API 級別 14) 推出 API,讓應用程式開發人員提供自己的 VPN 解決方案。您可以將 VPN 解決方案封裝成應用程式,讓使用者安裝到裝置上。開發人員通常會基於下列其中一個原因建構 VPN 應用程式:
- 為了提供內建用戶端不支援的 VPN 通訊協定。
- 為了協助其他使用者不必進行繁複設定程序,就能連線至 VPN 服務。
本指南其餘部分將說明如何開發 VPN 應用程式 (包括全天候運作和個別應用程式 VPN),不涵蓋內建 VPN 用戶端。
使用者體驗
Android 提供使用者介面 (UI),協助使用者設定、啟動及停止 VPN 解決方案。系統 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 方法,建立新的本機介面。
VpnService 如何將 Android 網路連線至 VPN 閘道應用程式會傳輸下列資料,將裝置連線至 VPN 閘道:
- 從本機介面的檔案描述元讀取傳出的 IP 封包,加密後傳送至 VPN 閘道。
- 將傳入封包 (從 VPN 閘道接收並解密) 寫入本機介面的檔案描述元。
每位使用者或設定檔只能有一個有效的服務。啟動新服務會自動停止現有服務。
新增服務
如要在應用程式中新增 VPN 服務,請建立繼承 VpnService 的 Android 服務。在應用程式資訊清單檔案中宣告 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 服務。詳情請參閱「Service 生命週期」一節。
連結服務
服務開始運作後,您可以建立連至 VPN 閘道的新本機介面。如要要求權限並將服務連線至 VPN 閘道,請按照下列順序完成步驟:
- 呼叫
VpnService.prepare()要求權限 (如有需要)。 - 呼叫
VpnService.protect(),將應用程式的通道通訊端保留在系統 VPN 之外,避免循環連線。 - 呼叫
DatagramSocket.connect(),將應用程式的通道通訊端連線至 VPN 閘道。 - 呼叫
VpnService.Builder方法,在裝置上為 VPN 流量設定新的本機 TUN 介面。 - 呼叫
VpnService.Builder.establish(),讓系統建立本機 TUN 介面,並開始透過該介面轉送流量。
VPN 閘道通常會在交握期間建議本機 TUN 介面的設定。應用程式會呼叫 VpnService.Builder 方法來設定服務,如以下範例所示:
// 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()
「Per-app VPN」一節中的範例顯示包含更多選項的 IPv6 設定。您必須先新增下列 VpnService.Builder 值,才能建立新介面:
addAddress()- 新增至少一個 IPv4 或 IPv6 位址,以及系統指派為本機 TUN 介面位址的子網路遮罩。應用程式通常會在交握期間,從 VPN 閘道接收 IP 位址和子網路遮罩。
addRoute()- 如要讓系統透過 VPN 介面傳送流量,請至少新增一條路由。路徑會依目的地地址篩選。如要接受所有流量,請設定開放路徑,例如
0.0.0.0/0或::/0。
establish() 方法會傳回 ParcelFileDescriptor 執行個體,應用程式會使用該執行個體,在介面的緩衝區中讀取及寫入封包。如果應用程式尚未準備就緒,或有人撤銷權限,establish() 方法會傳回 null。
服務生命週期
應用程式應追蹤系統所選 VPN 和任何有效連線的狀態。更新應用程式的使用者介面 (UI),讓裝置使用者瞭解任何變更。
啟動服務
您可以透過下列方式啟動 VPN 服務:
- 應用程式啟動服務,通常是因為使用者輕觸了連線按鈕。
- 系統會啟動服務,因為永久連線 VPN 已開啟。
應用程式會將意圖傳遞至 startService(),啟動 VPN 服務。詳情請參閱「啟動服務」。
系統會呼叫 onStartCommand(),在背景啟動服務。不過,Android 會在 8.0 版 (API 級別 26) 以上版本中對背景應用程式設下限制。如果您支援這些 API 級別,則需要呼叫 Service.startForeground(),將服務轉換至前景。詳情請參閱「在前景執行服務」。
停止服務
裝置使用者可以透過應用程式 UI 停止服務。請停止服務,而非只是關閉連線。當裝置使用者在「設定」應用程式的 VPN 畫面中執行下列操作時,系統也會停止使用中的連線:
- 中斷連線或清除 VPN 應用程式
- 關閉使用中連線的永久連線 VPN
系統會呼叫服務的 onRevoke() 方法,但這項呼叫不一定會在主執行緒上執行。系統呼叫這個方法時,替代網路介面已在轉送流量。您可以放心地捨棄下列資源:
- 呼叫
DatagramSocket.close(),關閉 VPN 閘道的受保護通道通訊端。 - 呼叫
ParcelFileDescriptor.close()關閉 Parcel 檔案描述元 (不必清空)。
永久連線 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 尚未連線,使用者會在「設定」應用程式中看到未連上網路連線的警告。「設定」應用程式會提示裝置使用者繼續或取消。
由於系統 (而非使用者) 會啟動及停止全天候運作連線,因此您需要調整應用程式的行為和使用者介面:
- 停用任何中斷連線的 UI,因為連線是由系統和「設定」應用程式控制。
- 在每次啟動應用程式時儲存設定,並使用最新設定建立連線。由於系統會視需要啟動應用程式,因此使用裝置的使用者不一定會想設定連線。
您也可以使用受管理的設定來設定連線。IT 管理員可透過受管理設定,從遠端設定 VPN。
偵測螢幕長亮模式
Android 未提供 API 來確認系統是否已啟動 VPN 服務。不過,如果應用程式會標記啟動的任何服務執行個體,您就可以假設系統啟動了未標記的服務,用於全天候運作 VPN。範例如下:
- 建立
Intent執行個體,啟動 VPN 服務。 - 在意圖中加入額外資訊,標記 VPN 服務。
- 在服務的
onStartCommand()方法中,尋找intent引數額外項目中的標記。
封鎖的連線
裝置使用者 (或 IT 管理員) 可以強制所有流量使用 VPN。系統會封鎖所有不使用 VPN 的網路流量。裝置使用者可以在「設定」的 VPN 選項面板中,找到「封鎖沒有 VPN 的連線」切換鈕。
選擇退出全天候運作
如果應用程式目前不支援永久連線 VPN,您可以將 SERVICE_META_DATA_SUPPORTS_ALWAYS_ON 服務中繼資料設為 false,選擇不採用 (Android 8.1 以上版本)。以下應用程式資訊清單範例說明如何新增中繼資料元素:
<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 連線。將應用程式加入清單時,該應用程式必須已安裝在裝置上。
// 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()
允許的應用程式
如要將應用程式加入許可清單,請呼叫 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 服務。