add route table, needs to handle route info in VPNService and arp table

This commit is contained in:
asxalex 2025-07-08 11:13:49 +08:00
parent f04b16f7c4
commit e0061c1277
8 changed files with 310 additions and 10 deletions

View File

@ -0,0 +1,47 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
<option name="composableFile" value="true" />
</inspection_tool>
</profile>
</component>

View File

@ -82,6 +82,9 @@ dependencies {
implementation("com.google.protobuf:protobuf-java:4.29.3") implementation("com.google.protobuf:protobuf-java:4.29.3")
implementation("com.google.protobuf:protobuf-kotlin: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.bcprov.jdk18on)
implementation(libs.bcpkix.jdk18on) implementation(libs.bcpkix.jdk18on)
// implementation(files("libs/org.asxalex.sdlan/sdlan-1.0-SNAPSHOT.jar")) // implementation(files("libs/org.asxalex.sdlan/sdlan-1.0-SNAPSHOT.jar"))

View File

@ -19,6 +19,9 @@ import com.jihe.punchnet.sdlan.network.ARPTable
import com.jihe.punchnet.sdlan.network.ARPWaitList import com.jihe.punchnet.sdlan.network.ARPWaitList
import com.jihe.punchnet.sdlan.network.DeviceConfig import com.jihe.punchnet.sdlan.network.DeviceConfig
import com.jihe.punchnet.sdlan.network.IfaceTun 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.ipInt2ByteArray
import com.jihe.punchnet.sdlan.network.run_sdlan import com.jihe.punchnet.sdlan.network.run_sdlan
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -69,7 +72,7 @@ class PunchnetService : VpnService() , IfaceTun {
override suspend fun send(content: ByteArray) { override suspend fun send(content: ByteArray) {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
Log.d(TAG, "WROTE bytes to vpn service") // Log.d(TAG, "WROTE bytes to vpn service")
output?.write(content) output?.write(content)
} }
} }
@ -104,7 +107,7 @@ class PunchnetService : VpnService() , IfaceTun {
output?.close() output?.close()
vpnDescriptor?.close() vpnDescriptor?.close()
stopForeground(true) // stopForeground(true)
stopSelf() stopSelf()
} }
@ -117,6 +120,14 @@ class PunchnetService : VpnService() , IfaceTun {
Log.d("DIR", "external storage = ${Environment.getExternalStorageDirectory().name}") Log.d("DIR", "external storage = ${Environment.getExternalStorageDirectory().name}")
Log.d("DIR", "filesdir = ${this.filesDir.path}") 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( val argument = Arguments(
baseDir = this.filesDir.path, baseDir = this.filesDir.path,
sn = "$server:1265", sn = "$server:1265",
@ -124,13 +135,12 @@ class PunchnetService : VpnService() , IfaceTun {
nat_server1 = "$server:1265", nat_server1 = "$server:1265",
nat_server2 = "47.98.178.3:1265", nat_server2 = "47.98.178.3:1265",
token = "", token = startArg?.token?:"",
name = "tau", name = "tau",
) )
scope.launch { scope.launch {
run_sdlan(iface, argument, startArg)
run_sdlan(iface, argument)
} }
val notification = createNotification() val notification = createNotification()
@ -141,8 +151,9 @@ class PunchnetService : VpnService() , IfaceTun {
super.onStartCommand(intent, flags, startId) super.onStartCommand(intent, flags, startId)
return if (intent?.action == ACTION_DISCONNECT) { return if (intent?.action == ACTION_DISCONNECT) {
scope.cancel()
disconnect() disconnect()
START_NOT_STICKY START_STICKY
} else { } else {
val argument = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { val argument = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent?.getParcelableExtra("argument", PunchnetServiceArgument::class.java) intent?.getParcelableExtra("argument", PunchnetServiceArgument::class.java)
@ -174,8 +185,8 @@ class PunchnetService : VpnService() , IfaceTun {
} }
override fun onDestroy() { override fun onDestroy() {
scope.cancel() // scope.cancel()
disconnect() // disconnect()
super.onDestroy() super.onDestroy()
} }
} }

View File

@ -0,0 +1,6 @@
package com.jihe.punchnet.helper
sealed class Screen(val route: String) {
object ScreenRoutes: Screen("routes")
object ScreenMain: Screen("main")
}

View File

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

View File

@ -11,7 +11,10 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class ARPTable() { class ARPTable(
) {
val routeTable: RouteTable = RouteTable()
val content = ConcurrentHashMap<Int, ARPInfo>() val content = ConcurrentHashMap<Int, ARPInfo>()
suspend fun agingARP() { suspend fun agingARP() {

View File

@ -1,6 +1,7 @@
package com.jihe.punchnet.sdlan.network package com.jihe.punchnet.sdlan.network
import android.util.Log import android.util.Log
import com.jihe.punchnet.PunchnetServiceArgument
import com.jihe.punchnet.protobuf.PunchProto.SDLDevAddr import com.jihe.punchnet.protobuf.PunchProto.SDLDevAddr
import com.jihe.punchnet.protobuf.PunchProto.SDLRegisterSuper import com.jihe.punchnet.protobuf.PunchProto.SDLRegisterSuper
import com.jihe.punchnet.protobuf.PunchProto.SDLRegisterSuperAck 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) UniqueNodeID.setBaseDir(argument.baseDir)
val edgeUUID = UniqueNodeID.getUUID() val edgeUUID = UniqueNodeID.getUUID()
val config = parseConfig(edgeUUID, argument) val config = parseConfig(edgeUUID, argument)
@ -135,6 +136,8 @@ suspend fun run_sdlan(iface: Iface, argument: Arguments) {
} }
return return
} }
val toSocket = Channel<ByteArray>(100) val toSocket = Channel<ByteArray>(100)
val start_stop_channel = Channel<StartStopChanInfo>(100) val start_stop_channel = Channel<StartStopChanInfo>(100)
initEdge(iface, argument.token, config, toSocket, start_stop_channel) initEdge(iface, argument.token, config, toSocket, start_stop_channel)

View File

@ -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<RouteDetail> = 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<RouteDetail>
) {
val routeInfo: Array<RouteDetail> = initRouteInfo.toTypedArray()
fun getGeteway(ip: Int): Int? {
for (item in routeInfo) {
if ((ip and item.mask) == item.maskedAddr) {
return item.gw
}
}
return null
}
}