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