diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..9b34f35
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+punchnet
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
index cde3e19..7061a0d 100644
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -49,6 +49,10 @@
+
+
+
+
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 74dd639..b2c751a 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -1,4 +1,3 @@
-
diff --git a/app/release/app-release.apk b/app/release/app-release.apk
new file mode 100644
index 0000000..884c4a2
Binary files /dev/null and b/app/release/app-release.apk differ
diff --git a/app/release/baselineProfiles/0/app-release.dm b/app/release/baselineProfiles/0/app-release.dm
new file mode 100644
index 0000000..58c9dc1
Binary files /dev/null and b/app/release/baselineProfiles/0/app-release.dm differ
diff --git a/app/release/baselineProfiles/1/app-release.dm b/app/release/baselineProfiles/1/app-release.dm
new file mode 100644
index 0000000..f26fd47
Binary files /dev/null and b/app/release/baselineProfiles/1/app-release.dm differ
diff --git a/app/release/output-metadata.json b/app/release/output-metadata.json
new file mode 100644
index 0000000..da7b5ab
--- /dev/null
+++ b/app/release/output-metadata.json
@@ -0,0 +1,37 @@
+{
+ "version": 3,
+ "artifactType": {
+ "type": "APK",
+ "kind": "Directory"
+ },
+ "applicationId": "com.jihe.punchnet",
+ "variantName": "release",
+ "elements": [
+ {
+ "type": "SINGLE",
+ "filters": [],
+ "attributes": [],
+ "versionCode": 1,
+ "versionName": "1.0",
+ "outputFile": "app-release.apk"
+ }
+ ],
+ "elementType": "File",
+ "baselineProfiles": [
+ {
+ "minApi": 28,
+ "maxApi": 30,
+ "baselineProfiles": [
+ "baselineProfiles/1/app-release.dm"
+ ]
+ },
+ {
+ "minApi": 31,
+ "maxApi": 2147483647,
+ "baselineProfiles": [
+ "baselineProfiles/0/app-release.dm"
+ ]
+ }
+ ],
+ "minSdkVersionForDexing": 26
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 352ce8d..6e2b630 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -11,7 +11,7 @@
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
- android:icon="@mipmap/ic_launcher"
+ android:icon="@drawable/punchnet_log"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
diff --git a/app/src/main/java/com/jihe/punchnet/PunchnetService.kt b/app/src/main/java/com/jihe/punchnet/PunchnetService.kt
index 3fc6285..109f582 100644
--- a/app/src/main/java/com/jihe/punchnet/PunchnetService.kt
+++ b/app/src/main/java/com/jihe/punchnet/PunchnetService.kt
@@ -12,6 +12,9 @@ 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
@@ -23,7 +26,9 @@ 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.cancel
@@ -34,10 +39,13 @@ import java.io.FileOutputStream
class PunchnetService : VpnService() , IfaceTun {
private val TAG = "PunchnetService"
- private val scope = CoroutineScope(Dispatchers.Default)
+ private val scope = CoroutineScope(Dispatchers.IO)
+
+ private val routes: MutableList = mutableListOf()
private var vpnDescriptor: ParcelFileDescriptor? = null
+
var input: FileInputStream? = null
var output: FileOutputStream? = null
@@ -91,10 +99,16 @@ class PunchnetService : VpnService() , IfaceTun {
}
vpnDescriptor?.close()
- vpnDescriptor = Builder()
+ 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)
@@ -107,7 +121,7 @@ class PunchnetService : VpnService() , IfaceTun {
output?.close()
vpnDescriptor?.close()
- // stopForeground(true)
+ stopForeground(true)
stopSelf()
}
@@ -121,11 +135,16 @@ class PunchnetService : VpnService() , IfaceTun {
Log.d("DIR", "filesdir = ${this.filesDir.path}")
arpTable.routeTable.clearRoute()
+ routes.clear()
for (route in startArg?.routes?.toList()?:listOf()) {
- val r = cidrToRouteDetail(route.targetNetCIDR, route.gateway)
- if (r != null) {
- arpTable.routeTable.addRoute(r)
- }
+ 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(
diff --git a/app/src/main/java/com/jihe/punchnet/PunchnetServiceArgument.kt b/app/src/main/java/com/jihe/punchnet/PunchnetServiceArgument.kt
index e55c509..a2588ca 100644
--- a/app/src/main/java/com/jihe/punchnet/PunchnetServiceArgument.kt
+++ b/app/src/main/java/com/jihe/punchnet/PunchnetServiceArgument.kt
@@ -2,6 +2,7 @@ package com.jihe.punchnet
import android.os.Parcel
import android.os.Parcelable
+import com.jihe.punchnet.data.RouteItem
data class RouteInfo(
// 192.168.80.0/24
@@ -12,14 +13,16 @@ data class RouteInfo(
data class PunchnetServiceArgument(
val token: String,
- val routes: Array,
+ val routes: Array,
): Parcelable {
override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeString(token)
dest.writeInt(routes.size)
routes.forEach { item ->
- dest.writeString(item.targetNetCIDR)
- dest.writeString(item.gateway)
+ dest.writeLong(item.id)
+ dest.writeInt(item.net_ip)
+ dest.writeInt(item.mask_ip)
+ dest.writeInt(item.gateway)
}
}
@@ -31,13 +34,18 @@ data class PunchnetServiceArgument(
override fun createFromParcel(source: Parcel): PunchnetServiceArgument {
val token = source.readString()!!
val size = source.readInt()
- var routes = mutableListOf()
+ var routes = mutableListOf()
for (i in 0.. = _isRunning
- private val _hasVpnPermission = mutableStateOf(false)
- val hasVpnPermission: State = _hasVpnPermission
-
- fun setVPNPermission(isGranted: Boolean) {
- _hasVpnPermission.value = isGranted
+ fun startService(context: Context, routes: Array) {
+ // should has the permission
+ startVpnService(context, routes)
+ _isRunning.value = true
}
- fun startService(context: Context, noPermissionCallback: (Intent)->Unit) {
- val intent = VpnService.prepare(context)
- if (intent != null) {
- noPermissionCallback(intent)
- } else {
- startVpnService(context)
- _isRunning.value = true
- }
- }
-
- private fun startVpnService(context: Context) {
+ private fun startVpnService(context: Context, routes: Array) {
val intent = Intent(context, PunchnetService::class.java)
intent.putExtra("argument", PunchnetServiceArgument(
"",
- arrayOf(
- RouteInfo(
- "192.168.80.0/24", "10.211.188.2"
- )
- )
- )
- )
+ routes,
+ ))
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
context.startForegroundService(intent)
} else {
diff --git a/app/src/main/java/com/jihe/punchnet/helper/Screens.kt b/app/src/main/java/com/jihe/punchnet/helper/Screens.kt
index b614f3e..cc9720a 100644
--- a/app/src/main/java/com/jihe/punchnet/helper/Screens.kt
+++ b/app/src/main/java/com/jihe/punchnet/helper/Screens.kt
@@ -1,6 +1,80 @@
package com.jihe.punchnet.helper
+import com.jihe.punchnet.data.RouteItem
+import com.jihe.punchnet.sdlan.network.maskDigitToInt
+
sealed class Screen(val route: String) {
object ScreenRoutes: Screen("routes")
object ScreenMain: Screen("main")
+}
+
+fun IntToIPString(ip: Int): String {
+ val one = ((ip ushr 24) and 0x000000ff)
+ val two = ((ip ushr 16) and 0x000000ff)
+ val three = ((ip ushr 8) and 0x000000ff)
+ val four = ((ip) and 0x000000ff)
+ return "${one}.${two}.${three}.${four}"
+}
+
+fun IPStringToInt(ipStr: String): Int? {
+ val digits = ipStr.split('.').filter{
+ val single = try {
+ it.toInt()
+ } catch(e: Exception) {
+ return@filter false
+ }
+ single in 0..255
+ }
+ if (digits.size != 4) {
+ return null
+ }
+ val result: Int
+ try {
+ result = (digits[0].toInt() shl 24) +
+ (digits[1].toInt() shl 16) +
+ (digits[2].toInt() shl 8) +
+ (digits[3].toInt())
+ } catch(e: Exception) {
+ return null
+ }
+
+ return result
+}
+
+fun parseCIDRAndGW(cidr: String, gw: String): RouteItem? {
+ val cidrSplit = cidr.split('/')
+ var ip = 0
+ var digit = 0
+
+ if (cidrSplit.size == 1) {
+ digit = 32
+ } else if (cidrSplit.size == 2) {
+ try {
+ val k = cidrSplit[1].toInt()
+ if (k in 1..32) {
+ digit = k
+ } else {
+ return null
+ }
+ } catch (e: Exception) {
+ return null
+ }
+ } else {
+ return null
+ }
+
+ val ipvalue = IPStringToInt(cidrSplit[0])
+ val gateway = IPStringToInt(gw)
+ if (ipvalue == null) {
+ return null
+ }
+ if (gateway == null) {
+ return null
+ }
+ val mask = maskDigitToInt(digit)!!
+ return RouteItem(
+ net_ip = ipvalue,
+ gateway = gateway,
+ mask_ip = mask,
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt b/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt
index 3d9cbec..b8111d8 100644
--- a/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt
+++ b/app/src/main/java/com/jihe/punchnet/screen/AppNavigationItems.kt
@@ -54,12 +54,22 @@ fun AppNavHost2(
}
composable(Screen.RouteScreen.route) {
CustomHeaderScreen(
- "光强计"
+ "路由设置"
) {
+ RouteScreen(!serviceViewModel.isRunning.value, routeViewModel)
// LightScreen()
}
}
composable(Screen.ProfileScreen.route) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text(
+ "TODO",
+ style = MaterialTheme.typography.titleLarge,
+ )
+ }
// Profile(dbdao, navController)
}
}
diff --git a/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt b/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt
index fae10ba..31ab650 100644
--- a/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt
+++ b/app/src/main/java/com/jihe/punchnet/screen/HomeScreen.kt
@@ -4,6 +4,7 @@ import android.app.Activity.RESULT_CANCELED
import android.app.Activity.RESULT_OK
import android.content.Context
import android.content.Intent
+import android.net.VpnService
import android.os.Build
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
@@ -25,6 +26,7 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@@ -46,17 +48,18 @@ fun HomeScreen(
// started: MutableState,
modifier: Modifier = Modifier,
) {
-
val context = LocalContext.current
+ val allroutes = routeViewModel.allRoutes.collectAsState(emptyList())
val vpnLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
) {result ->
when(result.resultCode) {
RESULT_OK -> {
- serviceViewModel.setVPNPermission(true)
+ // serviceViewModel.startService(context)
+ // serviceViewModel.setVPNPermission(true)
+ serviceViewModel.startService(context, allroutes.value.toTypedArray())
}
RESULT_CANCELED -> {
- serviceViewModel.setVPNPermission(false)
Toast.makeText(context, "vpn permission denied", Toast.LENGTH_SHORT).show()
}
}
@@ -115,12 +118,12 @@ fun HomeScreen(
// if is running, should stop service
serviceViewModel.stopVpnService(context)
} else {
- serviceViewModel.startService(
- context,
- {intent->
- vpnLauncher.launch(intent)
- },
- )
+ val intent = VpnService.prepare(context)
+ if (intent != null) {
+ vpnLauncher.launch(intent)
+ } else {
+ serviceViewModel.startService(context, allroutes.value.toTypedArray())
+ }
}
// started.value = !started.value
},
@@ -142,23 +145,3 @@ fun HomeScreen(
}
}
-
-private fun startVpnService(context: Context) {
- val intent = Intent(context, PunchnetService::class.java)
-
- intent.putExtra("argument", PunchnetServiceArgument(
- "",
- arrayOf(
- RouteInfo(
- "192.168.80.0/24", "10.211.188.2"
- )
- )
- )
- )
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- context.startForegroundService(intent)
- } else {
- context.startService(intent)
- }
- Toast.makeText(context, "VPN service started", Toast.LENGTH_SHORT).show()
-}
diff --git a/app/src/main/java/com/jihe/punchnet/screen/RouteScreen.kt b/app/src/main/java/com/jihe/punchnet/screen/RouteScreen.kt
new file mode 100644
index 0000000..d46dd9b
--- /dev/null
+++ b/app/src/main/java/com/jihe/punchnet/screen/RouteScreen.kt
@@ -0,0 +1,211 @@
+package com.jihe.punchnet.screen
+
+import android.widget.Toast
+import androidx.collection.emptyLongSet
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Clear
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.Card
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
+import androidx.room.util.TableInfo
+import com.jihe.punchnet.data.RouteItem
+import com.jihe.punchnet.data.RouteViewModel
+import com.jihe.punchnet.helper.IntToIPString
+import com.jihe.punchnet.helper.parseCIDRAndGW
+import com.jihe.punchnet.sdlan.network.maskIPToDigit
+
+@Composable
+fun AddOrModifyRoute(
+ routeViewModel: RouteViewModel,
+ initRoute: MutableState,
+ shouldShowRoute: MutableState,
+
+) {
+ if (shouldShowRoute.value) {
+ val context = LocalContext.current
+ var cidr by remember {
+ mutableStateOf(
+ if (initRoute.value == null)
+ ""
+ else
+ ""
+ )
+ }
+
+ var gw by remember {
+ mutableStateOf(
+ if (initRoute.value == null)
+ ""
+ else
+ ""
+ )
+ }
+
+ AlertDialog(
+ onDismissRequest = {
+ shouldShowRoute.value = false
+ },
+ confirmButton = {
+ Button(
+ onClick = {
+ val item = parseCIDRAndGW(cidr, gw)
+ if (item == null) {
+ Toast.makeText(context, "数据解析出错", Toast.LENGTH_SHORT).show()
+ } else {
+ routeViewModel.insert(item)
+ shouldShowRoute.value = false
+ }
+ }
+ ) {
+ Text("确定")
+ }
+ },
+ title = {
+ Text(
+ "添加新路由"
+ )
+ },
+ text = {
+ Column {
+ TextField(
+ value = cidr,
+ onValueChange = { newValue ->
+ cidr = newValue
+ },
+ placeholder = {
+ Text(
+ "192.168.0.0/24",
+ color = MaterialTheme.colorScheme.onBackground.copy(.5f)
+ )
+ },
+ label = {
+ Text("CIDR")
+ },
+ modifier = Modifier.padding(start = 16.dp)
+ )
+
+
+ TextField(
+ value = gw,
+ onValueChange = { newValue ->
+ gw = newValue
+ },
+ placeholder = {
+ Text(
+ "10.167.69.13",
+ color = MaterialTheme.colorScheme.onBackground.copy(.5f)
+ )
+ },
+ label = {
+ Text("gateway")
+ },
+ modifier = Modifier.padding(start = 16.dp, top = 8.dp)
+ )
+ }
+ }
+ )
+ }
+}
+
+
+@Composable
+fun RouteScreen(
+ enabled: Boolean,
+ routeViewModel: RouteViewModel,
+ modifier: Modifier = Modifier,
+) {
+ val showAddRoute: MutableState = remember { mutableStateOf(null) }
+ val shouldShow: MutableState = remember { mutableStateOf(false) }
+
+ AddOrModifyRoute(routeViewModel, showAddRoute, shouldShow)
+
+ val routeItems by routeViewModel.allRoutes.collectAsState(initial = emptyList())
+ LazyColumn {
+ item {
+ Row(
+ ) {
+
+ }
+ }
+ items(routeItems) {item ->
+ RouteCard(routeViewModel, item, enabled)
+ }
+ item {
+ Button(
+ enabled = enabled,
+ shape = RectangleShape,
+ onClick = {
+ shouldShow.value = true
+ },
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Text("添加路由")
+ }
+ }
+ }
+}
+
+@Composable
+fun RouteCard(
+ routeViewModel: RouteViewModel,
+ item: RouteItem,
+ enabled: Boolean,
+ modifier: Modifier = Modifier,
+) {
+ Card (
+ modifier = Modifier.fillMaxWidth()
+
+ ){
+ Row (
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.height(36.dp)
+ ){
+ Icon(
+ Icons.Default.Clear,
+ contentDescription = "delete route",
+ modifier = Modifier.size(30.dp).clickable(
+ enabled = enabled,
+ ) {
+ routeViewModel.deleteById(item.id)
+ }
+ )
+ Text(
+ "${IntToIPString(item.net_ip)}/${maskIPToDigit(item.mask_ip)}",
+ modifier = Modifier.padding(start = 8.dp)
+ )
+
+ Text(
+ IntToIPString(item.gateway),
+ modifier = Modifier.padding(start = 8.dp)
+ )
+
+ }
+ }
+}
\ No newline at end of file
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
index ea5054b..ca737f0 100644
--- a/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt
+++ b/app/src/main/java/com/jihe/punchnet/sdlan/network/route.kt
@@ -45,11 +45,21 @@ fun cidrToRouteDetail(cidr: String, gw: String): RouteDetail? {
)
}
+fun maskIPToDigit(mask: Int): Int {
+ var count = 0
+ var temp = mask
+ while (temp != 0) {
+ temp = temp shl 1
+ count += 1
+ }
+ return count
+}
+
fun maskDigitToInt(digit: Int): Int? {
if (digit <= 0 || digit >= 32) {
return null
}
- return ((2 shl (32-digit)) - 1)
+ return ((1 shl (32-digit)) - 1).inv()
}
fun ipStringToInt(ip: String): Int? {
diff --git a/key-store/punchnet.jks b/key-store/punchnet.jks
new file mode 100644
index 0000000..ef17162
Binary files /dev/null and b/key-store/punchnet.jks differ