VPN

Android cung cấp các API để nhà phát triển tạo mạng riêng ảo (VPN) Cloud. Sau khi đọc hướng dẫn này, bạn sẽ biết cách phát triển và thử nghiệm ứng dụng VPN riêng dành cho các thiết bị chạy Android.

Tổng quan

VPN cho phép các thiết bị không ở trên mạng truy cập an toàn vào mạng.

Android có ứng dụng VPN (PPTP và L2TP/IPSec) tích hợp sẵn mà đôi khi có tên là VPN cũ. Android 4.0 (API cấp 14) đã ra mắt các API để ứng dụng đó nhà phát triển có thể cung cấp giải pháp VPN của riêng họ. Bạn đóng gói giải pháp VPN vào một ứng dụng mà mọi người cài đặt trên thiết bị. Nhà phát triển thường xây dựng VPN vì một trong những lý do sau:

  • Để cung cấp các giao thức VPN mà ứng dụng tích hợp sẵn không hỗ trợ.
  • Giúp mọi người kết nối với dịch vụ VPN mà không cần định cấu hình phức tạp.

Phần còn lại của hướng dẫn này giải thích cách phát triển ứng dụng VPN (bao gồm VPN luôn bậtcho mỗi ứng dụng) đồng thời không bao gồm ứng dụng VPN tích hợp sẵn.

Trải nghiệm người dùng

Android cung cấp giao diện người dùng (UI) để giúp người dùng định cấu hình, bắt đầu và ngừng giải pháp VPN. Giao diện người dùng hệ thống cũng cho phép người dùng thiết bị nhận biết một kết nối VPN đang hoạt động. Android hiển thị các thành phần giao diện người dùng sau cho Kết nối VPN:

  • Trước khi một ứng dụng VPN có thể hoạt động lần đầu tiên, hệ thống sẽ hiện một hộp thoại yêu cầu kết nối. Hộp thoại nhắc người dùng đang sử dụng thiết bị xác nhận rằng họ tin tưởng VPN và chấp nhận yêu cầu.
  • Màn hình cài đặt VPN (Cài đặt > Mạng và Internet > VPN) hiển thị VPN những ứng dụng mà người dùng đã chấp nhận yêu cầu kết nối. Có một nút để định cấu hình tuỳ chọn hệ thống hoặc xoá VPN.
  • Khay Cài đặt nhanh hiển thị bảng thông tin khi có kết nối đang hoạt động. Thao tác nhấn vào nhãn sẽ hiển thị hộp thoại có thêm thông tin và đường liên kết vào phần Cài đặt.
  • Thanh trạng thái có một biểu tượng VPN (khoá) để cho biết kết nối đang hoạt động.

Ứng dụng của bạn cũng cần cung cấp một giao diện người dùng để người sử dụng thiết bị có thể định cấu hình các lựa chọn của dịch vụ. Ví dụ: giải pháp của bạn có thể cần nắm bắt chế độ cài đặt xác thực tài khoản. Ứng dụng phải cho thấy giao diện người dùng như sau:

  • Các tuỳ chọn kiểm soát để bắt đầu và dừng kết nối theo cách thủ công. VPN luôn bật có thể kết nối khi cần, nhưng cho phép mọi người định cấu hình kết nối mỗi khi họ dùng VPN của bạn.
  • Là một thông báo không đóng được khi dịch vụ đang hoạt động. Thông báo có thể hiển thị trạng thái kết nối hoặc cung cấp thêm thông tin—chẳng hạn như số liệu thống kê mạng. Khi nhấn vào thông báo đó, ứng dụng của bạn sẽ được đưa lên nền trước. Xoá sau khi dịch vụ không hoạt động.

Dịch vụ VPN

Ứng dụng của bạn kết nối mạng hệ thống cho người dùng (hoặc công việc cấu hình) đến một cổng VPN. Mỗi người dùng (hoặc hồ sơ công việc) đều có thể chạy ứng dụng VPN khác nhau. Bạn tạo một dịch vụ VPN mà hệ thống dùng để khởi động và dừng VPN và theo dõi trạng thái kết nối. Dịch vụ VPN của bạn kế thừa từ VpnService.

Dịch vụ này cũng đóng vai trò là vùng chứa của bạn cho các kết nối cổng vào VPN và giao diện thiết bị cục bộ của họ. Lệnh gọi thực thể dịch vụ Phương thức VpnService.Builder để thiết lập giao diện cục bộ mới.

Hình 1. Cách VpnService kết nối với Android kết nối mạng với cổng VPN
Sơ đồ cấu trúc khối cho thấy cách VpnService tạo TUN cục bộ
         trong mạng hệ thống.

Ứng dụng của bạn chuyển những dữ liệu sau đây để kết nối thiết bị với cổng VPN:

  • Đọc các gói IP gửi đi từ chỉ số mô tả tệp của giao diện cục bộ, mã hoá rồi gửi chúng đến cổng VPN.
  • Ghi các gói gửi đến (nhận và giải mã từ cổng VPN) vào chỉ số mô tả tệp của giao diện cục bộ.

Chỉ có một dịch vụ đang hoạt động cho mỗi người dùng hoặc hồ sơ. Bắt đầu một dịch vụ mới, tự động dừng một dịch vụ hiện có.

Thêm dịch vụ

Để thêm dịch vụ VPN vào ứng dụng, hãy tạo một dịch vụ Android kế thừa từ VpnService. Khai báo dịch vụ VPN trong ứng dụng tệp kê khai với các bổ sung sau:

  • Bảo vệ dịch vụ bằng BIND_VPN_SERVICE để chỉ hệ thống mới có thể liên kết với dịch vụ của bạn.
  • Quảng cáo dịch vụ bằng bộ lọc ý định "android.net.VpnService" để hệ thống có thể tìm thấy dịch vụ của bạn.

Ví dụ này cho thấy cách bạn có thể khai báo dịch vụ trong tệp kê khai ứng dụng:

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

Sau khi ứng dụng của bạn khai báo dịch vụ, hệ thống có thể tự động khởi động và dừng dịch vụ VPN của ứng dụng khi cần. Ví dụ: hệ thống điều khiển dịch vụ của bạn khi chạy VPN luôn bật.

Chuẩn bị dịch vụ

Để chuẩn bị ứng dụng trở thành dịch vụ VPN hiện tại của người dùng, hãy gọi VpnService.prepare(). Nếu người đang sử dụng thiết bị đã cấp quyền cho ứng dụng của bạn, phương thức này sẽ trả về một ý định hoạt động. Bạn dùng ý định này để bắt đầu một hoạt động hệ thống yêu cầu cấp quyền. Chiến lược phát hành đĩa đơn hệ thống sẽ hiển thị một hộp thoại tương tự với các hộp thoại cấp quyền khác, chẳng hạn như máy ảnh hoặc danh bạ. Nếu ứng dụng của bạn đã được chuẩn bị, phương thức này sẽ trả về null.

Chỉ một ứng dụng có thể là dịch vụ VPN đã chuẩn bị hiện tại. Luôn gọi VpnService.prepare() vì một người có thể đã đặt một làm dịch vụ VPN kể từ lần gần đây nhất ứng dụng của bạn gọi phương thức. Để tìm hiểu thêm, hãy xem phần Vòng đời dịch vụ.

Kết nối dịch vụ

Khi dịch vụ đang chạy, bạn có thể thiết lập giao diện cục bộ mới đã kết nối với cổng VPN. Để yêu cầu cấp quyền và kết nối dịch vụ của bạn với cổng VPN, bạn cần hoàn tất các bước theo thứ tự sau:

  1. Gọi VpnService.prepare() để yêu cầu quyền (khi nếu cần).
  2. Gọi VpnService.protect() để giữ lại ổ cắm đường hầm của ứng dụng ra bên ngoài VPN hệ thống và tránh kết nối vòng tròn.
  3. Gọi DatagramSocket.connect() để kết nối đường hầm của ứng dụng kết nối với cổng VPN.
  4. Gọi các phương thức VpnService.Builder để định cấu hình một tệp cục bộ mới Giao diện TUN trên cho lưu lượng truy cập VPN.
  5. Gọi VpnService.Builder.establish() để hệ thống thiết lập giao diện TUN cục bộ và bắt đầu định tuyến lưu lượng truy cập thông qua .

Cổng VPN thường đề xuất các chế độ cài đặt cho giao diện TUN cục bộ trong thời gian bắt tay. Ứng dụng của bạn gọi các phương thức VpnService.Builder để định cấu hình một như được minh hoạ trong mẫu sau đây:

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

Ví dụ trong phần VPN cho mỗi ứng dụng cho thấy một cấu hình IPv6 bao gồm tuỳ chọn khác. Bạn cần thêm các giá trị VpnService.Builder sau trước khi bạn có thể thiết lập giao diện mới:

addAddress()
Thêm ít nhất một địa chỉ IPv4 hoặc IPv6 cùng với mặt nạ mạng con mà hệ thống gán làm địa chỉ giao diện TUN cục bộ. Ứng dụng của bạn thường nhận được IP địa chỉ và mặt nạ mạng con từ cổng VPN trong quá trình bắt tay.
addRoute()
Thêm ít nhất một tuyến nếu bạn muốn hệ thống gửi lưu lượng truy cập qua VPN . Tuyến đường lọc theo địa chỉ điểm đến. Để chấp nhận tất cả lưu lượng truy cập, hãy đặt tuyến mở như 0.0.0.0/0 hoặc ::/0.

Phương thức establish() trả về một Thực thể ParcelFileDescriptor mà ứng dụng dùng để đọc và ghi các gói đến và đi từ vùng đệm của giao diện. establish() phương thức này sẽ trả về null nếu ứng dụng của bạn không được chuẩn bị hoặc ai đó thu hồi quyền.

Vòng đời dịch vụ

Ứng dụng của bạn phải theo dõi trạng thái của VPN đã chọn của hệ thống và mọi hoạt động kết nối. Cập nhật giao diện người dùng (UI) của ứng dụng để giữ chân người dùng thiết bị nhận biết được bất kỳ thay đổi nào.

Bắt đầu một dịch vụ

Bạn có thể bắt đầu dịch vụ VPN theo các cách sau:

  • Ứng dụng của bạn khởi động dịch vụ – bình thường vì một người đã nhấn vào nút kết nối.
  • Hệ thống bắt đầu dịch vụ vì VPN luôn bật đang bật.

Ứng dụng của bạn khởi động dịch vụ VPN bằng cách chuyển một ý định đến startService(). Để tìm hiểu thêm, hãy đọc bài viết Bắt đầu một .

Hệ thống khởi động dịch vụ của bạn ở chế độ nền bằng cách gọi onStartCommand(). Tuy nhiên, Android đặt các hạn chế về ứng dụng nền ở phiên bản 8.0 (API cấp 26) trở lên. Nếu bạn hỗ trợ Các cấp độ API, bạn cần chuyển dịch vụ của mình sang nền trước bằng cách gọi Service.startForeground(). Để tìm hiểu thêm, hãy đọc bài viết Chạy một trên nền trước.

Ngừng dịch vụ

Người dùng thiết bị có thể ngừng dịch vụ của bạn bằng cách sử dụng giao diện người dùng của ứng dụng. Dừng thay vì chỉ đóng kết nối. Hệ thống cũng dừng khi người dùng thiết bị thực hiện các thao tác sau trên màn hình VPN của ứng dụng Cài đặt:

  • ngắt kết nối hoặc quên ứng dụng VPN
  • tắt VPN luôn bật cho kết nối đang hoạt động

Hệ thống gọi phương thức onRevoke() của dịch vụ nhưng lệnh gọi này có thể không xảy ra trên luồng chính. Khi hệ thống gọi phương thức này, giao diện mạng thay thế đã định tuyến lưu lượng truy cập. Bạn có thể vứt bỏ một cách an toàn trong số các tài nguyên sau:

  • Đóng ổ cắm đường hầm được bảo vệ tới cổng VPN bằng cách gọi DatagramSocket.close().
  • Đóng chỉ số mô tả tệp của gói hàng (bạn không cần phải tiêu hao tệp đó) bằng cách gọi ParcelFileDescriptor.close().

VPN luôn bật

Android có thể bắt đầu dịch vụ VPN khi thiết bị khởi động và giữ cho thiết bị chạy trong khi thiết bị đang bật. Tính năng này được gọi là VPN luôn bật và có trong Android 7.0 (API cấp 24) trở lên. Mặc dù Android duy trì dịch vụ vòng đời, chính dịch vụ VPN của bạn sẽ chịu trách nhiệm về cổng vào VPN kết nối. VPN luôn bật cũng có thể chặn các kết nối không sử dụng VPN.

Trải nghiệm người dùng

Trên Android 8.0 trở lên, hệ thống sẽ hiện các hộp thoại sau để giúp người dùng thiết bị biết VPN luôn bật:

  • Khi kết nối VPN luôn bật bị ngắt kết nối hoặc không thể kết nối, mọi người sẽ thấy thông báo không đóng được. Thao tác nhấn vào thông báo sẽ hiển thị một hộp thoại giải thích thêm. Thông báo sẽ biến mất khi VPN kết nối lại hoặc có ai đó tắt tuỳ chọn VPN luôn bật.
  • VPN luôn bật cho phép người dùng thiết bị chặn bất kỳ mạng nào các kết nối không sử dụng VPN. Khi bật tuỳ chọn này, phần Cài đặt ứng dụng cảnh báo mọi người rằng họ không có kết nối Internet trước khi có VPN kết nối. Ứng dụng Cài đặt sẽ nhắc người dùng thiết bị tiếp tục hoặc huỷ.

Do hệ thống (chứ không phải một người) khởi động và dừng kết nối luôn bật, bạn cần điều chỉnh hành vi và giao diện người dùng của ứng dụng:

  1. Tắt mọi giao diện người dùng ngắt kết nối vì hệ thống và phần Cài đặt ứng dụng kiểm soát kết nối.
  2. Lưu mọi cấu hình giữa mỗi lần khởi động ứng dụng và định cấu hình kết nối với cài đặt mới nhất. Do hệ thống khởi động ứng dụng của bạn theo yêu cầu, nên người không phải lúc nào cũng muốn định cấu hình kết nối.

Bạn cũng có thể sử dụng cấu hình được quản lý để định cấu hình kết nối. Cấu hình được quản lý giúp quản trị viên CNTT định cấu hình VPN của bạn từ xa.

Phát hiện chế độ luôn bật

Android không có các API để xác nhận xem hệ thống có khởi động VPN của bạn hay không . Tuy nhiên, khi ứng dụng của bạn gắn cờ bất kỳ phiên bản dịch vụ nào mà nó khởi động, bạn có thể giả định rằng hệ thống đã khởi động các dịch vụ không gắn cờ cho VPN luôn bật. Sau đây là ví dụ:

  1. Tạo một thực thể Intent để bắt đầu dịch vụ VPN.
  2. Gắn cờ dịch vụ VPN bằng cách thêm dữ liệu bổ sung vào ý định.
  3. Trong phương thức onStartCommand() của dịch vụ, hãy tìm thuộc tính gắn cờ trong phần bổ sung của đối số intent.

Kết nối bị chặn

Người dùng thiết bị (hoặc quản trị viên CNTT) có thể buộc tất cả lưu lượng truy cập dùng VPN. Hệ thống sẽ chặn mọi lưu lượng truy cập mạng không dùng VPN. Những người sử dụng thiết bị có thể thấy nút chuyển Chặn kết nối mà không cần VPN trong phần tuỳ chọn VPN trong phần Cài đặt.

Chọn không bật chế độ luôn bật

Nếu ứng dụng của bạn hiện không hỗ trợ VPN luôn bật, bạn có thể chọn không tham gia (trong Android 8.1 trở lên) bằng cách đặt SERVICE_META_DATA_SUPPORTS_ALWAYS_ON siêu dữ liệu dịch vụ đến false. Ví dụ sau đây về tệp kê khai ứng dụng minh hoạ cách thêm phần tử siêu dữ liệu:

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

Khi ứng dụng của bạn chọn không sử dụng VPN luôn bật, hệ thống sẽ vô hiệu hoá các giao diện người dùng của các tuỳ chọn trong phần Cài đặt.

VPN cho mỗi ứng dụng

Các ứng dụng VPN có thể lọc ra những ứng dụng đã cài đặt nào được phép gửi lưu lượng truy cập thông qua Kết nối VPN. Bạn có thể tạo danh sách được phép hoặc danh sách không được phép, nhưng không phải cả hai. Nếu bạn không tạo danh sách được phép hoặc không được phép, hệ thống sẽ gửi tất cả lưu lượng truy cập mạng thông qua VPN.

Ứng dụng VPN phải đặt các danh sách này trước khi thiết lập kết nối. Nếu bạn cần thay đổi danh sách, thiết lập kết nối VPN mới. Ứng dụng phải trên thiết bị khi bạn thêm ứng dụng đó vào danh sách.

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

Ứng dụng được cho phép

Để thêm một ứng dụng vào danh sách cho phép, hãy gọi VpnService.Builder.addAllowedApplication(). Nếu danh sách này bao gồm một hoặc nhiều ứng dụng, sau đó chỉ những ứng dụng trong danh sách mới sử dụng VPN. Tất cả các ứng dụng khác (không có trong danh sách) sử dụng mạng hệ thống như thể VPN hiện không chạy. Khi danh sách được phép trống, tất cả ứng dụng đều sử dụng VPN.

Ứng dụng không được phép

Để thêm một ứng dụng vào danh sách không được phép, hãy gọi VpnService.Builder.addDisallowedApplication(). Các ứng dụng không được phép sử dụng kết nối mạng hệ thống như thể VPN không chạy – tất cả các ứng dụng khác ứng dụng sử dụng VPN.

Bỏ qua VPN

VPN có thể cho phép các ứng dụng bỏ qua VPN và chọn mạng riêng của ứng dụng đó. Người nhận bỏ qua VPN, gọi VpnService.Builder.allowBypass() khi thiết lập giao diện VPN. Bạn không thể thay đổi giá trị này sau khi bắt đầu Dịch vụ VPN. Nếu một ứng dụng không liên kết quá trình xử lý của chúng hoặc ổ cắm với một ứng dụng cụ thể mạng, lưu lượng truy cập mạng của ứng dụng sẽ tiếp tục thông qua VPN.

Các ứng dụng liên kết với một mạng cụ thể sẽ không có kết nối khi ai đó chặn lưu lượng truy cập không đi qua VPN. Để gửi lưu lượng truy cập thông qua một mạng, các phương thức gọi của ứng dụng, chẳng hạn như ConnectivityManager.bindProcessToNetwork() hoặc Network.bindSocket() trước khi kết nối với ổ cắm.

Mã mẫu

Dự án nguồn mở Android bao gồm một ứng dụng mẫu có tên là ToyVPN. Ứng dụng này cho biết cách thiết lập và kết nối dịch vụ VPN.