Sieć VPN

Android udostępnia interfejsy API dla programistów do tworzenia rozwiązań wirtualnych sieci prywatnych (VPN). Z tego przewodnika dowiesz się, jak utworzyć i przetestować własnego klienta VPN na urządzenia z Androidem.

Przegląd

Sieci VPN umożliwiają bezpieczny dostęp urządzeniom, które nie są fizycznie połączone z siecią.

Android ma wbudowanego klienta VPN (PPTP i L2TP/IPSec), który czasami jest nazywany starszą wersją VPN. W Androidzie 4.0 (poziom interfejsu API 14) wprowadzono interfejsy API, aby deweloperzy aplikacji mogli udostępniać własne rozwiązania VPN. Spakuj rozwiązanie VPN do aplikacji, którą użytkownicy zainstalują na urządzeniu. Deweloperzy zwykle tworzą aplikację VPN z jednego z tych powodów:

  • aby zapewnić protokoły VPN, których nie obsługuje wbudowany klient;
  • aby umożliwić innym skorzystanie z usługi VPN bez skomplikowanej konfiguracji;

W pozostałej części tego przewodnika dowiesz się, jak tworzyć aplikacje VPN (w tym VPN zawsze włączona i dla poszczególnych aplikacji), ale nie omawiamy wbudowanego klienta VPN.

Z perspektywy użytkownika

Android ma interfejs, który ułatwia konfigurację, uruchomienie i zatrzymanie Twojego rozwiązania VPN. Interfejs użytkownika systemu informuje też użytkownika o aktywnym połączeniu VPN. W przypadku połączeń VPN Android pokazuje te komponenty interfejsu:

  • Zanim aplikacja VPN zostanie aktywowana po raz pierwszy, system wyświetli okno z prośbą o połączenie. W oknie pojawi się prośba o potwierdzenie, że użytkownik urządzenia ufa sieci VPN, i zaakceptowanie prośby.
  • Ekran ustawień VPN (Ustawienia > Sieć i internet > VPN) pokazuje aplikacje VPN, w przypadku których osoba zaakceptowała prośby o połączenie. który służy do skonfigurowania opcji systemowych lub zapomnienia sieci VPN.
  • Gdy połączenie jest aktywne, w Szybkich ustawieniach wyświetla się panel informacyjny. Gdy klikniesz etykietę, wyświetli się okno z dodatkowymi informacjami i linkiem do ustawień.
  • Na pasku stanu znajduje się ikona VPN (klucza), która wskazuje, że połączenie jest aktywne.

Aplikacja musi też udostępniać interfejs, aby osoba używająca urządzenia mogła konfigurować opcje usługi. Może na przykład wymagać zarejestrowania ustawień uwierzytelniania konta. Aplikacje powinny wyświetlać ten interfejs:

  • Elementy sterujące do ręcznego rozpoczynania i zatrzymywania połączenia. Stały VPN może się połączyć w razie potrzeby, ale umożliwia też skonfigurowanie połączenia przy pierwszym korzystaniu z Twojej sieci VPN.
  • Nie można zamknąć powiadomienia, gdy usługa jest aktywna. Może ono zawierać stan połączenia lub zawierać dodatkowe informacje, np. statystyki sieci. Kliknij powiadomienie, aby wyświetlić aplikację na pierwszym planie. Usuń powiadomienie, gdy usługa stanie się nieaktywna.

Usługa VPN

Aplikacja łączy sieć systemową użytkownika (lub profil służbowy) z bramą VPN. Każdy użytkownik (lub profil służbowy) może uruchomić inną aplikację VPN. Tworzysz usługę VPN, której system używa do uruchamiania i zatrzymywania VPN, oraz śledzić stan połączenia. Usługa VPN dziedziczy wartość z VpnService.

Usługa ta działa też jako kontener dla połączeń bramy VPN i ich lokalnych interfejsów urządzeń. Instancja usługi wywołuje metody VpnService.Builder, aby ustanowić nowy interfejs lokalny.

Rysunek 1. Jak VpnService łączy sieć Androida z bramą VPN
Diagram architektury blokowej przedstawiający, jak VpnService tworzy lokalny interfejs TUN w sieci systemu.

Aby połączyć urządzenie z bramą VPN, aplikacja przesyła te dane:

  • Odczytuje wychodzące pakiety IP z deskryptora plików interfejsu lokalnego, szyfruje je i wysyła do bramy VPN.
  • Zapisuje pakiety przychodzące (odebrane i odszyfrowane z bramy VPN) w deskryptorze plików interfejsu lokalnego.

Każdy użytkownik lub profil ma tylko jedną aktywną usługę. Uruchomienie nowej usługi automatycznie zatrzymuje istniejącą.

Dodaj usługę

Aby dodać usługę VPN do aplikacji, utwórz usługę na Androida dziedziczoną z VpnService. Zadeklaruj usługę VPN w pliku manifestu aplikacji z tymi dodatkami:

  • Chroń usługę za pomocą uprawnienia BIND_VPN_SERVICE, aby tylko system mógł utworzyć powiązanie z Twoją usługą.
  • Reklamuj usługę za pomocą filtra intencji "android.net.VpnService", aby system mógł ją znaleźć.

Ten przykład pokazuje, jak zadeklarować usługę w pliku manifestu aplikacji:

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

Po zadeklarowaniu usługi przez aplikację system może w razie potrzeby automatycznie ją uruchomić i zatrzymać. Na przykład system kontroluje usługę, gdy działa stała sieć VPN.

Przygotowanie usługi

Aby przygotować aplikację, aby stała się usługą VPN użytkownika, wywołaj VpnService.prepare(). Jeśli użytkownik urządzenia nie przyznał jeszcze uprawnień Twojej aplikacji, metoda zwraca intencję związaną z aktywnością. W ten sposób uruchamiasz działanie systemu, które prosi o pozwolenie. System wyświetla okno podobne do innych okien uprawnień, np. z dostępem do aparatu lub kontaktów. Jeśli aplikacja jest już przygotowana, metoda zwraca null.

Tylko 1 aplikacja może być obecnie przygotowaną usługą VPN. Zawsze wywołuj metodę VpnService.prepare(), ponieważ użytkownik mógł ustawić inną aplikację jako usługę VPN od czasu ostatniego wywołania tej metody przez aplikację. Więcej informacji znajdziesz w sekcji Cykl życia usługi.

Połącz usługę

Po uruchomieniu usługi możesz utworzyć nowy interfejs lokalny połączony z bramą VPN. Aby poprosić o uprawnienia i połączyć się z bramą VPN, wykonaj te czynności:

  1. Zadzwoń pod numer VpnService.prepare(), aby poprosić o zgodę (w razie potrzeby).
  2. Wywołaj VpnService.protect(), aby umieścić gniazdo tunelu aplikacji poza systemową siecią VPN i uniknąć połączenia okrężnego.
  3. Wywołaj DatagramSocket.connect(), aby połączyć tunel tunelu aplikacji z bramą VPN.
  4. Wywołaj metody VpnService.Builder, aby skonfigurować na urządzeniu nowy lokalny interfejs TUN pod kątem ruchu VPN.
  5. Wywołaj VpnService.Builder.establish(), aby system ustanowił lokalny interfejs TUN i rozpoczął kierowanie ruchu przez ten interfejs.

Brama VPN zwykle sugeruje ustawienia dla lokalnego interfejsu TUN podczas uzgadniania ręki. Aplikacja wywołuje metody VpnService.Builder, aby skonfigurować usługę, tak jak w tym przykładzie:

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

Przykład w sekcji Sieć VPN dla poszczególnych aplikacji przedstawia konfigurację IPv6 z dodatkowymi opcjami. Przed utworzeniem nowego interfejsu musisz dodać te wartości VpnService.Builder:

addAddress()
Dodaj co najmniej 1 adres IPv4 lub IPv6 oraz maskę podsieci, którą system przypisze jako adres lokalnego interfejsu TUN. Podczas uzgadniania połączenia aplikacja zwykle otrzymuje adresy IP i maski podsieci z bramy VPN.
addRoute()
Dodaj co najmniej 1 trasę, jeśli system ma wysyłać ruch przez interfejs VPN. Filtrowanie tras według adresów docelowych. Aby zaakceptować cały ruch, ustaw otwartą trasę, np. 0.0.0.0/0 lub ::/0.

Metoda establish() zwraca instancję ParcelFileDescriptor, której aplikacja używa do odczytywania pakietów z bufora interfejsu i zapisywania ich z niego. Metoda establish() zwraca wartość null, jeśli aplikacja nie jest przygotowana lub ktoś unieważni to uprawnienie.

Cykl życia usługi

Aplikacja powinna śledzić stan wybranej przez system sieci VPN oraz wszystkich aktywnych połączeń. Zaktualizuj interfejs aplikacji, tak aby użytkownik urządzenia wiedział o wszelkich zmianach.

Uruchamianie usługi

Aby uruchomić usługę VPN:

  • Aplikacja uruchamia usługę – zwykle dlatego, że użytkownik kliknął przycisk Połącz.
  • System uruchamia usługę, ponieważ stały VPN jest włączony.

Aplikacja uruchamia usługę VPN, przekazując intencję do startService(). Więcej informacji znajdziesz w artykule Uruchamianie usługi.

System uruchamia usługę w tle, wywołując metodę onStartCommand(). Jednak w wersji 8.0 (interfejs API na poziomie 26) lub nowszej wersji Androida nakładają ograniczenia na aplikacje działające w tle. Jeśli obsługujesz te poziomy API, musisz przenieść usługę na pierwszy plan, wywołując metodę Service.startForeground(). Więcej informacji znajdziesz w artykule Uruchamianie usługi na pierwszym planie.

Zatrzymywanie usługi

Osoba korzystająca z urządzenia może zatrzymać usługę za pomocą interfejsu aplikacji. Zatrzymaj usługę, zamiast zamykać połączenie. System zatrzyma też aktywne połączenie, gdy osoba korzystająca z urządzenia wykona te czynności na ekranie VPN w aplikacji Ustawienia:

  • odłącza lub zapomina aplikację VPN
  • wyłącza stały VPN w przypadku aktywnego połączenia

System wywołuje metodę onRevoke() Twojej usługi, ale to wywołanie może nie wystąpić w wątku głównym. Gdy system wywołuje tę metodę, alternatywny interfejs sieci już kieruje ruch. Możesz bezpiecznie pozbyć się tych zasobów:

Stały VPN

Android może uruchomić usługę VPN podczas uruchamiania urządzenia i nie wyłączać jej do momentu, aż urządzenie jest włączone. Ta funkcja nazywa się stały VPN i jest dostępna na Androidzie 7.0 (interfejs API na poziomie 24) i nowszych. Podczas gdy Android utrzymuje cykl życia usługi, to Twoja usługa VPN odpowiada za połączenie przez bramę VPN. Stały VPN może też blokować połączenia, które nie korzystają z sieci VPN.

Z perspektywy użytkownika

W Androidzie 8.0 i nowszych system wyświetla następujące okna, aby poinformować osobę używającą urządzenia, że zawsze włączona jest sieć VPN:

  • Gdy stałe połączenia VPN rozłączają się lub nie mogą się połączyć, użytkownicy widzą powiadomienie, którego nie można zamknąć. Dotknięcie powiadomienia otwiera okno z dodatkowymi informacjami. Powiadomienie znika, gdy sieć VPN ponownie się połączy lub ktoś wyłączy opcję stałego VPN.
  • Zawsze włączona sieć VPN umożliwia osobie korzystającej z urządzenia blokowanie wszystkich połączeń sieciowych, które nie korzystają z VPN. Gdy włączysz tę opcję, aplikacja Ustawienia będzie ostrzegać użytkowników, że nie mają połączenia z internetem, zanim nastąpi połączenie z VPN. Aplikacja Ustawienia prosi osobę używającą urządzenia o kontynuowanie lub anulowanie.

Ponieważ system (a nie człowiek) uruchamia i zatrzymuje stałe połączenie, musisz dostosować działanie aplikacji i interfejs użytkownika:

  1. Wyłącz interfejs użytkownika, który odłącza połączenie, ponieważ połączenie jest kontrolowane przez system i aplikację Ustawienia.
  2. Zapisz dowolną konfigurację pomiędzy każdym uruchomieniem aplikacji i skonfiguruj połączenie z najnowszymi ustawieniami. System uruchamia aplikację na żądanie, więc osoba korzystająca z urządzenia nie zawsze będzie chciała skonfigurować połączenie.

Połączenia możesz też skonfigurować za pomocą konfiguracji zarządzanych. Konfiguracje zarządzane ułatwiają administratorowi IT zdalne konfigurowanie sieci VPN.

Wykrywanie zawsze wł.

Android nie zawiera interfejsów API służących do sprawdzania, czy system uruchomił usługę VPN. Gdy jednak aplikacja oznaczy instancje usługi, która się uruchamia, możesz założyć, że system uruchomił usługi nieoznaczone na potrzeby zawsze włączonej sieci VPN. Na przykład:

  1. Aby uruchomić usługę VPN, utwórz instancję Intent.
  2. Oznacz usługę VPN, umieszczając dodatkowy element w intencji.
  3. W metodzie onStartCommand() usługi znajdź flagę w elementach dodatkowych argumentu intent.

Zablokowane połączenia

Osoba korzystająca z urządzenia (lub administrator IT) może wymusić używanie VPN przez cały ruch. System blokuje ruch w sieci, który nie korzysta z VPN. Osoby korzystające z urządzenia mogą znaleźć przełącznik Blokuj połączenia bez VPN w panelu opcji VPN w Ustawieniach.

Wyłącz zawsze wł.

Jeśli Twoja aplikacja nie obsługuje obecnie stałej sieci VPN, możesz z niej zrezygnować (w Androidzie 8.1 lub nowszym), ustawiając metadane usługi SERVICE_META_DATA_SUPPORTS_ALWAYS_ON na false. Poniższy przykład pliku manifestu aplikacji pokazuje, jak dodać element metadanych:

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

Gdy aplikacja wyłączy stały VPN, system wyłączy opcje dostępne w interfejsie w Ustawieniach.

VPN dla poszczególnych aplikacji

Aplikacje VPN mogą określać, które zainstalowane aplikacje mogą wysyłać ruch przez połączenie VPN. Możesz utworzyć listę dozwolonych lub niedozwolonych, ale nie obie. Jeśli nie utworzysz list dozwolonych lub niedozwolonych, system będzie wysyłać cały ruch w sieci przez VPN.

Przed nawiązaniem połączenia aplikacja VPN musi skonfigurować te listy. Jeśli musisz zmienić listy, nawiąż nowe połączenie VPN. Aplikacja, którą chcesz dodać do listy, musi być zainstalowana na urządzeniu.

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

Aplikacje dozwolone

Aby dodać aplikację do listy dozwolonych, wywołaj metodę VpnService.Builder.addAllowedApplication(). Jeśli lista zawiera co najmniej 1 aplikację, tylko aplikacje z listy będą używać sieci VPN. Wszystkie inne aplikacje (których nie ma na liście) korzystają z sieci systemowych, jakby sieć VPN była wyłączona. Gdy lista dozwolonych jest pusta, wszystkie aplikacje korzystają z sieci VPN.

Aplikacje niedozwolone

Aby dodać aplikację do listy niedozwolonych, wywołaj metodę VpnService.Builder.addDisallowedApplication(). Niedozwolone aplikacje korzystają z sieci systemowych, jakby sieć VPN nie była uruchomiona. Wszystkie pozostałe aplikacje korzystają z sieci VPN.

Omijaj VPN

Twoja sieć VPN może zezwolić aplikacjom na ominięcie tej sieci i wybór własnej sieci. Aby pominąć VPN, wywołaj VpnService.Builder.allowBypass() podczas ustanawiania interfejsu VPN. Po uruchomieniu usługi VPN nie możesz zmienić tej wartości. Jeśli aplikacja nie wiąże swojego procesu lub gniazdka z konkretną siecią, jej ruch w sieci jest kontynuowany przez VPN.

Aplikacje, które są powiązane z konkretną siecią, nie będą miały połączenia, gdy ktoś zablokuje ruch, który nie przechodzi przez VPN. Aby wysyłać ruch przez określoną sieć, aplikacje wywołują metody takie jak ConnectivityManager.bindProcessToNetwork() lub Network.bindSocket() przed podłączeniem gniazdka.

Kod demonstracyjny

Projekt Android Open Source obejmuje przykładową aplikację o nazwie ToyVPN. Ta aplikacja pokazuje, jak skonfigurować i połączyć usługę VPN.