diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..005afb3 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,47 @@ + + + + \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index bc8a18c..b1c2b60 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -82,6 +82,9 @@ dependencies { implementation("com.google.protobuf:protobuf-java:4.29.3") implementation("com.google.protobuf:protobuf-kotlin:4.29.3") + implementation("androidx.navigation:navigation-compose:2.9.0") + implementation("io.coil-kt:coil-compose:2.6.0") + // implementation(libs.bcprov.jdk18on) implementation(libs.bcpkix.jdk18on) // implementation(files("libs/org.asxalex.sdlan/sdlan-1.0-SNAPSHOT.jar")) diff --git a/app/src/main/java/com/jihe/punchnet/PunchnetService.kt b/app/src/main/java/com/jihe/punchnet/PunchnetService.kt index a72302a..3fc6285 100644 --- a/app/src/main/java/com/jihe/punchnet/PunchnetService.kt +++ b/app/src/main/java/com/jihe/punchnet/PunchnetService.kt @@ -19,6 +19,9 @@ 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.run_sdlan import kotlinx.coroutines.CoroutineScope @@ -69,7 +72,7 @@ class PunchnetService : VpnService() , IfaceTun { override suspend fun send(content: ByteArray) { withContext(Dispatchers.IO) { - Log.d(TAG, "WROTE bytes to vpn service") + // Log.d(TAG, "WROTE bytes to vpn service") output?.write(content) } } @@ -104,7 +107,7 @@ class PunchnetService : VpnService() , IfaceTun { output?.close() vpnDescriptor?.close() - stopForeground(true) + // stopForeground(true) stopSelf() } @@ -117,6 +120,14 @@ class PunchnetService : VpnService() , IfaceTun { Log.d("DIR", "external storage = ${Environment.getExternalStorageDirectory().name}") Log.d("DIR", "filesdir = ${this.filesDir.path}") + arpTable.routeTable.clearRoute() + for (route in startArg?.routes?.toList()?:listOf()) { + val r = cidrToRouteDetail(route.targetNetCIDR, route.gateway) + if (r != null) { + arpTable.routeTable.addRoute(r) + } + } + val argument = Arguments( baseDir = this.filesDir.path, sn = "$server:1265", @@ -124,13 +135,12 @@ class PunchnetService : VpnService() , IfaceTun { nat_server1 = "$server:1265", nat_server2 = "47.98.178.3:1265", - token = "", + token = startArg?.token?:"", name = "tau", ) scope.launch { - - run_sdlan(iface, argument) + run_sdlan(iface, argument, startArg) } val notification = createNotification() @@ -141,8 +151,9 @@ class PunchnetService : VpnService() , IfaceTun { super.onStartCommand(intent, flags, startId) return if (intent?.action == ACTION_DISCONNECT) { + scope.cancel() disconnect() - START_NOT_STICKY + START_STICKY } else { val argument = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { intent?.getParcelableExtra("argument", PunchnetServiceArgument::class.java) @@ -174,8 +185,8 @@ class PunchnetService : VpnService() , IfaceTun { } override fun onDestroy() { - scope.cancel() - disconnect() + // scope.cancel() + // disconnect() super.onDestroy() } } \ No newline at end of file diff --git a/app/src/main/java/com/jihe/punchnet/helper/Screens.kt b/app/src/main/java/com/jihe/punchnet/helper/Screens.kt new file mode 100644 index 0000000..b614f3e --- /dev/null +++ b/app/src/main/java/com/jihe/punchnet/helper/Screens.kt @@ -0,0 +1,6 @@ +package com.jihe.punchnet.helper + +sealed class Screen(val route: String) { + object ScreenRoutes: Screen("routes") + object ScreenMain: Screen("main") +} \ No newline at end of file diff --git a/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt b/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt new file mode 100644 index 0000000..c46fba2 --- /dev/null +++ b/app/src/main/java/com/jihe/punchnet/screen/MainScreen.kt @@ -0,0 +1,105 @@ +package com.jihe.punchnet.screen + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController + +@Composable +fun MainApp( + navController: NavHostController, +) { + +} + +@Composable +fun CustomHeaderScreen( + text: String, + onBack: @Composable (() -> Unit)? = null, + onMenu: @Composable (() -> Unit)? = null, + body: @Composable () -> Unit, +) { + Column( + modifier = Modifier.fillMaxWidth() + ) { + Box ( + modifier = Modifier + .fillMaxWidth() + .height(56.dp) + .background(MaterialTheme.colorScheme.surfaceContainer), + //.padding(horizontal = 16.dp), + contentAlignment = Alignment.CenterStart + ) { + Row ( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + modifier = Modifier.fillMaxWidth() + ) { + + Box( + modifier = Modifier.fillMaxHeight() + .padding(horizontal = 8.dp) + .width(35.dp) + ) { + if (onBack != null) { + onBack() + } + } + + + Text( + text, + // color = MaterialTheme.colorScheme.onPrimary, + style = MaterialTheme.typography.titleLarge, + ) + + Box( + modifier = Modifier.fillMaxHeight() + .padding(horizontal = 8.dp) + .width(35.dp) + ) { + if (onMenu != null) { + onMenu() + /* + Icon( + modifier = Modifier + .clickable { + onMenu() + } + .fillMaxSize(), + imageVector = Icons.Filled.Menu, + contentDescription = "setting", + // tint = MaterialTheme.colorScheme.onPrimary + ) + */ + + } + } + + + } + } + + Box(modifier = Modifier.fillMaxSize()) { + body() + } + } +} diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt index d1c67db..8facc70 100644 --- a/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt +++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/arp.kt @@ -11,7 +11,10 @@ import kotlinx.coroutines.async import kotlinx.coroutines.delay import java.util.concurrent.ConcurrentHashMap -class ARPTable() { +class ARPTable( +) { + val routeTable: RouteTable = RouteTable() + val content = ConcurrentHashMap() suspend fun agingARP() { diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/libs.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/libs.kt index 83e7849..dbe3ac4 100644 --- a/app/src/main/java/com/jihe/punchnet/sdlan/network/libs.kt +++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/libs.kt @@ -1,6 +1,7 @@ package com.jihe.punchnet.sdlan.network import android.util.Log +import com.jihe.punchnet.PunchnetServiceArgument import com.jihe.punchnet.protobuf.PunchProto.SDLDevAddr import com.jihe.punchnet.protobuf.PunchProto.SDLRegisterSuper import com.jihe.punchnet.protobuf.PunchProto.SDLRegisterSuperAck @@ -125,7 +126,7 @@ suspend fun onMessage(data: SDLanTCP) { } -suspend fun run_sdlan(iface: Iface, argument: Arguments) { +suspend fun run_sdlan(iface: Iface, argument: Arguments, routeinfo: PunchnetServiceArgument?) { UniqueNodeID.setBaseDir(argument.baseDir) val edgeUUID = UniqueNodeID.getUUID() val config = parseConfig(edgeUUID, argument) @@ -135,6 +136,8 @@ suspend fun run_sdlan(iface: Iface, argument: Arguments) { } return } + + val toSocket = Channel(100) val start_stop_channel = Channel(100) initEdge(iface, argument.token, config, toSocket, start_stop_channel) diff --git a/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt b/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt new file mode 100644 index 0000000..d7c0a27 --- /dev/null +++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt @@ -0,0 +1,122 @@ +package com.jihe.punchnet.sdlan.network + +import com.jihe.punchnet.sdlan.logs.TerminalLogger +import org.bouncycastle.util.Strings +import java.util.LinkedList +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.locks.ReentrantReadWriteLock + +data class RouteDetail( + // Int representation of 255.255.255.0 + val mask: Int, + val gw: Int, + val maskedAddr: Int, +) + +fun cidrToRouteDetail(cidr: String, gw: String): RouteDetail? { + val ipAndMaskDigit = Strings.split(cidr, '/') + if (ipAndMaskDigit.size != 2) { + TerminalLogger.debugf { "cidr format error: ${cidr}" } + return null + } + val ip = ipStringToInt(ipAndMaskDigit[0]) + val mask = maskDigitToInt(ipAndMaskDigit[1].toInt()) + + if (ip == null || mask == null) { + TerminalLogger.debugf { "cidr format error2: ${cidr}" } + return null + } + + val gateway = ipStringToInt(gw) + if (gateway == null) { + TerminalLogger.debugf { "gateway format error: ${gateway}" } + return null + } + + if ((ip and mask) != ip) { + TerminalLogger.debugf { "net not masked" } + return null + } + + return RouteDetail( + mask = mask, + gw = gateway, + maskedAddr = ip, + ) +} + +fun maskDigitToInt(digit: Int): Int? { + if (digit <= 0 || digit >= 32) { + return null + } + return ((2 shl (32-digit)) - 1) +} + +fun ipStringToInt(ip: String): Int? { + val ipFiltered = ip.trim { it.isWhitespace() } + val ipSeg = Strings.split(ipFiltered, '.') + if (ipSeg.size != 4) { + return null + } + return ((ipSeg[0].toInt() and 0xff) shl 24) + + ((ipSeg[1].toInt() and 0xff) shl 16) + + ((ipSeg[2].toInt() and 0xff) shl 8) + + (ipSeg[3].toInt() and 0xff) + +} + +class RouteTable() { + val lock = ReentrantReadWriteLock() + + val routeInfo: MutableList = mutableListOf() + + fun addRoute(vararg routes: RouteDetail) { + try { + lock.writeLock().lock() + for (route in routes) { + routeInfo.add(route) + } + } finally { + lock.writeLock().unlock() + } + } + + fun clearRoute() { + try { + lock.writeLock().lock() + routeInfo.clear() + } finally { + lock.writeLock().unlock() + } + } + + fun getGeteway(ip: Int): Int? { + try { + lock.readLock().lock() + for (item in routeInfo) { + if ((ip and item.mask) == item.maskedAddr) { + return item.gw + } + } + return null + } finally { + lock.readLock().unlock() + } + } + +} + +class RouteTable2( + val initRouteInfo: List +) { + val routeInfo: Array = initRouteInfo.toTypedArray() + + fun getGeteway(ip: Int): Int? { + for (item in routeInfo) { + if ((ip and item.mask) == item.maskedAddr) { + return item.gw + } + } + return null + } +} \ No newline at end of file