227 lines
7.3 KiB
Kotlin
227 lines
7.3 KiB
Kotlin
package com.jihe.punchnet
|
|
|
|
import android.app.Notification
|
|
import android.app.NotificationChannel
|
|
import android.app.NotificationManager
|
|
import android.content.Context
|
|
import android.content.Intent
|
|
import android.net.VpnService
|
|
import android.os.Build
|
|
import android.os.Environment
|
|
import android.os.ParcelFileDescriptor
|
|
import android.util.Log
|
|
import android.widget.Toast
|
|
import androidx.core.app.NotificationCompat
|
|
import androidx.lifecycle.ViewModelProvider
|
|
import com.jihe.punchnet.data.RouteItem
|
|
import com.jihe.punchnet.data.RouteViewModel
|
|
import com.jihe.punchnet.sdlan.config.Arguments
|
|
import com.jihe.punchnet.sdlan.config.toIPV4String
|
|
import com.jihe.punchnet.sdlan.logs.TerminalLogger
|
|
import com.jihe.punchnet.sdlan.network.ARPTable
|
|
import com.jihe.punchnet.sdlan.network.ARPWaitList
|
|
import com.jihe.punchnet.sdlan.network.DeviceConfig
|
|
import com.jihe.punchnet.sdlan.network.IfaceTun
|
|
import com.jihe.punchnet.sdlan.network.RouteDetail
|
|
import com.jihe.punchnet.sdlan.network.RouteTable
|
|
import com.jihe.punchnet.sdlan.network.cidrToRouteDetail
|
|
import com.jihe.punchnet.sdlan.network.ipInt2ByteArray
|
|
import com.jihe.punchnet.sdlan.network.maskIPToDigit
|
|
import com.jihe.punchnet.sdlan.network.run_sdlan
|
|
import com.jihe.punchnet.sdlan.utils.ipToString
|
|
import kotlinx.coroutines.CoroutineScope
|
|
import kotlinx.coroutines.Dispatchers
|
|
import kotlinx.coroutines.async
|
|
import kotlinx.coroutines.cancel
|
|
import kotlinx.coroutines.launch
|
|
import kotlinx.coroutines.withContext
|
|
import java.io.FileInputStream
|
|
import java.io.FileOutputStream
|
|
|
|
class PunchnetService : VpnService() , IfaceTun {
|
|
private val TAG = "PunchnetService"
|
|
private var scope = CoroutineScope(Dispatchers.IO)
|
|
|
|
private val routes: MutableList<RouteItem> = mutableListOf()
|
|
|
|
private var vpnDescriptor: ParcelFileDescriptor? = null
|
|
|
|
|
|
var input: FileInputStream? = null
|
|
var output: FileOutputStream? = null
|
|
|
|
override var arpTable = ARPTable(scope)
|
|
override val arpWaitList = ARPWaitList()
|
|
|
|
var config: DeviceConfig = DeviceConfig(1400)
|
|
|
|
companion object {
|
|
const val ACTION_CONNECT = "com.jihe.punchnet.punchnetservice.CONNECT"
|
|
const val ACTION_DISCONNECT = "com.jihe.punchnet.punchnetservice.DISCONNECT"
|
|
}
|
|
|
|
override suspend fun recv(): ByteArray {
|
|
val result = withContext(Dispatchers.IO) {
|
|
val result = ByteArray(1500)
|
|
try {
|
|
var size = input?.read(result)
|
|
if (size == null) {
|
|
println("xxx failed to read")
|
|
size = 0
|
|
} else {
|
|
println("xxx got $size bytes")
|
|
}
|
|
|
|
// val size = input?.read(result)?:0
|
|
// Log.d(TAG, "RECEIVED $size bytes")
|
|
result.copyOf(size)
|
|
// result.slice(0..<size).toByteArray()
|
|
} catch (e: Exception) {
|
|
ByteArray(0)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
override suspend fun send(content: ByteArray) {
|
|
withContext(Dispatchers.IO) {
|
|
// Log.d(TAG, "WROTE bytes to vpn service")
|
|
try {
|
|
output?.write(content)
|
|
} catch (e: Exception) {
|
|
TerminalLogger.errorf { "failed to write $e" }
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun stopVpn() {
|
|
}
|
|
|
|
override suspend fun reload_config(config: DeviceConfig) {
|
|
config.mtu = 1400
|
|
this.config = config
|
|
val ip = ipInt2ByteArray(config.ip.netAddr).toIPV4String()
|
|
TerminalLogger.debugf {"got ip address from remote: ${ip}"}
|
|
withContext(Dispatchers.IO) {
|
|
input?.close()
|
|
output?.close()
|
|
}
|
|
vpnDescriptor?.close()
|
|
|
|
var tempVpnDescriptor = Builder()
|
|
.setMtu(config.mtu)
|
|
.addAddress(ip, config.ip.netBitLen.toInt())
|
|
.setBlocking(true)
|
|
|
|
for (route in routes) {
|
|
tempVpnDescriptor = tempVpnDescriptor.addRoute(ipToString(route.net_ip), maskIPToDigit(route.mask_ip))
|
|
}
|
|
|
|
vpnDescriptor = tempVpnDescriptor
|
|
.establish()
|
|
|
|
input = FileInputStream(vpnDescriptor!!.fileDescriptor)
|
|
output = FileOutputStream(vpnDescriptor!!.fileDescriptor)
|
|
}
|
|
|
|
private fun disconnect() {
|
|
Toast.makeText(this, "stop vpn called", Toast.LENGTH_LONG).show()
|
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
input?.close()
|
|
output?.close()
|
|
vpnDescriptor?.close()
|
|
|
|
vpnDescriptor = null
|
|
|
|
stopSelf()
|
|
}
|
|
|
|
private fun connect(startArg: PunchnetServiceArgument?) {
|
|
val iface = this
|
|
|
|
val server = "punchnet.aioe.tech"
|
|
|
|
Log.d("DIR", "datadir = ${Environment.getDataDirectory().name}")
|
|
Log.d("DIR", "external storage = ${Environment.getExternalStorageDirectory().name}")
|
|
Log.d("DIR", "filesdir = ${this.filesDir.path}")
|
|
|
|
arpTable.routeTable.clearRoute()
|
|
routes.clear()
|
|
for (route in startArg?.routes?.toList()?:listOf()) {
|
|
val r = RouteDetail(
|
|
mask = route.mask_ip,
|
|
gw = route.gateway,
|
|
maskedAddr = route.net_ip,
|
|
)
|
|
routes.add(route)
|
|
// val r = cidrToRouteDetail(route.targetNetCIDR, route.gateway)
|
|
arpTable.routeTable.addRoute(r)
|
|
}
|
|
|
|
val argument = Arguments(
|
|
baseDir = this.filesDir.path,
|
|
sn = "$server:1265",
|
|
tcp = "$server:18083",
|
|
|
|
nat_server1 = "$server:1265",
|
|
nat_server2 = "47.98.178.3:1265",
|
|
token = startArg?.token?:"",
|
|
name = "tau",
|
|
)
|
|
|
|
scope.launch {
|
|
run_sdlan(scope, iface, argument, startArg)
|
|
}
|
|
|
|
val notification = createNotification()
|
|
startForeground(1, notification)
|
|
}
|
|
|
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
super.onStartCommand(intent, flags, startId)
|
|
|
|
return if (intent?.action == ACTION_DISCONNECT) {
|
|
scope.cancel()
|
|
scope = CoroutineScope(Dispatchers.IO)
|
|
arpTable = ARPTable(scope)
|
|
|
|
disconnect()
|
|
START_NOT_STICKY
|
|
} else {
|
|
val argument = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
intent?.getParcelableExtra("argument", PunchnetServiceArgument::class.java)
|
|
} else {
|
|
intent?.getParcelableExtra("argument")
|
|
}
|
|
println("argument = ${argument}")
|
|
connect(argument)
|
|
START_STICKY
|
|
}
|
|
// return super.onStartCommand(intent, flags, startId)
|
|
}
|
|
|
|
private fun createNotification(): Notification {
|
|
val channelId = "punchnet_channel"
|
|
val channel = NotificationChannel (
|
|
channelId,
|
|
"punchnet",
|
|
NotificationManager.IMPORTANCE_DEFAULT
|
|
)
|
|
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
|
manager.createNotificationChannel(channel)
|
|
|
|
return NotificationCompat.Builder(this, channelId)
|
|
.setContentTitle("Punchnet Service")
|
|
.setContentText("Punchnet is running")
|
|
.setSmallIcon(R.drawable.ic_vpn)
|
|
.build()
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
// scope.cancel()
|
|
// disconnect()
|
|
scope.cancel()
|
|
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
super.onDestroy()
|
|
}
|
|
} |