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
方法,用於建立新的本機介面。
您的應用程式會轉移以下資料,將裝置連線至 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 閘道,您必須按照下列順序完成步驟:
- 呼叫
VpnService.prepare()
要求權限 (當 這類事件)。 - 呼叫
VpnService.protect()
以保留應用程式的通道通訊端 避免循環連線 - 呼叫
DatagramSocket.connect()
以連接應用程式的通道 VPN 閘道的一個通訊端 - 呼叫
VpnService.Builder
方法來設定新的本機 上的 TUN 介面 VPN 流量 - 呼叫
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 閘道的受保護的通道通訊端
DatagramSocket.close()
。 - 呼叫即可關閉 Parcel 檔案描述元 (無需清空檔案),請呼叫
ParcelFileDescriptor.close()
。
永久連線 VPN
Android 可以在裝置啟動時開啟 VPN 服務,並在啟動後保持運作 。這項功能稱為「永久連線 VPN」, Android 7.0 (API 級別 24) 以上版本。Android 負責維護服務 也就是私人 VPN 閘道 以獲得最佳效能和最安全的連線此外,永久連線 VPN 也可能會封鎖未使用 VPN 的連線。
使用者體驗
在 Android 8.0 以上版本中,系統會顯示下列對話方塊, 裝置的使用者是否知道永久連線 VPN:
- 永久連線 VPN 連線中斷或無法連線時,使用者會看到 此通知不得關閉。輕觸通知會顯示對話方塊 。VPN 重新連線或他人時,通知就會消失 關閉永久連線的 VPN 選項。
- 永久連線 VPN 可讓使用者封鎖任何網路 未使用 VPN 連線啟用這個選項後 應用程式在 VPN 開啟前,警告使用者未連上網際網路 以獲得最佳效能和最安全的連線「設定」應用程式會提示使用裝置的使用者繼續操作,或是 cancel。
因為系統 (而非使用者) 會啟動及停止全天候連線, 請調整應用程式的行為和使用者介面:
- 停用所有因為系統和設定而中斷連線的 UI 應用程式控制連結。
- 儲存每個應用程式啟動之間的任何設定,並設定與 最新的設定。系統會按照需求啟動應用程式,因此使用者 使用裝置有時可能不想設定連線。
您也可以使用受管理的設定來設定 以獲得最佳效能和最安全的連線受管理設定可協助 IT 管理員從遠端設定 VPN。
偵測螢幕長亮模式
Android 不包含用於確認系統是否啟動 VPN 的 API 課程中也會快速介紹 Memorystore 這是 Google Cloud 的全代管 Redis 服務不過,當應用程式標記啟動的任何服務執行個體時,您可以假設 系統針對永久連線 VPN 啟動了未標記的服務。範例如下:
- 建立
Intent
執行個體來啟動 VPN 服務。 - 在意圖中加入額外項目,藉此標記 VPN 服務。
- 在服務的
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 服務。