برنامه نویسی

ساختن یک VPN ایمن در Android با WireGuard: یک راهنمای کامل

👋 سلام همه ،

در این راهنما ، ما چگونه می آموزیم که ادغام شود محافظ به شما اندرویدی برنامه با استفاده از Jetpack آهنگسازی وت کلاتلینبشر طیف گسترده ای از پروتکل های VPN برای ادغام با اندروید در بازار موجود است و ما انتخاب کردیم محافظ (یک پروتکل VPN مدرن) که به دلیل سادگی ، سرعت و ویژگی های امنیتی قوی شناخته شده است. در پایان این آموزش ، شما یک اجرای Wireguard VPN را خواهید داشت که می توانید در برنامه خود ادغام شوید.

اگر تازه وارد VPN ها هستید یا با اصطلاحات مرتبط ناآشنا هستید ، لطفاً این وبلاگ را قبل از ادامه مطلب بخوانید:

اگر قبلاً با VPN ها آشنا هستید یا وبلاگ را قبلاً خوانده اید ، بیایید شروع کنیم !!!

فهرست مطالب

قبل از اجرای VPN ، پروتکل های مختلف VPN را مورد بررسی قرار دادم و Wireguard را مناسب ترین انتخاب برای توسعه موبایل کردم. سادگی ، سرعت و ویژگی های امنیتی قوی آن از پروتکل های سنتی مانند OpenVPN و IPSec مشخص است. Wireguard حتی بهتر است زیرا بسیاری از افراد از آن پشتیبانی می کنند ، و نمونه های کد رایگان زیادی به صورت آنلاین وجود دارد. این باعث می شود اضافه کردن WireGuard به یک برنامه Android آسانتر شود.


# پیش نیازهای اساسی برای تنظیم پروژه شما

قبل از شروع توسعه ، اطمینان از نصب همه ابزارهای لازم و به روز بودن آنها ضروری است.

اطمینان حاصل کنید که موارد زیر را قبل از شروع آماده کرده اید:
استودیوی اندرویدی – آخرین نسخه پایدار را نصب کنید.
سرور VPN – برای تولید سرور به سرور نیاز دارید wg.conf پرونده برای Wireguard.


# تست نحوه عملکرد VPN در دستگاه ها

برای آزمایش نحوه کار VPN روی دستگاه شما (لپ تاپ ، موبایل و غیره) ، می توانید برنامه های موجود را از فروشگاه بارگیری کنید. در اینجا برخی از بهترین برنامه ها آورده شده است:

شما به یک فایل پیکربندی نیاز دارید (به عنوان مثال. client.conf) برای اجرای VPN روی دستگاه. با پسر ارائه دهنده سرور خود تماس بگیرید یا به صورت شخصی ایجاد کنید.

در اینجا چند پرونده نمونه برای نشان دادن نحوه پیکربندی به نظر می رسد پرونده: client.conf


# تنظیم پروژه

آیا شما آماده شیرجه زدن به قسمت برنامه نویسی هستید؟ بیایید شروع کنیم!

وابستگی به پروژه اضافه کنید

dependencies {
    // Add WireGuard dependency  
    implementation("com.wireguard.android:tunnel:1.0.20210211") 

    // Add desugaring library for Java 8+ API support
    implementation("com.android.tools:desugar_jdk_libs:2.1.5")  
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

پیکربندی سرویس VPN در AndroidManifest.xml

قبل از استفاده از ماژول WireGuard ، خود را به روز کنید AndroidManifest.xml پرونده

مجوزهای مورد نیاز را اضافه کنید:

این مجوزها به برنامه اجازه می دهد تا به اینترنت دسترسی پیدا کند و وضعیت شبکه را بررسی کند:



حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

خدمات VPN را ثبت کنید:

موارد زیر را در داخل اضافه کنید برای فعال کردن سرویس VPN WireGuard:


    

        
            
        

    

حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

به منطق برنامه نویسی شیرجه بزنید

  • یک کلاس ایجاد کنید WireGuardTunnel.kt در پروژه شما این کلاس به عنوان یک بسته بندی برای یک تونل Wireguard عمل می کند و به شما امکان می دهد وضعیت آن را به طور کارآمد پیگیری و مدیریت کنید.
import com.wireguard.android.backend.Tunnel

typealias StateChangeCallback = (Tunnel.State) -> Unit

class WireGuardTunnel(
    private var name: String,
    private val onStateChanged: StateChangeCallback? = null
) : Tunnel {
    private var state: Tunnel.State = Tunnel.State.DOWN

    override fun getName() = name

    override fun onStateChange(newState: Tunnel.State) {
        state = newState
        onStateChanged?.invoke(newState)
    }

    fun getState(): Tunnel.State = state
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • برای مدیریت جزئیات سرور VPN ، موارد زیر را اضافه کنید ServerInfo کلاس داده به پروژه شما:
import com.google.gson.annotations.SerializedName

data class ServerInfo(
    // Interface details
    @SerializedName("address") val interfaceAddress: String?,
    @SerializedName("dns") val interfaceDns: String?,
    @SerializedName("private_key") val interfacePrivateKey: String?,

    // Peer details
    @SerializedName("public_key") val peerPublicKey: String?,
    @SerializedName("preshared_key") val peerPresharedKey: String?,
    @SerializedName("allowed_ips") val peerAllowedIPs: String?,
    @SerializedName("endpoint") val peerEndpoint: String?,
    @SerializedName("persistent_keep_alive") val peerPersistentKeepalive: String?
)
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • برای ردیابی حالتهای مختلف اتصال VPN ، کلاس enum vpnstatus زیر را اضافه کنید:
enum class VPNStatus {
    PREPARE,        // VPN is getting ready  
    CONNECTING,     // Establishing a connection  
    CONNECTED,      // VPN is active and running  
    DISCONNECTING,  // Disconnecting from the VPN  
    DISCONNECTED,   // VPN is not connected  
    NO_CONNECTION,  // No available VPN connection  
    REFRESHING      // Refreshing VPN status  
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • برای رسیدگی به عملیات VPN ، موارد زیر را اضافه کنید WireguardManager کلاس. بعداً آن را با روش های بیشتر گسترش خواهیم داد.
import android.app.Activity
import android.content.Context
import com.wireguard.android.backend.Backend
import kotlinx.coroutines.*

class WireguardManager(private val context: Context, private val activity: Activity?) {
    private val scope = CoroutineScope(Job() + Dispatchers.Main.immediate)
    private var backend: Backend? = null
    private var tunnelName: String = "wg_default"
    private var config: Config? = null
    private var tunnel: WireGuardTunnel? = null
    private val futureBackend = CompletableDeferred()
    private val TAG = "WireguardManager"

    companion object {
        private var state: VPNStatus = VPNStatus.NO_CONNECTION
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • روش اولیه را در WireguardManager کلاس:
class WireguardManager(private val context: Context, private val activity: Activity?) {

    // Remaining code

    init {
        scope.launch(Dispatchers.IO) {
            try {
                cachedTunnelData = SharedPreferenceHelper.getVpnData()
                backend = GoBackend(context)
                futureBackend.complete(backend!!)
                activity?.let { GoBackend.VpnService.prepare(it) }
            } catch (e: Throwable) {
                Log.e(TAG, "ERROR: Exception during WireguardManager initialization: ${e.localizedMessage}")
                Log.e(TAG, Log.getStackTraceString(e))
            }
        }
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

class WireguardManager(private val context: Context, private val activity: Activity?) {

    // Remaining code

    /**
     * Generates WireGuard configuration from the provided ServerInfo.
     * This creates a wg-quick compatible configuration for the VPN tunnel.
     */
    private fun getConfigData(tunnelData: ServerInfo): Config {
        val wgQuickConfig = """
            [Interface]
            Address = ${tunnelData.interfaceAddress ?: ""}
            DNS = ${tunnelData.interfaceDns ?: ""}
            PrivateKey = ${tunnelData.interfacePrivateKey ?: ""}

            [Peer]
            PublicKey = ${tunnelData.peerPublicKey ?: ""}
            PresharedKey = ${tunnelData.peerPresharedKey ?: ""}
            AllowedIPs = ${tunnelData.peerAllowedIPs ?: ""}
            Endpoint = ${tunnelData.peerEndpoint ?: ""}
            PersistentKeepalive = ${tunnelData.peerPersistentKeepalive ?: ""}
        """.trimIndent()

        val inputStream = ByteArrayInputStream(wgQuickConfig.toByteArray())
        return Config.parse(inputStream)
    }

    /**
     * Checks if any VPN connection is currently active on the device.
     * Returns `true` if a VPN connection is detected, otherwise `false`.
     */
    val isVpnActive: Boolean
        get() {
            return try {
                val connectivityManager =
                    context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager

                val activeNetwork = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                    connectivityManager.activeNetwork ?: return false
                } else {
                    // For Android < 6.0, use the old method
                    val networkInfo = connectivityManager.activeNetworkInfo
                    return networkInfo != null && networkInfo.type == ConnectivityManager.TYPE_VPN
                }

                val networkCapabilities = connectivityManager.getNetworkCapabilities(activeNetwork)

                // Check if any VPN is active
                networkCapabilities?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true
            } catch (e: Exception) {
                Log.e(TAG, "isVpnActive - ERROR - ${e.localizedMessage}", e)
                false
            }
        }

    /**
     * Retrieves an existing WireGuard tunnel or creates a new one if none exists.
     * The `callback` function listens for state changes in the tunnel.
     */
    private fun getTunnel(name: String, callback: StateChangeCallback? = null): WireGuardTunnel {
        if (tunnel == null) {
            tunnel = WireGuardTunnel(name, callback)
        }
        return tunnel as WireGuardTunnel
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • روش هایی را برای به روزرسانی وضعیت اضافه کنید:
class WireguardManager(private val context: Context, private val activity: Activity?) {

    // Remaining code

    /**
     * Updates the VPN status based on the tunnel's state.
     * Runs on the main thread to ensure UI updates happen smoothly.
     */
    private fun updateStageFromState(state: Tunnel.State) {
        scope.launch(Dispatchers.Main) {
            when (state) {
                Tunnel.State.UP -> updateStage(VPNStatus.CONNECTED)      // VPN is active
                Tunnel.State.DOWN -> updateStage(VPNStatus.DISCONNECTED) // VPN is disconnected
                else -> updateStage(VPNStatus.NO_CONNECTION)             // No active VPN connection
            }
        }
    }

    /**
     * Sets the VPN status and saves it in shared preferences.
     * Ensures status updates run on the main thread.
     */
    private fun updateStage(stage: VPNStatus?) {
        scope.launch(Dispatchers.Main) {
            val updatedStage = stage ?: VPNStatus.NO_CONNECTION
            state = updatedStage
            // Store VPN status in SharedPreferences if required
        }
    }

     /**
     * Returns the VPN status based on the tunnel's state.
     */
    fun getStatus(): VPNStatus {
        return state
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • روش هایی را برای شروع سرویس VPN اضافه کنید:
class WireguardManager(private val context: Context, private val activity: Activity?) {

    // Remaining code

    /**
     * Starts the VPN connection process.
     * Initializes the tunnel and attempts to connect.
     */
    fun start(tunnelData: ServerInfo) {
        initialize(tunnelName)
        connect(tunnelData)
    }

    /**
     * Initializes the tunnel with a given name.
     * Ensures the tunnel name is valid before proceeding.
     */
    private fun initialize(localizedDescription: String) {
        if (Tunnel.isNameInvalid(localizedDescription)) {
            Log.e(TAG, "Invalid Tunnel Name: $localizedDescription")
            return
        }
        tunnelName = localizedDescription
    }

    /**
     * Connects to the VPN using the provided tunnel configuration.
     * Updates VPN status at different stages of the connection process.
     */
    private fun connect(tunnelData: ServerInfo) {
        scope.launch(Dispatchers.IO) {
            try {
                updateStage(VPNStatus.PREPARE) // Preparing VPN connection

                // Generate WireGuard configuration
                config = getConfigData(tunnelData)
                updateStage(VPNStatus.CONNECTING) // Attempting to connect

                // Retrieve or create the WireGuard tunnel
                val tunnel = getTunnel(tunnelName) { state ->
                    scope.launch {
                        Log.i(TAG, "onStateChange - $state")
                        updateStageFromState(state)
                    }
                }

                // Activate the VPN connection
                futureBackend.await().setState(tunnel, Tunnel.State.UP, config)

                scope.launch(Dispatchers.Main) {
                    updateStage(VPNStatus.CONNECTED) // VPN is successfully connected
                    // Store VPN status in SharedPreferences if required
                }

                Log.i(TAG, "Connect - success!")
            } catch (e: Throwable) {
                updateStage(VPNStatus.NO_CONNECTION) // Failed to establish a connection
                Log.e(TAG, "Connect - ERROR - ${e.message}")
            }
        }
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • روش هایی را برای قطع سرویس VPN اضافه کنید:
class WireguardManager(private val context: Context, private val activity: Activity?) {
    // Remaining code

    /**
     * Stops the VPN connection by calling the disconnect method.
     */
    fun stop() {
        disconnect()
    }

    /**
     * Disconnects the active VPN tunnel.
     * - If no tunnel is running, logs an error.
     * - Updates VPN status before and after disconnection.
     * - Handles reconnection if cached tunnel data exists.
     */
    private fun disconnect() {
        scope.launch(Dispatchers.IO) {
            try {
                // Check if any tunnel is currently running
                if (futureBackend.await().runningTunnelNames.isEmpty()) {
                    throw Exception("Tunnel is not running")
                }

                updateStage(VPNStatus.DISCONNECTING)

                // Retrieve the active tunnel and monitor state changes
                val tunnel = getTunnel(tunnelName) { state ->
                    scope.launch {
                        Log.i(TAG, "onStateChange - $state")
                        updateStageFromState(state)
                    }
                }

                // Set the tunnel state to DOWN to disconnect
                futureBackend.await().setState(tunnel, Tunnel.State.DOWN, config)

                // Update VPN status and shared preferences on the main thread
                scope.launch(Dispatchers.Main) {
                    updateStage(VPNStatus.DISCONNECTED)
                    // Store VPN status in SharedPreferences if required
                }
                Log.i(TAG, "Disconnect - success!")
            } catch (e: Throwable) {
                Log.e(TAG, "Disconnect - ERROR - ${e.message}")
            }
        }
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • اکنون یک کلاس مدل View ایجاد کنید VPNViewModel، مسئول رسیدگی به حالت VPN ، شروع/متوقف کردن اتصال و نظارت بر وضعیت VPN به طور کارآمد.
class VPNViewModel(application: Application) : AndroidViewModel(application) {
    private val context by lazy { application.applicationContext }
    private var wireguardManager: WireguardManager? = null

    // VPN connection status as a StateFlow
    private val _vpnState = MutableStateFlow(VPNStatus.NO_CONNECTION)
    val vpnState: StateFlow = _vpnState.asStateFlow()

    // Whether VPN is currently active
    private val _isVpnActive = MutableStateFlow(false)
    val isVpnActive: StateFlow = _isVpnActive.asStateFlow()

    /**
     * Initializes the WireGuard manager and starts monitoring VPN state changes.
     * This method should be called when the ViewModel is created.
     */
    fun initVPN(activity: Activity) {
        wireguardManager = WireguardManager(context, activity)

        // Observe VPN state changes in a coroutine
        viewModelScope.launch {
            while (isActive) {
                delay(500) // Check every 500ms (half a second)

                // Restore last known VPN state from SharedPreferences if needed and assign in fallback
                _vpnState.value = wireguardManager?.getStatus()
                    ?: VPNStatus.NO_CONNECTION

                _isVpnActive.value = wireguardManager?.isVpnActive ?: false
            }
        }
    }

    /**
     * Starts the VPN connection with the given tunnel data.
     */
    fun startVPN(tunnelData: ServerInfo) {
        viewModelScope.launch {
            wireguardManager?.start(tunnelData)
        }
    }

    /**
     * Stops the active VPN connection.
     */
    fun stopVPN() {
        viewModelScope.launch {
            wireguardManager?.stop()
        }
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید

  • مدیریت اتصالات VPN با ViewModel در JetPack آهنگسازی
// Handling VPN Permission Request
val vpnPermissionLauncher = rememberLauncherForActivityResult(
    contract = ActivityResultContracts.StartActivityForResult()
) { result ->
    if (result.resultCode == Activity.RESULT_OK) {
        // Permission granted, now connect
        viewModel.startVPN(tunnelData)
    } else {
        // Permission denied, show an error or update UI
    }
}

// Starting a VPN Connection
coroutineScope.launch(Dispatchers.IO) {
    val intent = GoBackend.VpnService.prepare(activity)
    delay(100) // Small delay to prevent UI lag
    if (intent != null) {
        vpnPermissionLauncher.launch(intent) // Request permission if needed
    } else {
        viewModel.startVPN(tunnelData) // No permission needed, start directly
    }
}

// Stopping a VPN Connection
coroutineScope.launch(Dispatchers.IO) {
    delay(100) // Simulate network request
    withContext(Dispatchers.Main) {
        viewModel.stopVPN() // Stop the VPN connection
    }
}
حالت تمام صفحه را وارد کنید

از حالت تمام صفحه خارج شوید


# بیایید بپیچیم

مدیریت اتصالات VPN در یک برنامه Android نیاز به رسیدگی به مجوزها ، شروع اتصالات و مدیریت قطع ارتباطات دارد. با استفاده از آهنگسازی JetPack و ViewModel ، می توانیم ضمن نگه داشتن کد سازماندهی و مدیریت آسان ، برنامه را آسان کنیم.

با تشکر از خواندن

اگر این وبلاگ را مفید یا سؤال دیگری پیدا کردید ، دوست داریم از شما بشنویم. احساس کردن رایگان برای رسیدن به وت ما را دنبال کنید در سیستم عامل های رسانه های اجتماعی ما برای راهنمایی ها و آموزش های بیشتر در مورد پست های فناوری گرا.

برنامه نویسی مبارک!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا