0

Hey Guys I tried to make simple android ssh vpn i'm new to android programming and made simple logic to just work and everything in that code seems ok but somehow when i run the code i have Invalid Argument Error and idk what's the problem heres's the code

vpn service logic


import android.content.Intent
import android.net.VpnService
import android.os.ParcelFileDescriptor
import android.util.Log
import com.jcraft.jsch.ChannelDirectTCPIP
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.io.FileInputStream
import java.io.FileOutputStream
import java.net.Inet4Address
import java.net.InetSocketAddress
import java.net.Socket
import java.nio.ByteBuffer
import java.nio.channels.DatagramChannel

class ServiceAction: VpnService() {
    private val tag = "serviceAction"

    private var vpnInterface: ParcelFileDescriptor? = null
    private var sshClient: SshLogic? = null
    private var directTcpIpChannel: ChannelDirectTCPIP? = null
    private var isConnected: Boolean? = false

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        if (intent?.getStringExtra("COMMAND") == "STOP"){
            stopVPN()
        }
        Log.d(tag, "Activity Extras -> ${intent?.extras.toString()}")
        return START_STICKY
    }

    override fun onDestroy() {
        super.onDestroy()
        stopSelf()
    }

    override fun onCreate() {
        super.onCreate()
        Log.d(tag, "Creating Service Action")
        //Initialize The VPN Interface
        setupVPN()
        //Setup the ssh Client
        initializeSSHClient()
        //start The VPN
        startVPN()

    }

    @OptIn(DelicateCoroutinesApi::class)
    private fun startVPN(){
        GlobalScope.launch(Dispatchers.IO) {
            runVpn()
        }
    }

    @OptIn(DelicateCoroutinesApi::class)
    private fun initializeSSHClient(){
        GlobalScope.launch(Dispatchers.IO) {
            sshClient = SshLogic("server hostname", port, "username", "password")
            isConnected = sshClient?.connect()
            directTcpIpChannel = sshClient!!.openChannel()
        }
    }

    private fun stopVPN(){
        sshClient?.disconnect()
        vpnInterface?.close()
        stopSelf()
        Log.d(tag, "Stop Vpn!")
    }

    private fun setupVPN(){
        val packageName = applicationContext.packageName
        val builder = Builder()
            .addAddress("79.76.0.1", 24)
            .addDnsServer("9.9.9.9")
            .addRoute("0.0.0.0", 0)
            .setSession(tag)
            .addDisallowedApplication(packageName)
            .setMtu(1500)
        vpnInterface = builder.establish()
        Log.d(tag, "VPN Interface Established")
    }

    @OptIn(DelicateCoroutinesApi::class)
    fun runVpn() {

        Thread.sleep(3000L)
        Log.d(tag, "Running VPN Loop...")
        val vpnInterfaceChannelIn = FileInputStream(vpnInterface?.fileDescriptor).channel
        val vpnInterfaceChannelOut = FileOutputStream(vpnInterface?.fileDescriptor).channel
        Log.d(tag, "Vpn Interface state ${vpnInterface?.fileDescriptor?.valid()}")

        //Log.d(tag, "VPN  Interface File -> ${vpnInterface?.fileDescriptor}")

        directTcpIpChannel?.let {
            val channelInput = it.inputStream
            val channelOutput = it.outputStream
            it.connect()
            Log.d(tag, "VPN Loop Started with channel id of ${it.id}")
            if (it.isConnected){

                GlobalScope.launch(Dispatchers.IO) {

                    loop@ while (true){
                        val buffer = ByteBuffer.allocate(1500)
                        buffer.clear()
                        val readBytes = vpnInterfaceChannelIn.read(buffer)
                        if (readBytes <= 0){
                            continue@loop
                        }
                        Log.d(tag, "Incoming Traffic w/ Length $readBytes")
                        buffer.flip()
                        channelOutput.write(buffer.array())
                        channelOutput.flush()

                        buffer.clear()
                        val channelReadBytes = channelInput.read(buffer.array())
                        if (channelReadBytes <= 0){
                            continue@loop
                        }
                        Log.d(tag, "Length From Server $channelReadBytes")
                        //There is a Bug here `out of the band index` fix it
                        try {
                            Log.d(tag, "Length of prepared data to write in to interface ${buffer.get()}")
                            vpnInterfaceChannelOut.write(buffer)
                        } catch (e: Exception){
                            e.printStackTrace()
                            //stopVPN()
                        }

                    }
                }

            }else{
                Log.d(tag, "Channel is not Connected!")
                stopVPN()
            }
        }

        //channelIn.close()
        //channelOut.close()
        //stopVPN()
    }
}

ssh connection logic


import android.util.Log
import com.jcraft.jsch.ChannelDirectTCPIP
import com.jcraft.jsch.JSch
import com.jcraft.jsch.JSchException
import com.jcraft.jsch.Session

class SshLogic(
    private val hostname : String,
    private val port : Int,
    private val username : String,
    private val password : String
) {
    private lateinit var session: Session
    private val tag = "sshLogic"
    fun connect() : Boolean {
        Log.d(tag, "Trying To Connect")
        val jsch = JSch()
        session = jsch.getSession(username, hostname, port)
        session.setPassword(password)
        session.setConfig("StrictHostKeyChecking", "no")

        try {
            session.connect()
            Log.d(tag, "Connected to ssh server")
            Log.d(tag, "User Info -> ${session.userInfo} - Status : ${session.isConnected}")
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
        Log.d(tag, "Failed To Connect")
        return false
    }

    fun disconnect() {
        Log.d(tag, "Disconnecting")
        session.disconnect()
    }

    fun openChannel(): ChannelDirectTCPIP? {
        Log.d(tag, "Trying To Open Channel")
        var channel: ChannelDirectTCPIP? = null
        try {
            channel = session.openChannel("direct-tcpip") as ChannelDirectTCPIP
            Log.d(tag, "Channel Opened Successfully -> ${channel.id}")
        } catch (e: JSchException){
            Log.d(tag, "Error Opening Channel")
            e.printStackTrace()
        }
        channel?.setHost(hostname)
        channel?.setPort(port)
        return channel
    }
}

i was expecting to capture all the traffic and tunnel them to the ssh server

rynsm4rt
  • 13
  • 2

1 Answers1

0

There are some minor things I guess that you need to checkup. However this doesn't seem to have any syntax or logic trouble.

So, I believe this needs the full project files to be checked again. If thats possible please hand me a copy of them using an upload service or sth.

  1. Incorrect Channel Usage:

In your runVpn() function, there is a part where you're using ByteBuffer.get() to get the length of the prepared data to write into the interface. This is incorrect, and it's causing the "out of the band index" issue. You should use buffer.remaining() instead to get the number of bytes remaining in the buffer.

Replace this line:

Log.d(tag, "Length of prepared data to write in to interface ${buffer.get()}")

With this line:

Log.d(tag, "Length of prepared data to write in to interface ${buffer.remaining()}")
  1. Invalid VpnInterfaceChannelOut Usage:

In your runVpn() function, you are using vpnInterfaceChannelOut.write(buffer.array()) to write the buffer data to vpnInterfaceChannelOut. This might cause issues because the array contains the entire buffer capacity, not just the actual data. Instead, you should use buffer.flip() before writing to ensure that only the valid data is written.

Replace this line:

channelOutput.write(buffer.array())

With these lines:

buffer.flip()
val bufferData = ByteArray(buffer.remaining())
buffer.get(bufferData)
channelOutput.write(ByteBuffer.wrap(bufferData))
  1. Non-Blocking Looping:

In your runVpn() function, it seems you have a while loop that doesn't handle non-blocking behavior and could lead to high CPU usage. To address this, consider introducing a sleep duration within the loop to give the CPU some rest. You might also want to introduce a more sophisticated non-blocking approach.

  1. Ensure Buffer Synchronization:

When using buffers across different threads, you need to ensure proper synchronization to avoid data corruption. Consider using thread-safe mechanisms like ByteBuffer.allocateDirect() and wrapping it in a ByteBuffer instance, which can be safely passed between threads.

Please keep in mind that debugging complex network-related code like this can be challenging, and it might involve testing and iterating multiple times to identify and fix issues. Also, the quality of the VPN tunnel might depend on various factors like the stability of the SSH server, network conditions, and more.

Hope you'll fix this cause I was also looking for some samples about ssh connection to be used as a vpn.

Sss
  • 1