Yalkap Gurbanow
Yalkap Gurbanow

Reputation: 11

Android VPNService - DatagramChannel cannot write packets after calling VPNService.establish

I'm working on a pet VPN project for Android. Now I need to develop an application that would send all packets from the device to a remote server and receive a response from it (without authorization and data encryption). The server part is ready and working fine, but there are problems on the client side. After I call VPNService.establish, a ParcelFileDescriptor is returned to me (i.e. a tun is created on the device) and the UDP connection to the server disappears. I can't send any packets to the server, although before VPNService.establish I can communicate with the server without any problems. Here is my VPNService code:


class DPNService : VpnService() {

    private val currentConnection = AtomicReference<Thread>()

    private val handshakeServiceListener = object : HandshakeService.Listener {
        override fun onError(exception: Exception) {
            Log.e(TAG, "Handshake error", exception)
        }

        override fun onSuccess(params: ServerParams, tunnel: DatagramChannel) {
            protect(tunnel.socket())
            updateNotification("VPN is connecting...")
            val vpnInterface = configureVpnService(params)
            vpnInterface?.let {
                connect(tunnel, it)
            }
        }
    }


    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        if (intent.action == ACTION_CONNECT) {
            val tunnel = DatagramChannel.open()
            if (protect(tunnel.socket())) {
                val handshakeService = HandshakeService(tunnel, handshakeServiceListener)
                handshakeService.sendHandshake()
            }
            return START_STICKY
        }
        if (intent.action == ACTION_DISCONNECT){
            disconnect()
            return START_NOT_STICKY
        }

        return super.onStartCommand(intent, flags, startId)
    }

    private fun connect(tunnel: DatagramChannel, vpnInterface: ParcelFileDescriptor?) {

        val connection = DPNConnection(tunnel, vpnInterface)
        val thread = Thread(connection, "DPN Connection")
        setCurrentConnection(thread)
        thread.start()
        updateNotification("VPN is connected")
    }

    private fun configureVpnService(serverParams: ServerParams): ParcelFileDescriptor? {
        val builder = this.Builder()
        builder.addAddress(serverParams.address, serverParams.addressPrefixLength)
        builder.addDnsServer(serverParams.dns)
        builder.addRoute(serverParams.route, serverParams.routePrefixLength)
        builder.setMtu(serverParams.mtu)

        builder.addDisallowedApplication("com.google.chrome")
        builder.addDisallowedApplication("com.werebug.androidnetcat")

        builder.setSession(serverParams.address)
        return builder.establish()
    }

    private fun disconnect() {
        setCurrentConnection(null)
        stopSelf()
    }

    override fun onDestroy() {
        disconnect()
    }

    private fun setCurrentConnection(thread: Thread?) {
        // set new thread and interrupt old
        currentConnection.getAndSet(thread)?.interrupt()
    }

    private fun updateNotification(message: String) {
        val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager

        val notificationChannel = NotificationChannel(
            NOTIFICATION_CHANNEL_ID,
            NOTIFICATION_CHANNEL_ID,
            NotificationManager.IMPORTANCE_HIGH,
        )
        val notification = Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_vpn_key)
            .setContentText(message)
            .build()


        notificationManager.createNotificationChannel(notificationChannel)
        notificationManager.notify(1, notification)
    }

    companion object {
        const val TAG = "DPNService"
        const val ACTION_CONNECT = "DPNService.CONNECT"
        const val ACTION_DISCONNECT = "DPNService.DISCONNECT"
        const val NOTIFICATION_CHANNEL_ID = "DPN"
    }
}



class DPNConnection(
    private val tunnel: DatagramChannel,
    private val vpnInterface: ParcelFileDescriptor?,
) : Runnable {

    override fun run() {
        var iteration = 0
        while (iteration < 100) {
            iteration++
            try {
                tunnel.sendMessage("Hello DPN Server Side".toByteArray())
            } catch (e: Exception) {
                Log.e(TAG, "DPNConnection", e)
            }
        }

        tunnel.close()
    }

    private fun DatagramChannel.sendMessage(byteArray: ByteArray) {
        val buffer = ByteBuffer.allocate(1024)
        buffer.put(byteArray)
        buffer.flip()
        write(buffer)
    }

    companion object {
        private const val TAG = "DPNConnection"
    }
}

And here is the exception that I get when I try to send a package:

DPNConnection
                 java.net.PortUnreachableException
                    at sun.nio.ch.DatagramDispatcher.write0(Native Method)
                    at sun.nio.ch.DatagramDispatcher.write(DatagramDispatcher.java:60)
                    at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
                    at sun.nio.ch.IOUtil.write(IOUtil.java:65)
                    at sun.nio.ch.DatagramChannelImpl.write(DatagramChannelImpl.java:649)
                    at com.example.dpn.data.DPNConnection.sendMessage(DPNConnection.kt:35)
                    at com.example.dpn.data.DPNConnection.run(DPNConnection.kt:20)
                    at java.lang.Thread.run(Thread.java:1012)

I expected that I would see the message "Hello DPN Server Side" in the server logs. But this message does not reach the server and perhaps does not leave the device. I look in WireShark and don't see any packets after VpnService.establish is called. Moreover, if I send a packet to this port from another device, then the server side reads it

Upvotes: 1

Views: 114

Answers (0)

Related Questions