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-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"))

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

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 java.util.concurrent.ConcurrentHashMap
class ARPTable() {
class ARPTable(
) {
val routeTable: RouteTable = RouteTable()
val content = ConcurrentHashMap<Int, ARPInfo>()
suspend fun agingARP() {

View File

@ -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<ByteArray>(100)
val start_stop_channel = Channel<StartStopChanInfo>(100)
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
}
}